生命周期 Hooks 与工作区信任

七个钩子事件的触发时机、matcher 与能力,配置格式与默认值,进程钩子的 stdin 与 stdout 协议、退出码 2 与失败语义,Stop 续跑上限,钩子输出怎样进模型上下文,与 Claude Code 的兼容程度,以及项目目录里的钩子为什么要按声明摘要逐条信任。

作者 David更新于 22 篇(共 47 篇)

钩子让外部命令在 Agent 生命周期的固定时机插一手:补充上下文、改写工具输入、参与权限决策、拦下提示词,或者让即将结束的回合再跑一步。执行核心在 apps/zcode-cli/packages/core/src/hooksrunner.ts 按顺序执行并合并结果,configured-runner*.ts 把配置翻译成子进程调用,output.ts 解释钩子的 JSON 输出;事件与输出协议的类型定义在 apps/zcode-cli/packages/contracts/src/hooks/index.ts。工具调用上的四个事件由 apps/zcode-cli/packages/core/src/tool/executor/hook-flow.ts 发起,会话级的三个由 apps/zcode-cli/packages/core/src/runtime/methods/hooks.ts 发起。

钩子有四种来源:用户配置、插件、工作区(项目目录里的配置文件)和内部实现。前两种装配后就能执行;工作区钩子来自你刚克隆下来的仓库,必须按声明摘要逐条信任,相关代码分布在 packages/shared/src/workspace-hook-*.tsapps/zcode-cli/packages/core/src/hooks/workspace-hook-*.ts、adapters 的信任存储和 zcode hooks trust 子命令里。钩子怎样叠加到权限判定上,上一篇权限模式与规则已经讲过。

配置格式与默认值

用户钩子写在主配置文件的 hooks 段,通常是 ~/.zcode/cli/config.jsonapps/zcode-cli/README.md:184)。README 的示例(apps/zcode-cli/README.md:199,节选):

