set-up-machine
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSet up machine
配置机器
Make every coding-agent harness on the machine (Claude Code, Codex, opencode, Cursor's IDE and CLI) match three sources: the rule table, , which holds each global rule once as what it covers; the shared global instructions file, , which every harness reads; and the user's workstation repo, named by the pointer , from which the shared file's personal parts are generated (which tool fills each role, and the personal workflow lines) and the personal permissions, permission rows each harness carries alongside the rule table's. Codex also takes explicitly declared CLI defaults from that repository's , and every machine gets the skills and commands its lists. Each harness also runs the pre-tool hook, , before every tool call, and keeps its memory off. Never remove or loosen an entry the table didn't produce: it's the user's. Harness formats change, so check the docs a harness reference links before writing; where they differ, follow the docs and name the difference in your report. Running the skill again is the audit: the same steps, ending with a diff that changes nothing.
rules.json~/.config/agents/AGENTS.md~/.config/agents/source.mdagents/codex.tomlagents/installs.jsonscripts/pre_tool_hook.py让机器上的每个编码 Agent harness(Claude Code、Codex、opencode、Cursor 的 IDE 和 CLI)与三个来源保持一致:规则表 ,其中按规则所覆盖的含义只保存一次每条全局规则;共享全局指令文件 ,每个 harness 都会读取它;以及用户的工作站仓库,由 pointer 指定。共享文件中的个人部分(每个角色由哪个 tool 填充,以及个人工作流行)和 personal permissions 均由此生成;每个 harness 会在规则表的权限行之外,携带个人 permissions 所定义的权限行。Codex 还会从该仓库的 取得明确声明的 CLI 默认值;每台机器都会安装其 列出的 skills 和 commands。每个 harness 还会在每次 tool call 前运行 pre-tool hook ,并保持 memory 关闭。绝不删除或放宽任何并非规则表生成的条目:它们属于用户。Harness 的格式会发生变化,因此写入前应检查各 harness reference 链接的文档;若文档存在差异,以文档为准,并在报告中说明差异。再次运行此 skill 即为 audit:执行相同步骤,最后得到一份没有任何变更的 diff。
rules.json~/.config/agents/AGENTS.md~/.config/agents/source.mdagents/codex.tomlagents/installs.jsonscripts/pre_tool_hook.pyParameters
参数
- : the user's own skills repo on GitHub, as
<skills-repo>. Installed only when the workstation repo has noowner/repo.agents/installs.json
- :用户在 GitHub 上的个人 skills 仓库,格式为
<skills-repo>。仅当工作站仓库没有owner/repo时才会安装。agents/installs.json
Steps
步骤
-
Inspect. Find each harness on the machine, read its reference, then the non-secret configuration fields that reference needs. Leave credentials, authentication stores and runtime state unread. Read the shared file and the pointer too, and when the pointer names a workstation repo, readand the repository's
references/workstation-repo.md,agents/instructions.mdandagents/permissions.jsonin its clone. For Codex, inspect the optionalagents/installs.jsonas its reference directs; use one resolved config home throughout setup and verification.agents/codex.toml- When the pointer is missing: read , then ask the user once which repository holds their personal setup and where it is cloned, or whether they have none. The diff writes the pointer, recording "none" too, so the next run doesn't ask.
references/workstation-repo.md - When the machine is remote or headless, such as a VPS: read first.
references/remote-machine.md - When trying a change without touching the real machine: run the steps against a copy of the home folder in the project's , and check it with
.scratch/. Start no harness there, since it would read the real login.verify.py --home <copy>
- When the pointer is missing: read
-
Propose one diff that brings each harness in line with the rule table, the shared file's shape and personal parts, the pointer, memory off, the hook wired, declared supported Codex defaults, and the installs: every skill and commandlists for this machine, each skill up to date. Give every harness found its own section, listing each gap its reference names and the hook's blind spots. Read
agents/installs.jsonwhenever a global instructions file is in the diff.references/global-instructions.mdWork out what each harness should hold, from, the personal permissions, the shared file's shape and the harness's reference, and compare it with what the machine holds. Write the whole diff: per harness and file, the exact entries or a unified diff, each line marked:rules.json- ,
added(a stricter entry added where it wins),tightened,removed(an installed skill behind its source);updated - ,
present(the hook),wired(the MCP tools a row matched);found - (the machine is stricter than the table: kept),
stricter(neither the table's nor a personal permission's: kept),extra(an installed skillignoredleaves out on purpose: kept, never installed or removed),agents/installs.json(what the harness can't express),gap(no such feature);none - beside the mark of every entry a personal permission produced (
personal;added, personal), so the user can tell those entries from the table's and never reads them aspresent, personal;extra - (not applicable) for a personal permission whose tool this harness lacks, with why: nothing is written for it there.
n/a
Useonly for memory files, a harness's own global file whose every line is already in the shared file (it becomes a link), and this skill's own wiring that points at an old script. An entry a dropped row, the table's or a personal one, left behind isremoved, for the user to remove.extra- When the workstation repo has : the diff holds a line per entry that applies to this machine, and an
agents/installs.jsonline, with why, per entry that doesn't. The file is the whole list: install nothing it leaves out.n/agives the format, the checks and the commands.references/workstation-repo.md - Without the file, with a value: the diff installs that repo's skills globally:
<skills-repo>, leaving outnpx --yes skills add <owner>/<repo> -g -a codex -a claude-code -ywhen-a claude-codeis a link to~/.claude/skills. It's~/.agents/skillsoncepresentrecords a skill from that source. With no value, it's a~/.agents/.skill-lock.jsonline.none
-
Ask once for one approval of the whole diff. A change after that needs a new approval.
- When the diff holds only and
addedinstalls fromupdated: write it without asking, since the user approved that list by writing it.agents/installs.json
- When the diff holds only
-
Back up every file the diff changes or removes. Before writing any file, copy each one into, at its path relative to the home folder (
~/.config/agents/backups/<UTC time as YYYYmmddTHHMMSSZ>/). Copy a symlink as a link..claude/settings.json -
Write exactly the approved diff. Run any install command first, such as a skill the diff installs. If one fails, write nothing and report it. In JSON and TOML files, change only the keys the diff names and keep the rest. Write through a symlink to its target.
-
Verify with, then inspect again until the diff changes nothing: no
scripts/verify.py,addedortightenedline. Report the backup folder, what's wired, the gaps, and the stricter and extra entries. For Codex, distinguish persisted defaults from effective overrides and state how subsequent sessions pick up changes.removedRun(Python 3.9+, standard library only; it writes nothing). It passes when it printspython3 <this skill>/scripts/verify.py, arules okline for every harness found, ahook wiredorpersonal okline and nopersonal noneline, everypersonal FAILline is a row codex.md says gets no rule, and declared Codex defaults match without acodex differsline, or aconfig FAILline says Codex isn't installed. Reportconfig n/aandconfig gaplines separately; they limit what the audit proves.config override
-
**检查。**在机器上查找每个 harness,阅读其 reference,然后阅读该 reference 所需的非机密配置字段。不要读取凭据、身份验证存储和运行时状态。还要读取共享文件与 pointer;当 pointer 指定某个工作站仓库时,在其 clone 中读取以及仓库的
references/workstation-repo.md、agents/instructions.md和agents/permissions.json。对于 Codex,按其 reference 的指引检查可选的agents/installs.json;在设置和验证过程中始终使用同一个解析后的 config home。agents/codex.toml- **pointer 缺失时:**读取 ,然后一次性询问用户:哪个仓库保存其个人设置、该仓库 clone 在哪里,或者是否没有这样的仓库。diff 会写入 pointer;即使答案为“none”也会记录下来,因此下次运行时不再询问。
references/workstation-repo.md - **机器是远程或无头环境,例如 VPS 时:**先读取 。
references/remote-machine.md - **需要在不触碰真实机器的情况下试用变更时:**针对项目 中 home 文件夹的副本执行这些步骤,并用
.scratch/检查。不要在那里启动任何 harness,否则它会读取真实登录信息。verify.py --home <copy>
- **pointer 缺失时:**读取
-
**提出一份 diff。**让每个 harness 与规则表、共享文件的结构和个人部分、pointer、关闭 memory、hook 接线、已声明且受支持的 Codex 默认值以及 installs 保持一致:对于本机,纳入列出的每个 skill 和 command,并确保每个 skill 均为最新版本。为找到的每个 harness 设置独立章节,列出其 reference 指出的每个 gap 以及 hook 的盲区。只要 diff 中包含全局指令文件,就读取
agents/installs.json。references/global-instructions.md根据、个人 permissions、共享文件的结构和 harness 的 reference,确定每个 harness 应包含的内容,再与机器当前的内容进行比较。写出完整 diff:按 harness 和文件列出精确条目或 unified diff,每一行标记为:rules.json- 、
added(新增后成为更严格且优先生效的条目)、tightened、removed(已安装的 skill 落后于其来源);updated - 、
present(hook)、wired(某一行匹配到的 MCP tools);found - (机器比规则表更严格:保留)、
stricter(既非规则表生成,也非个人 permission 生成:保留)、extra(被ignored有意排除的已安装 skill:保留,绝不由该列表安装或删除)、agents/installs.json(harness 无法表达的内容)、gap(无此功能);none - 紧挨着由个人 permission 生成的每个条目的标记(
personal;added, personal),让用户能够区分这些条目与规则表生成的条目,而不会将它们误认为present, personal;extra - (不适用),用于该 harness 缺少相应 tool 的个人 permission,并说明原因:因此不在其中写入任何内容。
n/a
仅在以下情况使用:memory 文件;某个 harness 自己的全局文件,且其中每一行都已存在于共享文件中(该文件将改为链接);以及此 skill 自身指向旧脚本的接线。某个已删除行——无论是规则表中的行还是个人 permission 中的行——遗留下来的条目属于removed,由用户决定是否删除。extra- **工作站仓库包含 时:**diff 对每个适用于本机的条目保留一行,对每个不适用的条目保留一行
agents/installs.json并说明原因。该文件是完整列表:其中排除的内容一律不安装。n/a给出格式、检查项和命令。references/workstation-repo.md - **不含该文件,但提供了 值时:**diff 会全局安装该仓库的 skills:
<skills-repo>;当npx --yes skills add <owner>/<repo> -g -a codex -a claude-code -y是指向~/.claude/skills的链接时,省略~/.agents/skills。一旦-a claude-code记录了来自该来源的 skill,该项即为~/.agents/.skill-lock.json。没有该值时,标记为present。none
-
**一次性请求批准。**就整份 diff 请求一次批准。批准后如需变更,必须重新批准。
- **当 diff 仅包含来自 的
agents/installs.json和addedinstalls 时:**无需询问,直接写入,因为用户编写该列表即已批准它。updated
- **当 diff 仅包含来自
-
**备份。**备份 diff 修改或删除的每个文件。写入任何文件之前,先将每个文件复制到,并保持其相对于 home 文件夹的路径,例如
~/.config/agents/backups/<UTC time as YYYYmmddTHHMMSSZ>/。符号链接应按链接复制。.claude/settings.json -
**写入。**精确写入已批准的 diff。先运行任何安装命令,例如 diff 要安装的 skill。如果某个命令失败,不写入任何内容并报告失败。在 JSON 和 TOML 文件中,只改 diff 指明的键,保留其余内容。符号链接应写入其目标。
-
**验证。**运行,然后再次检查,直至 diff 不再产生任何变更:没有
scripts/verify.py、added或tightened行。报告备份目录、已接线的内容、gaps,以及更严格和额外的条目。对于 Codex,区分持久化默认值与有效 overrides,并说明后续 session 如何获取这些变更。removed运行(Python 3.9+,仅使用标准库;不会写入任何内容)。当它输出python3 <this skill>/scripts/verify.py、每个找到的 harness 都有rules ok行、有hook wired或personal ok行且没有personal none行、每个personal FAIL行所指的规则都是 codex.md 标明不接收规则的行,并且声明的 Codex 默认值匹配且没有codex differs行,或者config FAIL行说明未安装 Codex 时,即表示通过。分别报告config n/a和config gap行;它们会限制 audit 所能证明的范围。config override
Rule table
规则表
rules.jsonverify.pyscripts/setupmachine/rules.pyagents/permissions.jsonverify.pyreferences/workstation-repo.mdrules.jsonverify.pyscripts/setupmachine/rules.pyagents/permissions.jsonverify.pyreferences/workstation-repo.mdA row
一行
| Field | Holds |
|---|---|
| a stable kebab-case name |
| |
| what the rule covers, shown in rejection messages |
| what it covers, in one of the three kinds below |
| why the rule exists |
| for |
| |
| optional: what no harness can catch for the row ( |
| optional, on an |
| 字段 | 含义 |
|---|---|
| 稳定的 kebab-case 名称 |
| |
| 规则涵盖的内容,显示在拒绝消息中 |
| 规则涵盖的内容,采用下面三种类型之一 |
| 规则存在的原因 |
| 对于 |
| |
| 可选:该行中任何 harness 都无法拦截的内容( |
| 可选,仅用于 |
match
kinds
matchmatch
类型
matchA object's keys say which kind it is:
match- Command: , a bare name (
program) or a list of them, and optionally:rm- : alternative word lists after the program (
subcommands);[["repo", "delete"], ["repo", "archive"]] - : every flag group the command carries, each listing one flag's names without dashes, a one-letter name being the short flag (
flags);["r", "R", "recursive"] - : words right after the subcommand and flags (
operands), or alternative word lists (["777"]);[["777"], ["a+rwx"]] - : words any one of which may appear anywhere after the subcommand (
any_operand);["main", "master"] - : the program with nothing after it (
arguments: "none");env: with flags and nothing else (arguments: "flags");declare -x - : globs one of its operands must match (
files), with an optionalcat .env;except - : globs over the names of the variables an argument expands (
variablescovers*TOKEN*, notecho $API_TOKEN).echo '$API_TOKEN'
- File: , globs relative to the project (
paths), or starting**/.envor~/;/,accessorread; an optionalwrite(except). A**/.env.examplerow covers writes too.read - MCP tool: and
server, case-insensitive regular expressions over the two parts oftool. Store the meaning (mcp__<server>__<tool>,mail), never one account's server ID, so the row matches on every machine and account.^(send|reply|forward)
一个 对象的键用于指明其类型:
match- Command:,即一个裸名称(如
program)或一组名称,并可选包含:rm- :位于 program 之后的备选单词列表(
subcommands);[["repo", "delete"], ["repo", "archive"]] - :命令携带的每个 flag 组,每组列出一个 flag 的名称且不带短横线,单字母名称表示短 flag(
flags);["r", "R", "recursive"] - :紧接在 subcommand 和 flags 之后的单词(
operands),或备选单词列表(["777"]);[["777"], ["a+rwx"]] - :subcommand 之后任意位置都可能出现的单词(
any_operand);["main", "master"] - :program 之后没有任何内容(
arguments: "none");env:只有 flags,没有其他内容(arguments: "flags");declare -x - :某个 operand 必须匹配的 glob(
files),可附带cat .env;except - :对参数所展开变量名称进行 glob 匹配(
variables可覆盖*TOKEN*,但不能覆盖echo $API_TOKEN)。echo '$API_TOKEN'
- File:,相对于项目的 glob(
paths),或以**/.env、~/开头;/,即access或read;可选的write(except)。**/.env.example行也会覆盖写入操作。read - MCP tool:和
server,分别对tool的两个部分使用不区分大小写的正则表达式。保存含义(如mcp__<server>__<tool>、mail),而不要保存某个账户的 server ID,这样该行才能在每台机器和每个账户上匹配。^(send|reply|forward)
Spellings
拼写形式
A harness that matches a command's text catches only the spellings it lists, so give it one entry per way of writing a command row:
- Programs: each program as typed, then as and
/bin/<program>, except shell builtins (/usr/bin/<program>,.,source,set,export,declare,typeset,unset,eval), which have no path.alias - Flags: a one-letter name is a short flag (), a longer one a long flag (
-r). Every order of the groups, every spelling in each group, as separate words; and, when every group has a one-letter name, the one-letter names clustered in every order (--recursive,-rf,-fr,-Rf).-fR - Order: program, then subcommand words, then flags, then operands (,
git push --force).chmod -R 777 - 's options (
find) are one-dash words written after the path, so no prefix entry catches them: a command row on-deletegets no native entry, a gap the hook covers on every harness.find
Harness 只会拦截其明确列出的 command 文本,因此 command 行每一种写法都要提供单独条目:
- **Programs:**每个 program 按用户输入的形式列出,然后是 和
/bin/<program>;但 shell builtins(/usr/bin/<program>、.、source、set、export、declare、typeset、unset、eval)没有路径。alias - **Flags:**单字母名称是短 flag(),较长的名称是长 flag(
-r)。每种组排列顺序、每组中的每种拼写,都应作为独立单词列出;当每组都有一个单字母名称时,还要将单字母名称按每种顺序组合列出(--recursive、-rf、-fr、-Rf)。-fR - **顺序:**program、subcommand 单词、flags、operands(、
git push --force)。chmod -R 777 - 的 options:(
find)是写在路径之后的单短横线单词,因此任何带前缀的条目都无法拦截:-delete的 command 行没有原生条目,这个 gap 由 hook 在每个 harness 上覆盖。find
Pre-tool hook
Pre-tool hook
scripts/pre_tool_hook.pyrules.json- deny rows: it reads a command the way the shell runs it, and checks file tools, redirects and MCP tools too. It refuses the call, naming each refused part with its rule's reason and instruction.
- allow-and-report rows: it writes one JSON line per call to , readable by the user alone; the harness's permissions decide.
<report folder>/<date>.jsonl - ask rows: the harness's native ask entries do the asking. A row with is refused instead where the call shows that no one will ask the user: Claude Code in
approver: "user",autoordontAskmode, Codex with approval policybypassPermissions, and every call under Cursor and opencode, which can't promise a prompt (their references say why).never - allow rows: it says nothing; the harness's native allow entries let the call run without a prompt.
A malformed personal file leaves the table's rows in force: the hook skips the personal permissions and reports the file.
verify.pyscripts/pre_tool_hook.pyrules.json- **行:**它按照 shell 的执行方式读取 command,并检查 file tools、重定向和 MCP tools。它拒绝调用,并针对每个被拒绝的部分说明相应规则的原因和 instruction。
deny - **行:**它会向
allow-and-report为每次调用写入一行 JSON,仅用户可读;最终是否允许由 harness 的 permissions 决定。<report folder>/<date>.jsonl - **行:**由 harness 原生的 ask 条目负责询问。带有
ask的行,在调用表明不会有人询问用户时会被直接拒绝:Claude Code 处于approver: "user"、auto或dontAsk模式,Codex 的 approval policy 为bypassPermissions,以及 Cursor 和 opencode 下的每次调用;这些 harness 无法保证弹出提示(其 references 说明了原因)。never - **行:**hook 不作任何提示;harness 原生的 allow 条目让调用无需提示即可运行。
allow
个人权限文件格式错误时,规则表中的行仍然有效:hook 会跳过个人 permissions,并由 报告该文件。
verify.pyWiring
接线
Each harness reference shows its wiring with in place of the script's path. Replace it with the absolute path of in the installed skill (under or ), never in a checkout or worktree, which can be deleted. If there is no installed copy, name the gap.
<script>scripts/pre_tool_hook.py~/.agents/skills~/.claude/skillsThe wiring fails open (, or the form a harness reference gives): if the script is gone, each call goes on under the native entries. Without it, would exit 2 on the missing script, which blocks every call.
[ -f <script> ] && … || truepython3The report folder is , or in .
~/.local/state/agents/reportsreport_dir~/.config/agents/hook.json每个 harness reference 都用 表示脚本路径。将其替换为已安装 skill 中 的绝对路径(位于 或 下),绝不能指向 checkout 或 worktree,因为它们可能会被删除。如果没有安装副本,请指出该 gap。
<script>scripts/pre_tool_hook.py~/.agents/skills~/.claude/skills接线采用 fail-open 行为(,或 harness reference 给出的形式):如果脚本消失,每次调用会依据原生条目继续执行。若没有 这样的 fail-open 兜底,脚本缺失时 会以状态码 2 退出,从而阻止每次调用。
[ -f <script> ] && … || true|| truepython3报告目录为 ,或 中的 。
~/.local/state/agents/reports~/.config/agents/hook.jsonreport_dirWhat it can't see
无法看到的内容
Name these once in every audit:
- a command inside a script file or another interpreter ();
python -c - a command built from variables ();
$cmd -rf x - an alias or function defined elsewhere;
- an abbreviated long option ();
--recur - a force push by refspec ();
git push origin +main - a glob the shell expands ();
cat .env* - an automatic reviewer, such as Codex's , answering an
auto_reviewrow, since the call's payload doesn't show the reviewer;approver: "user" - each row's own .
gap
每次 audit 中都要指出以下盲区:
- 脚本文件内部的 command,或交给另一种解释器执行的 command();
python -c - 由变量拼出的 command();
$cmd -rf x - 在别处定义的 alias 或 function;
- 缩写的长 option();
--recur - 通过 refspec 执行的 force push();
git push origin +main - 由 shell 展开的 glob();
cat .env* - 自动 reviewer(例如 Codex 的 )回答
auto_review行,因为调用的 payload 不显示 reviewer;approver: "user" - 每一行自身的 。
gap
References
参考文档
- references/global-instructions.md: the shared file's shape, the roles table, what counts as personal workflow, moving a harness's own file, and why memory stays off.
- references/rule-table.md: changing a row of the rule table, changing the hook's code and its tests, and adding a harness.
- references/workstation-repo.md: the pointer, the workstation repo's layout, generating the shared file's personal parts from it, its personal permissions, and its installs list.
- references/remote-machine.md: running agents on any remote or headless machine: signing in without a browser, per-machine settings, keeping sessions alive, and containing a misled agent.
- references/new-remote-machine.md: for a brand-new machine, first: the base it needs, done with the user as root, from key access, the dedicated user, keys-only SSH, the firewall, updates, swap and the docker group to the session host's integration, git credentials, over SSH and a headless browser.
PATH
- references/new-remote-machine.md: for a brand-new machine, first: the base it needs, done with the user as root, from key access, the dedicated user, keys-only SSH, the firewall, updates, swap and the docker group to the session host's integration, git credentials,
- One reference per harness, read for each harness found. Each says how the harness is found, where it keeps each setting, a row's native form with worked examples, the hook's wiring, and its gaps:
- references/claude-code.md: Claude Code.
- references/codex.md: Codex.
- references/opencode.md: opencode.
- references/cursor.md: Cursor's IDE and CLI.
- references/global-instructions.md:共享文件的结构、角色表、哪些内容属于个人工作流、如何迁移 harness 自己的文件,以及 memory 始终关闭的原因。
- references/rule-table.md:修改规则表中的一行、修改 hook 的代码及其测试,以及添加 harness。
- references/workstation-repo.md:pointer、工作站仓库的布局、共享文件个人部分的生成方式、个人 permissions,以及 installs 列表。
- references/remote-machine.md:在任意远程或无头机器上运行 agents:无需浏览器登录、每台机器的设置、保持 session 存活,以及控制被误导的 Agent。
- references/new-remote-machine.md:用于全新机器:首先完成机器所需的基础配置,由用户以 root 身份执行,依次包括密钥访问、专用用户、仅限密钥的 SSH、防火墙、系统更新、swap、用于 session host 集成的 docker group、Git 凭据、SSH 下的 和无头浏览器。
PATH
- references/new-remote-machine.md:用于全新机器:首先完成机器所需的基础配置,由用户以 root 身份执行,依次包括密钥访问、专用用户、仅限密钥的 SSH、防火墙、系统更新、swap、用于 session host 集成的 docker group、Git 凭据、SSH 下的
- 每个 harness 都有一个 reference;针对找到的每个 harness 都要读取。每个 reference 说明如何发现该 harness、每项设置的存放位置、一行规则对应的原生形式及示例、hook 接线以及其 gaps:
- references/claude-code.md:Claude Code。
- references/codex.md:Codex。
- references/opencode.md:opencode。
- references/cursor.md:Cursor 的 IDE 和 CLI。
Scripts
Scripts
- scripts/verify.py: checks, without writing anything, that the rules work on this machine: each row's samples through the hook, and through Codex's own policy check; that the shared file carries the workstation repo's values; that Claude Code holds each personal permission's entries; and Codex defaults, installed support and override gaps at the resolved config home.
- scripts/pre_tool_hook.py: the pre-tool hook every harness calls before each tool call.
- references/opencode-plugin.js: the opencode plugin that calls the hook.
- scripts/verify.py:在不写入任何内容的情况下,检查规则是否在本机生效:每行样本是否通过 hook 及 Codex 自身的 policy check;共享文件是否包含工作站仓库中的值;Claude Code 是否包含每条个人 permission 的条目;以及解析后的 config home 中的 Codex 默认值、已安装支持和 override gaps。
- scripts/pre_tool_hook.py:每个 harness 在每次 tool call 前调用的 pre-tool hook。
- references/opencode-plugin.js:调用该 hook 的 opencode 插件。