{
  "hooks": {
    "enabled": true,
    "timeoutMs": 60000,
    "maxOutputBytes": 32768,
    "events": {
      // ...
      "PreToolUse": [
        {
          "matcher": "^(Bash|Write|Edit)$",
          "hooks": [
            {
              "type": "process",
              "command": "node",
              "args": ["./scripts/pre-tool-hook.mjs"],
              "timeoutMs": 5000
            }
          ]
        }
      ],
      // ...
    }
  }
}
默认值说明
hooks.enabledfalseapps/zcode-cli/packages/contracts/src/config/index.ts:350总开关。多层配置里任一层为 true 即开启;写了 false 的那一层,自己的事件不并入(apps/zcode-cli/packages/adapters/src/config/config-merger.ts:178
hooks.timeoutMs60000apps/zcode-cli/packages/contracts/src/hooks/index.ts:429每个钩子的默认超时
hooks.maxOutputBytes32768apps/zcode-cli/packages/contracts/src/hooks/index.ts:428stdout、stderr 的捕获上限
hooks.events.<事件>matcher 分组数组,按配置顺序执行
matcher匹配全部规则见下
type必填processcommand
timeoutMstimeout继承根优先 timeoutMscommand 型还认以秒计的 timeout
enabledtrue单条钩子开关,写 false 不注册(apps/zcode-cli/packages/core/src/hooks/configured-runner.ts:54

两种类型的区别在 apps/zcode-cli/packages/core/src/hooks/configured-runner-callback.ts:26processcommand 当可执行文件、args 当参数数组,不经 shell;command 把整串交给 shell 执行,shell 可以写 true 或指定一个 shell,async: true 让它在后台跑,输出不参与任何决策(apps/zcode-cli/packages/core/src/hooks/runner.ts:125)。README 说目前只支持 processapps/zcode-cli/README.md:255),实际两种都支持(apps/zcode-cli/packages/contracts/src/hooks/index.ts:386)。超时的取值顺序见 packages/shared/src/workspace-hook-config.ts:113

matcher 的规则(apps/zcode-cli/packages/core/src/hooks/output.ts:98):省略或写 * 匹配全部;只由字母、数字、下划线和竖线组成时,按竖线拆成名单逐个精确比较;否则当 JavaScript 正则去 test,正则写错则匹配不上。工具事件的匹配值是工具名,另加别名:AgentTask 互为别名,ApplyPatch 同时能被 WriteEdit 匹配(apps/zcode-cli/packages/core/src/tool/compat.ts:6)。

同一事件的钩子按注册顺序逐个执行,前一个拦截了,后一个照样跑,结果合并:权限意见取 deny 优先于 ask 优先于 allow(output.ts:145),改写的输入以最后一个为准(output.ts:81),补充上下文全部累加。来源之间的次序是用户配置在前,工作区钩子插在第一个非用户来源之前,插件与内部钩子在后(configured-runner.ts:174)。

七个事件

图表加载中…
事件何时触发matcher 看到能做什么
SessionStart首个回合初始化上下文之后(startup),或恢复会话时(resume);每个运行时实例只跑一次来源 startupresume补充上下文
UserPromptSubmit用户输入写进消息历史之前不参与匹配,所有分组都跑拦下提示词,补充上下文
PreToolUse工具输入校验、归一化之后,权限判定之前工具名及别名deny、ask、allow,改写输入,补充上下文
PermissionRequest权限判为询问、审批已挂出时,与 broker 竞速工具名及别名允许、拒绝、改写输入后允许、写入权限规则
PostToolUsehandler 成功、结果序列化之后工具名及别名补充上下文
PostToolUseFailurehandler 执行中抛错之后,含超时与取消工具名及别名补充恢复用的上下文
Stop一次模型请求没有工具调用、也没有待注入的引导,回合将结束时不参与匹配带上下文续跑

对应的调用点:SessionStartapps/zcode-cli/packages/core/src/runtime/methods/turn.ts:228apps/zcode-cli/packages/core/src/runtime/methods/resume.ts:254,只跑一次靠 hooks.ts:27 的标记;UserPromptSubmitturn.ts:363,子 Agent 的回报和后台任务通知这类只给模型看的输入会跳过它(apps/zcode-cli/packages/core/src/runtime/methods/runtime-command-queue.ts:277runtime-command-queue.ts:364);工具上的四个在 apps/zcode-cli/packages/core/src/tool/executor/call-runner.ts:232apps/zcode-cli/packages/core/src/tool/executor/permission-flow.ts:224call-runner.ts:471call-runner.ts:561Stopapps/zcode-cli/packages/core/src/runtime/methods/turn-stop.ts:201

几处与直觉不同的地方。SessionStart 的类型里还有 clearcompact 两种来源(apps/zcode-cli/packages/contracts/src/hooks/index.ts:130),但代码只以 startupresume 调用它。README 说 UserPromptSubmit 的 matcher 看到原始提示词(apps/zcode-cli/README.md:189),实际调用时没有传匹配值,而匹配值为空时任何 matcher 都算命中(apps/zcode-cli/packages/core/src/hooks/runner-helpers.ts:46),Stop 同理,写了 matcher 也每次都跑。PostToolUseFailure 只覆盖 handler 开跑之后的失败;输入校验失败、被 PreToolUse 或权限拒绝的调用在此之前就返回了,不触发它。

进程钩子的协议

输入。每个钩子从 stdin 读到一行 JSON,由 createCompatibleHookStdin 生成(apps/zcode-cli/packages/core/src/hooks/configured-runner-input.ts:14):

export async function createCompatibleHookStdin(input: HookInput): Promise<{
  cleanup: () => Promise<void>;
  value: string;
}> {
  const compatible: Record<string, unknown> = {
    ...input,
    agent_type: input.agentName,
    hook_event_name: input.hookEventName,
    permission_mode: input.mode,
    session_id: input.sessionId,
  };
  const tempDir = await mkdtemp(join(tmpdir(), "zcode-hook-"));
  const transcriptPath = join(tempDir, "transcript.jsonl");
  await writeFile(transcriptPath, formatTranscript(input), "utf8");
  compatible.transcript_path = transcriptPath;
  compatible.transcriptPath = transcriptPath;

  if ("toolName" in input) {

    // 这里只补无损 alias,继续保留 ZCode camelCase 字段作为内部主契约。
    compatible.tool_name = input.toolName;
    compatible.tool_input = input.toolInput;
    compatible.tool_use_id = input.toolCallId;
  }

camelCase 字段是 ZCode 自己的契约:公共部分有 cwdhookEventNamemodesessionIdtimestamptraceIdturnIdagentNameapps/zcode-cli/packages/contracts/src/hooks/index.ts:67),各事件再加自己的字段,如 PreToolUse 的 toolInputriskLevel,UserPromptSubmit 的 prompt 与附件摘要,Stop 的 responseText、截到 4000 字符的 responsePreviewtoolCallCountstopHookActivehooks.ts:16hooks.ts:85)。snake_case 字段是给 Claude Code 风格脚本的别名,按事件还有 tool_responseerrorerror_detailsis_interruptlast_assistant_messagestop_hook_activeconfigured-runner-input.ts:39)。permission_suggestions 虽有映射,但调用方从不填 permissionSuggestionshook-flow.ts:60),所以它总是缺席。

子进程的工作目录是会话工作目录,环境里额外设置 ZCODE_PROJECT_DIRZCODE_SESSION_ID 与对应的 CLAUDE_PROJECT_DIRCLAUDE_SESSION_IDCLAUDE_CODE_SESSION_ID,插件钩子另有插件目录与数据目录(configured-runner-input.ts:74);commandargs 里的 ${ZCODE_PROJECT_DIR} 这类写法在启动前展开,${ZCODE_SKILL_DIR} 在钩子里没有意义,直接报配置错误(configured-runner-input.ts:100)。

输出。stdout 可以打印一个 JSON 对象,由 processHookOutput 解释(output.ts:11,schema 见 apps/zcode-cli/packages/contracts/src/hooks/index.ts:282):

字段效果
continue: false在 UserPromptSubmit、PreToolUse、PermissionRequest 上是拦截;其他事件只把本次运行记为 blocked,没有实际作用;Stop 上忽略
continue: true只对 Stop 有意义:请求续跑
decision: "block"同拦截;在 Stop 上是续跑,reasonsystemMessage 作为续跑的上下文
decision: "approve"PreToolUse、PermissionRequest 上等于 allow
reasonstopReason拦截理由
additionalContextadditional_context顶层补充上下文,除 PermissionRequest 外都会用上
hookSpecificOutputhookEventName 必须等于当前事件,否则算钩子失败;PreToolUse 可带 permissionDecisionpermissionDecisionReasonupdatedInput;PermissionRequest 带 decision,其中 behavior 为 allow 时可附 updatedInputupdatedPermissions(也认 permissionUpdates),为 deny 时可附 message;其余事件带 additionalContext
suppressOutputschema 接受,没有任何效果

退出码与失败configured-runner-callback.ts:118):

进程结果处理
退出码 0,stdout 为空或不以 { 开头当作没有输出
退出码 0,以 { 开头但解析不了同上,当作诊断文本(configured-runner-callback.ts:171
合法 JSON 但不符合 schema钩子失败
退出码 2拦截,理由取 stderr,没有再取 stdout,都空就是 Hook blocked execution,按事件转成相应输出(configured-runner-callback.ts:195
其他退出码、超时、取消钩子失败

“钩子失败”只会发一条 hook_run_failed 事件、记一条警告,这个钩子等于没说话,工具调用和回合照常继续(runner.ts:211);反过来,准入闸门自己抛错时一律不执行(runner-helpers.ts:21)。README 把“非 JSON 的 stdout”也算作失败(apps/zcode-cli/README.md:261),代码里并不是。超时由运行器计时(runner.ts:340),同一个值也传给执行端口;stdout 与 stderr 的捕获上限都是 maxOutputBytesconfigured-runner-callback.ts:47)。

Stop 与续跑上限

Stop 钩子要让回合再跑一步,必须同时满足三个条件(hooks.ts:120):有钩子请求续跑(continue: truedecision: "block" 或退出码 2),合并后有非空的补充上下文,本回合的续跑次数还没到常量 MAX_STOP_HOOK_CONTINUATIONS 规定的 3 次(hooks.ts:10)。计数每个回合从 0 开始(turn.ts:591)。续跑时补充上下文作为一条历史记录追加,回合机回到下一次模型请求(turn-stop.ts:208);从第二次起,钩子输入里的 stopHookActive 为 true(turn-stop.ts:206),脚本可以据此避免反复拦截。只写 continue: true 而没有上下文的输出会被忽略,README 对此的描述与代码一致。

钩子输出怎样进模型上下文

  • 工具事件:PreToolUse、PostToolUse、PostToolUseFailure 的补充上下文以 [Hook additional context] 开头、按 #1#2 编号,附在工具结果后面(hook-flow.ts:234),受该工具的模型字节预算约束(apps/zcode-cli/packages/core/src/tool/executor/result-serialization.ts:220);PreToolUse 拒绝或权限拒绝这类提前返回的错误结果也会附上(call-runner.ts:688)。
  • 会话事件:SessionStart、UserPromptSubmit、Stop 的补充上下文变成一条 hook_context 类型的 system reminder 附件进入消息历史,开头是事件名加 hook additional context:,整体截到 24000 字符(hooks.ts:15hooks.ts:106)。提醒怎样投影给模型见系统提示词、上下文与提醒
  • 诊断信息:stderr、stdout 预览只进 hook_run_* 生命周期事件,并先脱敏(apps/zcode-cli/packages/core/src/hooks/display-metadata.ts:35),不会给模型看(apps/zcode-cli/packages/core/src/hooks/types.ts:23)。内部钩子的事件对客户端不可见(display-metadata.ts:20)。

与 Claude Code 的兼容程度

ZCode 明显在向 Claude Code 的钩子看齐:这七个事件名都取自 Claude Code,stdin 补上 tool_nametool_inputhook_event_nametranscript_path 等 snake_case 别名,环境里设置 CLAUDE_PROJECT_DIRhookSpecificOutputpermissionDecision 的写法、“退出码 2 表示拦截”的约定都一样;UserPromptSubmit 与 Stop 不看 matcher,这一点也与 Claude Code 相同。插件沿用 hooks/hooks.json 的位置(apps/zcode-cli/packages/adapters/src/plugins/hook-sources.ts:8)和“事件名直接挂在 hooks 下”的形状(apps/zcode-cli/packages/adapters/src/plugins/index.ts:497)。对照 Claude Code 的公开文档,差异主要有这些:

  • 用户与项目配置多了一层 enabledevents,Claude Code 的设置文件里事件直接挂在 hooks 下;
  • Claude Code 的事件远不止七个,ZCode 只认这七个,插件里的其他事件会被跳过并记一条 plugin_hook_unsupported_event 警告(apps/zcode-cli/packages/adapters/src/plugins/index.ts:518);
  • Claude Code 的 transcript_path 指向会话记录,这里只是一个装着当前这条提示词或回答的临时文件(configured-runner-input.ts:142),钩子结束即删除(configured-runner-callback.ts:67);
  • permission_mode 取 ZCode 的档位名,如 buildyolo,不是 Claude Code 的 defaultacceptEditsbypassPermissions 那一套;
  • 超时默认 60 秒,比 Claude Code 命令钩子的默认值短得多,耗时的脚本要自己写 timeouttimeoutMs
  • PostToolUse 上的退出码 2,Claude Code 会把 stderr 交给模型,这里只把本次运行记为 blocked,理由不进上下文,SessionStart 与 PostToolUseFailure 也是如此;deny 决定里的 interrupt 不起作用(hook-flow.ts:104)。

工作区钩子为什么要信任

项目配置文件同样可以写 hooks 段。发现规则是从最近一个含 .git 的祖先目录一路到工作目录,每层看 zcode.json.zcode/config.json,找不到 .git 就只看工作目录,另加显式指定的项目配置(workspace-hook-config.ts:174workspace-hook-config.ts:335)。这些声明不会进入可执行的配置:加载时就被剥掉,只留一条 config_project_hooks_pending_trust 诊断(apps/zcode-cli/packages/adapters/src/config/project-config.adapter.ts:63project-config.adapter.ts:122),另存为一份冻结的快照(apps/zcode-cli/packages/adapters/src/config/config-factory.ts:220)。理由很直接:克隆一个仓库,不应该让仓库作者的命令在你的账号下自动运行。NOTICE 的表述是(NOTICE.md:18):

工作区 Hook 按工作区身份和声明摘要审查;声明变化会使原信任失效。该机制不等于对插件 Hook、MCP 配置或其他项目脚本的统一审核。

按什么摘要信任。每条声明各有一个 SHA-256 摘要,输入是下面这个数组的 JSON(packages/shared/src/workspace-hook-digest.ts:222):

  const execution =
    input.hook.type === "process"
      ? ["process", input.hook.command, [...(input.hook.args ?? [])]]
      : [
          "command",
          input.hook.command,
          input.hook.async === true,
          input.hook.shell === undefined
            ? ["unset"]
            : input.hook.shell === true
              ? ["true"]
              : ["string", input.hook.shell],
        ];
  return [
    "workspace-hook-declaration",
    WORKSPACE_HOOK_DIGEST_SCHEMA_VERSION,
    input.sourceRelativePath,
    input.sourceDiscoveryOrder,
    input.event,
    input.matcher,
    input.matcherIndex,
    input.hookIndex,
    execution,
    input.resolvedTimeoutMs,
    input.resolvedMaxOutputBytes,
  ];

命令、参数、shell、事件、matcher、在文件里的位置、来源文件,以及解析后的超时与输出上限,任何一项变化都会得到新摘要,旧的信任随之失效;statusMessageenabled 不在其中,改显示文字或开关不需要重新审查。信任记录以“工作区身份加声明摘要”为键(apps/zcode-cli/packages/core/src/hooks/workspace-hook-trust-evaluation.ts:66),工作区身份默认就是工作目录路径,宿主可以另给(config-factory.ts:221)。另有一个覆盖全部来源文件与声明的 bundle 摘要(workspace-hook-digest.ts:160),用于“信任当前全部”这类操作的校验。

信任状态。每次评估按固定次序给每条声明定一个状态(workspace-hook-trust-evaluation.ts:28):

  if (policy.mode === "deny") {
    trustState = "blocked_policy";
  } else if (input.storeStatus === "corrupt") {
    trustState = "blocked_untrusted";
  } else if (policy.mode === "allow_trusted_only") {
    trustState = persistent ? "trusted_persistent" : "blocked_policy";
  } else if (persistent) {
    trustState = "trusted_persistent";
  } else if (input.revokedKeys.has(key)) {
    trustState = "revoked";
  } else if (hasStaleSlotRecord(snapshot, entry, input.persistentRecords.values())) {
    trustState = "stale_digest";
  } else {
    trustState = "pending_trust";
  }
图表加载中…

stale_digest 指同一个位置(事件、来源文件、matcher、序号都相同)有旧摘要的信任记录,说明声明被改过(workspace-hook-trust-evaluation.ts:70);revoked 只是本会话内存里的标记,记录删掉后,新会话看到的就是待信任。策略有三种:默认的 user_decides、全部禁止的 deny、只允许已信任的 allow_trusted_onlyapps/zcode-cli/packages/core/src/hooks/workspace-hook-policy.ts:4);策略只能由宿主注入,项目文件填不了(apps/zcode-cli/packages/bootstrap/src/app/types.ts:186),读取策略出错时按 deny 处理(apps/zcode-cli/packages/core/src/hooks/workspace-hook-trust-coordinator.ts:155)。

最终能不能执行还要过三道配置开关:来源文件的 hooks.enabled 不是 false、这条声明的 enabled 不是 false、运行时根开关为 true(workspace-hook-config.ts:154)。根开关由默认、用户、各项目文件、环境与命令行各层的 hooks.enabled 取“任一为真”(workspace-hook-config.ts:130),所以项目文件可以自己打开开关,但打开了仍要信任。准入不在装配时一次定死:运行器在每个钩子真正执行前重新询问准入(runner.ts:87),期间有人撤销信任、收紧策略或重载信任文件,都会让安全版本号变化,下一次评估立即生效(apps/zcode-cli/packages/core/src/hooks/workspace-hook-runtime-admission.ts:148)。

信任存在哪、怎样审查

信任记录存在 ~/.zcode/security/workspace-hook-trust-v1.json;用户配置里设了 storage.dir 时换成它下面的 security 目录,相对路径按家目录解析,不随工作目录漂移,也不读项目配置(apps/zcode-cli/packages/adapters/src/storage/workspace-hook-trust-store.ts:130workspace-hook-trust-store.ts:543)。目录权限 0700、文件 0600(workspace-hook-trust-store.ts:438workspace-hook-trust-store.ts:496);多个进程靠一个 .lock 文件互斥,锁里记着进程号、进程启动时间与随机令牌,拿锁最多等 5 秒,超过 30 秒且持有者确已退出的锁才回收(workspace-hook-trust-store.ts:16);写入走临时文件、fsync 再改名。文件读不懂时改名为 .corrupt-<时间戳> 备查,所有工作区钩子按不可信处理(workspace-hook-trust-store.ts:442)。每条记录存摘要、授予时间与授予时的事件、命令、来源等快照(packages/shared/src/workspace-hook-trust-store-file.ts:37)。

信任只在支持它的宿主里生效。ZCode Protocol 服务端创建会话时显式打开这个开关(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/server-operations.ts:3382),桌面与 Web 都用 app-server --stdio 拉起它(packages/services/src/zcode-agent/zcodeAgentProcessManager.ts:369);不打开时,工作区钩子一律以 workspace_hooks_feature_disabled 拦下,连信任文件都不读(apps/zcode-cli/packages/bootstrap/src/app/workspace-hook-trust.ts:100workspace-hook-runtime-admission.ts:135)。独立 CLI 的 TUI 与 -p 创建会话时都没有传这个开关,所以从代码看,在独立 CLI 里工作区钩子不会执行;有工作区钩子被拦下时,-p 结束前会在 stderr 打印 Workspace Hooks skipped 和待信任的摘要,并提示去跑 zcode hooks trust reviewapps/zcode-cli/packages/cli/src/prompt-command.ts:624),但即使信任了,这两个入口也照样拦截。

桌面上是“软门禁”:准入层把待信任的数量随 workspace_hook_admission_updated 事件报给界面(app/workspace-hook-trust.ts:119),用户点开审查面板后开启一次审查流程,时限 10 分钟(apps/zcode-cli/packages/contracts/src/hooks/workspace-hook-trust.ts:4),选中的条目以 trust_selected 提交后写入信任文件;关闭某条声明会把 enabled: false 写回 <工作目录>/.zcode/config.json,写之前先核对磁盘上的声明与审查时一致(packages/shared/src/workspace-hook-mutation.ts:61workspace-hook-mutation.ts:134)。界面细节见桌面应用:Electron 的三层

命令行审查用 zcode hooks trust,它在全局参数解析之前就被分流(apps/zcode-cli/packages/cli/src/run.ts:306);zcode hooks 下目前只有 trust 这一组,不写动作时等于 statusapps/zcode-cli/packages/cli/src/hooks-trust-command.ts:37hooks-trust-command.ts:48),用法见 hooks-trust-command.ts:13

子命令作用
statusreview两者相同:列出每条声明的信任状态、开关、事件与 matcher、命令、来源和摘要,并给出预信任命令
grant --hook-digest <sha256>按摘要信任,可重复;摘要不在当前快照里就报 workspace_hooks_snapshot_mismatch
grant --all-current --bundle-digest <sha256>信任当前全部已启用的声明,bundle 摘要必须与当前一致,否则报 workspace_hooks_bundle_changed
revoke --hook-digest <sha256>revoke --all撤销,两者必选其一

--workspace 接受路径,或以 local:remote:ssh:container:wsl: 开头的工作区身份(hooks-trust-command.ts:127),--json 输出机器可读结果。实现在 apps/zcode-cli/packages/bootstrap/src/workspace-hook-trust-cli.ts:信任文件损坏时 grant 直接拒绝,注释说不能把恢复的副作用伪装成一次成功授权(workspace-hook-trust-cli.ts:158);它只区分“已信任”和“待信任”两种状态(workspace-hook-trust-cli.ts:33),计算根开关时只看默认、用户和项目配置(workspace-hook-trust-cli.ts:203)。

插件钩子与工作区钩子:两条准入路径

NOTICE 也专门提醒,插件钩子与工作区钩子走不同的准入路径(NOTICE.md:19)。四种来源对比:

来源从哪来准入条件hooks.enabled 控制
用户~/.zcode/cli/config.json写了就算
工作区项目里的 zcode.json.zcode/config.json逐条按摘要信任,且宿主支持信任不看合并后的开关,看上面的三道门
插件已启用插件的 hooks/hooks.json 或清单里的 hooks插件启用即准入,没有逐条审查,三方市场插件与官方同等放行不受控
内部会话信箱,ZCODE_MESSAGE_ENABLED 打开时内置不受控

插件钩子是否可执行由 canRunPluginHooks 决定,它目前恒为 true,注释承认这等于放弃了“只有官方插件能执行钩子”的信任边界,将来需要逐插件信任时再把判断收回到这里(apps/zcode-cli/packages/adapters/src/plugins/index.ts:366)。插件钩子在装配时并进运行时配置,只要有一个启用的插件带了钩子,合并结果的 enabled 就被直接置为 true(apps/zcode-cli/packages/bootstrap/src/app/runtime-config.ts:264)。从代码看这有一个副作用:用户配置里写了事件却没写 enabled 的钩子(默认视为关闭,但事件已并入)会跟着一起执行;显式写了 enabled: false 的文件,其事件在合并时就被排除(config-merger.ts:181),不受影响。还有一处不对称:项目配置里的 hooks 会被剥掉,同一个文件里的 plugins.dirs 却照常参与合并(config-merger.ts:89),列在这里的本地插件默认启用(apps/zcode-cli/packages/adapters/src/plugins/index.ts:243)。从代码看,仓库可以借一个本地插件,让自带的钩子绕开工作区信任。插件的安装、启用与变量展开见插件与官方市场。内部的信箱钩子挂在 UserPromptSubmit、PostToolUse、Stop 上,每次最多取 20 条未读消息(apps/zcode-cli/packages/core/src/hooks/session-mailbox.ts:9session-mailbox.ts:44)。

下一篇:Provider 规则、模型目录与选项映射——内置 Provider 配置从哪来、账号型 Provider 与 BYOK 怎样并存,以及“推理强度”这类选项怎样翻译成各家的请求参数。

本页目录