@sema-agent/settings-schema 18.0.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,56 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 19.0.0 — 2026-11-03 — major:hook 条目承运 `args`(command 的 exec 形参数表)与 `onFailure`(command / http:失败按放行还是按 exit 2 阻断)· 词闭集 `HOOK_ON_FAILURE_WORDS` / 型 `HookOnFailure` · 这两个键的坏值自此是坏块(校验收紧)
12
+
13
+ > 档位(按发版纪律):**major** —— 校验收紧、同一份配置解析出不同结果(发版纪律「破坏性变更必 bump major」点名的两类),理由是下面「会变的三处」,尤其第 ②:守卫事件里一个坏值能让整份设置不装。加性的部分:新导出两个(`HOOK_ON_FAILURE_WORDS` 值、`HookOnFailure` 型);hook 条目字段表多两个可选列(`CommandHookEntry` 多可选键 `args` 与 `onFailure`,`HttpHookEntry` 多可选键 `onFailure`;对 `keyof` 写穷举表的消费方在提货时编译红,是提货成本)。
14
+ > **同一输入解析结果会变的三处**:① 带这两个键的条目,装上的那一份自此带着它们(此前被当作契约外的键剥掉);② 这两个键带**坏值**的条目 —— 词表外的 `onFailure`、不是串数组或含 NUL 元素的 `args` —— 自此是坏块:非守卫事件里剔那一条,**守卫事件(`PreToolUse` / `PermissionRequest`)里整份设置不装,同文件的 `permissions.deny` 一起不生效**;此前这两个键不论值是什么都被剥掉、整份照装(值里藏守卫迹象的除外:那一直是整份拒,见 `classifyHooksConfig` 头注)。上面是分类器的读法;zod 面(`HooksConfig` / `HookEntry` / 逐型五只)对该值**整个拒**,`readSettingsUnit("hooks", …)` 对该设置单元**整个答 `unreadable`**(此前都受理)——不止「剔那一条」;
15
+ > ③ 已经是坏块的条目若还带着这两个键的坏值,`field` 点名字段表序里第一个坏字段 —— `args` 在表里排在 `shell` / `async` / `asyncRewake` / `onFailure` 与公共字段(`if` / `timeout` / `statusMessage` / `once`)之前,`onFailure` 排在公共字段之前(command 里在 `async*` 之后,http 里在 `headers` / `allowedEnvVars` 之后),所以 `{ type: "command", command: "x", args: [1], shell: "zsh" }` 的 `field` 由 `shell` 变 `args`(`async: "yes"` / `asyncRewake: 5` 同),`{ type: "command", command: "x", onFailure: "retry", timeout: 0 }` 的由 `timeout` 变 `onFailure`(结局不变:仍是同一条坏块)。对不带这两个键的文档,读数逐字不变(差分门语料里原先拿 `onFailure` 当契约外键的样本换成了 `rewakeSummary`,其余样本零翻面)。
16
+ > **迁移**:不配一次性迁移 —— 坏值没有可猜的正确值,要人改。升级前检查设置文件、请求体里的 `hooks`(含存下来要续跑的):把词表外的 `onFailure` 改成 `continue` | `block`、把不合形的 `args` 改成串数组(元素不含 NUL;`[]` 与空串元素合法),或把这两个键整个删掉(删掉后读数逐字同 18.0.0)。不确定就先跑一遍 `classifyHooksConfig`:结局不是 `loaded`,或 `field` 是 `args` / `onFailure`,就是要改的那一条。
17
+ > 律:hook 条目的字段集只有一张表(`HOOK_ENTRY_FIELDS`),分类器、zod 面、条目类型同读它;「承运」与「执行」是两件事 —— 本包只判形,读到 `onFailure: "block"` 的消费方执行它或明拒它,静默不执行等于守卫起不来照放行。
18
+
19
+ ### 字段表两列与词闭集
20
+
21
+ - **`args`(只 command)**:exec 形参数表(CC `cli309.js:252382`:`args` 在场时 `command` 是一个可执行文件、带这些参数直接 spawn,不过 shell;在场本身就是开关,`[]` 也是 exec 形)。值 = 串数组,装上时拷贝;本包另判**元素零 NUL**(`spawn()` 对含 NUL 的参数抛;CC 的 `C(o())` 不判);空串元素是值(空参数是合法的 exec 参数)。别的四型上写 `args` 仍是契约外的键(剥掉,不算坏块;键下装着守卫迹象的除外)。
22
+ - **`onFailure`(command 与 http)**:这个 hook 的**失败**怎么办 —— 起不来(脚本缺失)、超时、退出码非 0 非 2、输出的 JSON 非法或校验不过。`continue`:失败被报告,动作照做;`block`:失败按 exit 2 算,事件所守卫的动作被阻断(CC 的 `onFailure:At()` 只在 command 与 http 两个 schema 上;对异步条目与 `Stop` / `SubagentStop` / `TaskCompleted` / `TeammateIdle` 事件不生效,那是执行方的事)。
23
+ 缺席就是缺席:本包不补缺省(CC 的缺省是 `continue`,读方自己补)。prompt / agent / mcp_tool 上写 `onFailure` 仍是契约外的键。
24
+ - **`HOOK_ON_FAILURE_WORDS = ["continue", "block"] as const`**(包根与 `./hooks`)· 型 **`HookOnFailure`**。登记表 `docs/API-SURFACE.md` 已重生。
25
+ - **一事一源**:字段表仍是唯一声明 —— 两个新取值类(`argv` · `onFailure`)是表里的两个词,分类器的取值判、zod 面、条目类型都从表读;没有按字段名写的分支。词表的运行期镜像是分类器闭包私有的 `Record<HookOnFailure, true>`(分类器被消费方的生成器整函数抄走,自由名只有 `HOOK_EVENTS`,读不了导出的词表),
26
+ 编译器双向咬死 —— 导出词表加一个词而镜像不补一行、或镜像多一行,`tsc` 红;钉再核一遍:分类器受理的值恰是导出的词表。
27
+ - **仍不承运**(`docs/CC-DIVERGENCE.md` H7 留):command / http 的 `rewakeMessage` · `rewakeSummary` · `cloud`(CC 标 `@internal`)与 prompt 的 `continueOnBlock` —— 本版仍按契约外的键剥掉、不看值:守卫事件里 `rewakeMessage: ""`、非布尔的 `continueOnBlock` 照装,比 CC 松(CC 的 schema 判这两个值;`cloud` 的坏值 CC 读成 `skip` 照装,与本包同)。**已知限制**,随下一个 major 承运这四个余键时一并收(键下装着守卫迹象的除外:那一直是整份拒,不因本版改变)。
28
+ - **三问** —— **谁需要**:把 `hooks` 当守卫写的部署与壳:`onFailure: "block"` 让「守卫起不来 / 超时 / 吐坏 JSON」按拒绝算而不是照放行;`args` 让带引号 / `$` / 反引号的路径不过 shell 解析器;此前这两个键一进契约就被剥掉,守卫起不来照放行、exec 形条目只剩可执行名去跑。
29
+ **谁受伤**:第 ① 处,依消费方是否执行 exec 形而定 —— 带 `args` 且 `command` 是整行 shell 命令的条目(例 `{ command: "/g.sh --strict", args: [] }`):18.0.0 的消费方把 `args` 剥掉,整行过 shell 照跑;执行 exec 形的 19.0.0 消费方把整行当可执行文件名去 spawn,起不来,缺省 `continue` 下被守卫的动作照做(CC 同读,这类文档对 CC 本来就是错的);第 ② 处 —— 在这两个键上写过坏值的设置文档(词表外的 `onFailure`、不合形的 `args`);守卫事件里是整份不装。CC 的 schema 对这两个键同样判值(`onFailure` 是 `continue` | `block` 的枚举,`args` 是串数组),所以按 CC 写的文档只在一种情形下受伤:`args` 的元素含 NUL(CC 不判,本包判)。
30
+ **补偿**:坏块的 `field` 点名 `args` / `onFailure`;改成 `continue` | `block` / 串数组即可。不想要这两个键的文档删掉它们,读数逐字同 18.0.0。
31
+
32
+ ### 消费方提货
33
+
34
+ - **server 7.147.0**:`CommandHookEntry` / `HttpHookEntry` 的 `onFailure`(随条目到达,`block` 的失败按 exit 2 执行;不执行就要在受理面明拒)· `args`(契约字段)· `HOOK_ON_FAILURE_WORDS` / `HookOnFailure`(穷举处读词表)。
35
+ - **cli**:`CommandHookEntry` / `HttpHookEntry` 的两个键 —— 壳若自己执行 hook(引擎属主事件 `ENGINE_OWNED_HOOK_EVENTS` 以外)执行它们或明拒;「能不能装」仍只问 `classifyHooksConfig`。
36
+ - **client-core**:由 `classifyHooksConfig` 生成的读器副本 —— 闭包多一个私有件 `HOOK_ON_FAILURE_ACCEPTED`(与 `HOOK_SHELLS` 同法随函数抄走),自由名仍只有 `HOOK_EVENTS`;生成器照旧整函数抄。
37
+ - **web-admin**:`HookEntry` 一族的编辑面可选地承运两列;不读也不受影响(加性的类型面)。
38
+ - **sdk**:类型面 —— 多两个可选键;对 `CommandHookEntry` / `HttpHookEntry` 的 `keyof` 写穷举表的地方编译红,补两列。
39
+ - 文书:README hooks 条一句 + 升级指引(18.0.x → 19.0.0)· `llms.txt` / `llms-full.txt` · `docs/CONSUMER-INTEGRATION.md` §18 · `docs/CC-DIVERGENCE.md` H7 改准 · `docs/API-SURFACE.md` 重生。
40
+
41
+ ### 钉
42
+
43
+ `test/hooks-command-fields-19.0.0.test.ts`(承运集:每型装上的键与键序、类型面 `keyof` 精确 · `args` 正控 / `[]` 与空串元素 / 坏形(非数组 · 非串元素 · 含 NUL)× 守卫与非守卫 / 别的四型不带 · `onFailure` 两词 × 两型 × 两类事件 / 缺席不补缺省 / 词表外与非串(含大小写变体与原型名)× 两型 × 两类事件 / 别的三型不带 · zod 面投影 ·
44
+ 词表导出与分类器同一份读法〔含原型名;分类器私有镜像的键从源码字面量读出,与导出元组逐词比〕· 四个仍不承运的键〕)· `test/hooks-classify-13.0.0.test.ts`(两处把 `onFailure` 当「契约外键」的例子换成 `rewakeSummary`,判不变:契约外的键剥掉、不算坏块;差分门语料的 `extra` 样本同换)· `test/docs-19.0.0.test.ts`(文书对账)。
45
+
46
+ **自 v18.0.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
47
+
48
+ ```text
49
+ AgentHookEntry [., ./hooks]
50
+ CommandHookEntry [., ./hooks]
51
+ HookEntry [., ./hooks]
52
+ HookMatcherEntry [., ./hooks]
53
+ HooksConfig [., ./hooks]
54
+ HttpHookEntry [., ./hooks]
55
+ McpToolHookEntry [., ./hooks]
56
+ PromptHookEntry [., ./hooks]
57
+ classifyHooksConfig [., ./hooks]
58
+ readSettingsUnit [.]
59
+ ```
60
+
11
61
  ## 18.0.0 — 2026-11-03 — major:车道词 `none`(不给智能体的工具提供执行环境)· `REQUIRE_EXECUTION_LANE` 进墓碑表 · 车道集单一判官 `laneSetFlaws` / `LANE_SET_RULE` · 便携新域 `a2a`(常驻出站 A2A 对端名册)· 凭据引用集合不可部分抢救 `ATOMIC_SALVAGE` · 复合凭据名 `credentialParamWordOf`
12
62
 
13
63
  > 档位(按发版纪律):**major** —— 三处不加性边:① 词表联合加成员(`ExecutionLane` 加 `none`、`TenantSeparationBasis` 加 `no-execution-target`、`RemoteExecSpec` 加 `none` 臂、`DomainName` 尾插 `a2a`;对它们写穷举 `switch` / `Record<…>` 的消费方编译红,把不认识的词读成某条车道的消费方运行期读错;公开记录读器的保留成员名由一个名改成一张表 —— `PLAIN_STRING_RECORD_RESERVED_MEMBER` 删除、`PlainStringRecordRead` 的 `reservedMember: boolean` 改名 `reservedMembers`,读它们的消费方编译红);
package/README.md CHANGED
@@ -47,6 +47,16 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
47
47
  > zod 3, nested inside every zod-4 host). The move to zod 4 did not change what any schema accepts or produces —
48
48
  > the conformance corpus reads byte-identically on both — only the wording of validation errors (see CHANGELOG 11.0.0).
49
49
 
50
+ ## 升级指引:18.0.x → 19.0.0(BREAKING:hook 条目承运 `args` / `onFailure`,这两个键的坏值自此是坏块)
51
+
52
+ 一处**不加性**的边(三问与逐条细节见 CHANGELOG 19.0.0):
53
+
54
+ 1. **`hooks` 条目的两个键此前被当作契约外的键剥掉,自此是契约字段**:command 条目的 `args`(exec 形参数表,串数组、元素不含 NUL;`[]` 与空串元素合法)、command / http 条目的 `onFailure`(`HOOK_ON_FAILURE_WORDS` = `continue` | `block`,型 `HookOnFailure`;缺席不补缺省)。别的条目型上写这两个键仍是契约外的键。
55
+ - 合形的值:装上的那一份自此带着它们(此前没有)。读到 `onFailure: "block"` 的一侧要执行它(失败按 exit 2)或在自己的受理面明拒,不能静默忽略。
56
+ - **坏值**(词表外的 `onFailure`、不是串数组或含 NUL 元素的 `args`):**非守卫事件里那一条 hook 被剔;守卫事件(`PreToolUse` / `PermissionRequest`)里整份设置不装,同文件的 `permissions.deny` 一起不生效**。此前这两个键不论值是什么都被剥掉、整份照装。**例外 —— 值里装着守卫事件名记号**(例:`Stop` 事件的条目 `args: { "PreToolUse": "guard" }`):18.0.0 与 19.0.0 **都**整份拒(`guard-entry-invalid`,`field` 点名 `args`),不是「18.0.0 剥掉后装、19.0.0 只剔那一条」;本版没改这一条。zod 面与 `readSettingsUnit("hooks", …)` 对坏值则整个拒 / 整个答 `unreadable`(此前都受理)。
57
+ - 动作:升级前把设置文件与请求体里(含存下来要续跑的)词表外的 `onFailure` 改成 `continue` | `block`、不合形的 `args` 改成串数组,或整个删掉这两个键(删掉后读数逐字同 18.0.0)。不确定就跑一遍 `classifyHooksConfig(settingsDoc)`:结局不是 `loaded`,或拒 / 剔项的 `field` 是 `args` / `onFailure`,就是要改的那一条。
58
+ 2. 对 `CommandHookEntry` / `HttpHookEntry` 的 `keyof` 写穷举表的地方编译红(多两个可选键):补两列。已是坏块的条目若还带着这两个键的坏值,`field` 点名字段表序里第一个坏字段(`args` 排在 `shell` / `async` / `asyncRewake` / `onFailure` 与公共字段之前,`onFailure` 排在公共字段之前)。
59
+
50
60
  ## 升级指引:17.2.x → 18.0.0(BREAKING:车道词 `none` · `REQUIRE_EXECUTION_LANE` 进墓碑表 · 新域 `a2a` 成为 `EffectiveConfig` 的必填成员)
51
61
 
52
62
  三处**不加性**的边,逐条给读点与动作(三问与逐条细节见 CHANGELOG 18.0.0):
@@ -688,7 +698,8 @@ settings 单元读不进的成员**不是缺席**(`loosen` 可丢且要响,`tigh
688
698
  `HOOK_EVENT_OWNER` (engine | shell), the matcher-field table, a per-event input schema (`HookInput`),
689
699
  the output/decision shapes (`SyncHookOutput` / `HookSpecificOutput` / `HookPermissionRequestDecision`),
690
700
  the config surface (defined in `hooks-config.ts`, re-exported by `./hooks`): `HOOK_ENTRY_TYPES` (CC 2.1.296's five: command / prompt / agent / http /
691
- `mcp_tool`) and the ONE classifier `classifyHooksConfig(settingsDoc)` — which pieces of a settings document's `hooks` drop
701
+ `mcp_tool`; the field table carries `args` — the exec-form argument list — on `command` entries and `onFailure` — `HOOK_ON_FAILURE_WORDS` = `continue` | `block`, type `HookOnFailure` —
702
+ on `command` and `http` entries; carried, not executed: a consumer that reads `block` executes it or refuses it) and the ONE classifier `classifyHooksConfig(settingsDoc)` — which pieces of a settings document's `hooks` drop
692
703
  (`HOOKS_DROPPED_PIECE_CODES`), whether the whole document is rejected because a guard (`PreToolUse` / `PermissionRequest`) may be
693
704
  in a bad piece (`HOOKS_REJECTION_CODES`), and what loads; codes + `ConfigPath` coordinates + the wrong key's name (`field`: for a matcher / entry, the contract
694
705
  field whose value is wrong, or the member that holds a guard sign; absent only when the piece is not an object), no sentences, self-contained (no zod;
@@ -1 +1 @@
1
- 18.0.0
1
+ 19.0.0
@@ -52,24 +52,39 @@ export declare const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostTo
52
52
  /** One hook event name (the same union as `./hooks`' `HookEventName`, which is the zod enum over {@link HOOK_EVENTS}). */
53
53
  type HookEventWord = (typeof HOOK_EVENTS)[number];
54
54
  /**
55
- * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (shell command on the host that runs the hook),
55
+ * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (a command on the host that runs the hook: a shell command line, or — with `args` — an executable spawned directly, no shell),
56
56
  * `prompt` (one-shot LLM evaluation of the hook input), `agent` (agentic verifier loop), `http` (POST the hook input to a URL),
57
57
  * `mcp_tool` (call a tool on an already-configured MCP server). An entry of another type is a bad piece — and in a guard event that rejects
58
58
  * the whole document, which is why `test/upstream-drift.test.ts` ①b holds this set against the CC anchor snapshot.
59
59
  */
60
60
  export declare const HOOK_ENTRY_TYPES: readonly ["command", "prompt", "agent", "http", "mcp_tool"];
61
61
  export type HookEntryType = (typeof HOOK_ENTRY_TYPES)[number];
62
+ /**
63
+ * The `onFailure` closed set (CC `cli309.js:252382`, `onFailure:At()`): what a FAILURE of the hook does — it could not start (a missing script), timed out, exited with a code other
64
+ * than 0 or 2, or printed JSON that is invalid or fails validation. `continue` (CC's default when the key is absent; this package does not fill it in — an absent key stays absent): the
65
+ * failure is reported and the action goes ahead. `block`: the failure counts as exit code 2, so the action the event guards is blocked. Carried by `command` and `http` entries only.
66
+ * CARRIED, not executed here: a consumer that reads `block` executes it or refuses it — ignoring it makes a guard that cannot start let the action through.
67
+ * The classifier reads this set through a closure-private projection that the compiler ties to it in both directions.
68
+ */
69
+ export declare const HOOK_ON_FAILURE_WORDS: readonly ["continue", "block"];
70
+ /** One word of {@link HOOK_ON_FAILURE_WORDS}. */
71
+ export type HookOnFailure = (typeof HOOK_ON_FAILURE_WORDS)[number];
62
72
  /** `shell` values: 'bash' = the user's $SHELL family; 'powershell' = pwsh. Default bash. */
63
73
  declare const HOOK_SHELLS: readonly ["bash", "powershell"];
64
74
  /**
65
75
  * THE field table of a hook entry, per type — read by {@link classifyHooksConfig} (which field is bad, what is carried) and by the entry
66
76
  * types below (derived from it). Field order = the order a loaded entry's keys come out in.
67
- * · command: `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2;
77
+ * · command: `args` = exec form (CC `cli309.js:252382`: when present — `[]` included — `command` is an executable spawned directly with these arguments, no shell; the mere
78
+ * presence of `args` is the switch), `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2,
79
+ * `onFailure` (see {@link HOOK_ON_FAILURE_WORDS});
68
80
  * · prompt / agent: `prompt` ($ARGUMENTS = the hook input JSON), `model`;
69
81
  * · http: `url`, `headers` (values may reference $NAMEs listed in `allowedEnvVars` — env-NAME interpolation only, the config never carries a
70
- * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`;
82
+ * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`, `onFailure` (the other schema CC gives it to);
71
83
  * · mcp_tool: `server` (an already-configured MCP server's name), `tool`, `input` (the tool's arguments; string values may interpolate
72
84
  * `${path}` from the hook input JSON — CC 2.1.296 semantics, the runner's business).
85
+ * NOT in the table, on purpose: CC's `@internal` command / http keys `rewakeMessage` / `rewakeSummary` / `cloud` (not public settings keys; `cloud` even reads a bad value
86
+ * as 'skip' and loads the file) and the prompt entry's `continueOnBlock`. A key outside a type's row is not carried and not a bad piece (`docs/CC-DIVERGENCE.md` H7) —
87
+ * which is also why `args` on a non-command entry and `onFailure` on a prompt / agent / mcp_tool entry are such keys.
73
88
  */
74
89
  declare const HOOK_ENTRY_FIELDS: {
75
90
  readonly command: {
@@ -78,9 +93,11 @@ declare const HOOK_ENTRY_FIELDS: {
78
93
  readonly statusMessage: "string?";
79
94
  readonly once: "boolean?";
80
95
  readonly command: "text";
96
+ readonly args: "argv?";
81
97
  readonly shell: "shell?";
82
98
  readonly async: "boolean?";
83
99
  readonly asyncRewake: "boolean?";
100
+ readonly onFailure: "onFailure?";
84
101
  };
85
102
  readonly prompt: {
86
103
  readonly if: "string?";
@@ -106,6 +123,7 @@ declare const HOOK_ENTRY_FIELDS: {
106
123
  readonly url: "text";
107
124
  readonly headers: "stringRecord?";
108
125
  readonly allowedEnvVars: "stringList?";
126
+ readonly onFailure: "onFailure?";
109
127
  };
110
128
  readonly mcp_tool: {
111
129
  readonly if: "string?";
@@ -117,7 +135,7 @@ declare const HOOK_ENTRY_FIELDS: {
117
135
  readonly input: "record?";
118
136
  };
119
137
  };
120
- type HookFieldBaseValue<B> = B extends "text" | "string" ? string : B extends "shell" ? (typeof HOOK_SHELLS)[number] : B extends "boolean" ? boolean : B extends "seconds" ? number : B extends "stringRecord" ? Record<string, string> : B extends "stringList" ? string[] : B extends "record" ? Record<string, unknown> : never;
138
+ type HookFieldBaseValue<B> = B extends "text" | "string" ? string : B extends "shell" ? (typeof HOOK_SHELLS)[number] : B extends "onFailure" ? HookOnFailure : B extends "boolean" ? boolean : B extends "seconds" ? number : B extends "stringRecord" ? Record<string, string> : B extends "stringList" | "argv" ? string[] : B extends "record" ? Record<string, unknown> : never;
121
139
  type HookFieldValue<K> = K extends `${infer B}?` ? HookFieldBaseValue<B> : HookFieldBaseValue<K>;
122
140
  type Flat<O> = {
123
141
  [K in keyof O]: O[K];
@@ -130,7 +148,7 @@ type HookEntryOf<T extends HookEntryType, F = (typeof HOOK_ENTRY_FIELDS)[T]> = F
130
148
  } & {
131
149
  -readonly [K in keyof F as F[K] extends `${string}?` ? K : never]?: HookFieldValue<F[K]>;
132
150
  }>;
133
- /** `command` — shell command on the host that runs the hook (DESIGN POINT ② in `./hooks`). */
151
+ /** `command` — a command on the host that runs the hook: a shell command line, or — when the entry carries `args` — an executable spawned directly with those arguments, no shell (DESIGN POINT ② in `./hooks`). */
134
152
  export type CommandHookEntry = HookEntryOf<"command">;
135
153
  /** `prompt` — one-shot LLM evaluation of the hook input ($ARGUMENTS = input JSON). */
136
154
  export type PromptHookEntry = HookEntryOf<"prompt">;
@@ -99,14 +99,26 @@ export const HOOK_EVENTS = [...CC_198_HOOK_EVENTS, ...POST_198_HOOK_EVENTS];
99
99
  const HOOK_GUARD_EVENTS = ["PreToolUse", "PermissionRequest"];
100
100
  // ── entry vocabulary and the one field table ───────────────────────────────────────────────────────────────
101
101
  /**
102
- * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (shell command on the host that runs the hook),
102
+ * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (a command on the host that runs the hook: a shell command line, or — with `args` — an executable spawned directly, no shell),
103
103
  * `prompt` (one-shot LLM evaluation of the hook input), `agent` (agentic verifier loop), `http` (POST the hook input to a URL),
104
104
  * `mcp_tool` (call a tool on an already-configured MCP server). An entry of another type is a bad piece — and in a guard event that rejects
105
105
  * the whole document, which is why `test/upstream-drift.test.ts` ①b holds this set against the CC anchor snapshot.
106
106
  */
107
107
  export const HOOK_ENTRY_TYPES = ["command", "prompt", "agent", "http", "mcp_tool"];
108
+ /**
109
+ * The `onFailure` closed set (CC `cli309.js:252382`, `onFailure:At()`): what a FAILURE of the hook does — it could not start (a missing script), timed out, exited with a code other
110
+ * than 0 or 2, or printed JSON that is invalid or fails validation. `continue` (CC's default when the key is absent; this package does not fill it in — an absent key stays absent): the
111
+ * failure is reported and the action goes ahead. `block`: the failure counts as exit code 2, so the action the event guards is blocked. Carried by `command` and `http` entries only.
112
+ * CARRIED, not executed here: a consumer that reads `block` executes it or refuses it — ignoring it makes a guard that cannot start let the action through.
113
+ * The classifier reads this set through a closure-private projection that the compiler ties to it in both directions.
114
+ */
115
+ export const HOOK_ON_FAILURE_WORDS = ["continue", "block"];
108
116
  /** `shell` values: 'bash' = the user's $SHELL family; 'powershell' = pwsh. Default bash. */
109
117
  const HOOK_SHELLS = ["bash", "powershell"];
118
+ /** The classifier's reading of {@link HOOK_ON_FAILURE_WORDS}. The classifier is copied as source by consumers' generators, so its closure cannot read an exported name (its one free name
119
+ * is {@link HOOK_EVENTS}): this record is the closure-private projection of the exported set, and the compiler ties the two in both directions — a word added to the set without a row
120
+ * here, or a row here that the set does not hold, is a compile error. */
121
+ const HOOK_ON_FAILURE_ACCEPTED = { continue: true, block: true };
110
122
  /** Fields every entry type takes: `if` = permission-rule syntax (e.g. "Bash(git *)") evaluated against tool_name/tool_input before the hook
111
123
  * runs (tool events only; shell superset field, CC-compatible); `timeout` = seconds for this entry (default `DEFAULT_HOOK_TIMEOUT_SECONDS`);
112
124
  * `statusMessage` = spinner status message; `once` = run once, then remove. */
@@ -114,18 +126,23 @@ const HOOK_ENTRY_COMMON_FIELDS = { if: "string?", timeout: "seconds?", statusMes
114
126
  /**
115
127
  * THE field table of a hook entry, per type — read by {@link classifyHooksConfig} (which field is bad, what is carried) and by the entry
116
128
  * types below (derived from it). Field order = the order a loaded entry's keys come out in.
117
- * · command: `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2;
129
+ * · command: `args` = exec form (CC `cli309.js:252382`: when present — `[]` included — `command` is an executable spawned directly with these arguments, no shell; the mere
130
+ * presence of `args` is the switch), `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2,
131
+ * `onFailure` (see {@link HOOK_ON_FAILURE_WORDS});
118
132
  * · prompt / agent: `prompt` ($ARGUMENTS = the hook input JSON), `model`;
119
133
  * · http: `url`, `headers` (values may reference $NAMEs listed in `allowedEnvVars` — env-NAME interpolation only, the config never carries a
120
- * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`;
134
+ * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`, `onFailure` (the other schema CC gives it to);
121
135
  * · mcp_tool: `server` (an already-configured MCP server's name), `tool`, `input` (the tool's arguments; string values may interpolate
122
136
  * `${path}` from the hook input JSON — CC 2.1.296 semantics, the runner's business).
137
+ * NOT in the table, on purpose: CC's `@internal` command / http keys `rewakeMessage` / `rewakeSummary` / `cloud` (not public settings keys; `cloud` even reads a bad value
138
+ * as 'skip' and loads the file) and the prompt entry's `continueOnBlock`. A key outside a type's row is not carried and not a bad piece (`docs/CC-DIVERGENCE.md` H7) —
139
+ * which is also why `args` on a non-command entry and `onFailure` on a prompt / agent / mcp_tool entry are such keys.
123
140
  */
124
141
  const HOOK_ENTRY_FIELDS = {
125
- command: { command: "text", shell: "shell?", async: "boolean?", asyncRewake: "boolean?", ...HOOK_ENTRY_COMMON_FIELDS },
142
+ command: { command: "text", args: "argv?", shell: "shell?", async: "boolean?", asyncRewake: "boolean?", onFailure: "onFailure?", ...HOOK_ENTRY_COMMON_FIELDS },
126
143
  prompt: { prompt: "text", model: "string?", ...HOOK_ENTRY_COMMON_FIELDS },
127
144
  agent: { prompt: "text", model: "string?", ...HOOK_ENTRY_COMMON_FIELDS },
128
- http: { url: "text", headers: "stringRecord?", allowedEnvVars: "stringList?", ...HOOK_ENTRY_COMMON_FIELDS },
145
+ http: { url: "text", headers: "stringRecord?", allowedEnvVars: "stringList?", onFailure: "onFailure?", ...HOOK_ENTRY_COMMON_FIELDS },
129
146
  mcp_tool: { server: "string", tool: "string", input: "record?", ...HOOK_ENTRY_COMMON_FIELDS },
130
147
  };
131
148
  // ── classification vocabulary ──────────────────────────────────────────────────────────────────────────────
@@ -338,6 +355,17 @@ function hookFieldFits(base, value) {
338
355
  if (typeof value[i] !== "string")
339
356
  return false;
340
357
  return true;
358
+ case "argv":
359
+ if (!hookIsArray(value))
360
+ return false;
361
+ for (let i = 0; i < value.length; i++) {
362
+ const arg = value[i];
363
+ if (typeof arg !== "string" || arg.includes("\0"))
364
+ return false;
365
+ }
366
+ return true;
367
+ case "onFailure":
368
+ return typeof value === "string" && Object.hasOwn(HOOK_ON_FAILURE_ACCEPTED, value);
341
369
  case "record":
342
370
  return hookIsRecord(value);
343
371
  default: {
package/dist/hooks.d.ts CHANGED
@@ -1442,8 +1442,8 @@ export declare const HookInput: z.ZodDiscriminatedUnion<[z.ZodObject<{
1442
1442
  export type HookInput = z.infer<typeof HookInput>;
1443
1443
  /** Legacy top-level decision (CC): "approve" ≈ allow, "block" feeds `reason` back to the model. */
1444
1444
  export declare const HookDecision: z.ZodEnum<{
1445
- approve: "approve";
1446
1445
  block: "block";
1446
+ approve: "approve";
1447
1447
  }>;
1448
1448
  export type HookDecision = z.infer<typeof HookDecision>;
1449
1449
  /** PermissionRequest hook decision — mirrors CC PermissionResult (allow may rewrite input/permissions). */
@@ -1728,19 +1728,21 @@ export type HookSpecificOutput = z.infer<typeof HookSpecificOutput>;
1728
1728
  * Synchronous hook JSON output (stdout of a command hook, CC-verbatim). Exit-code semantics ride
1729
1729
  * alongside: exit 0 = success (stdout parsed as this shape when JSON); exit 2 = BLOCKING error (stderr is fed
1730
1730
  * back to the model as the block reason / additionalContext, per event); any other exit, a timeout, a spawn
1731
- * failure or malformed JSON = NON-BLOCKING failure: the run continues, and the consumer's hook-runner forwards
1731
+ * failure or malformed JSON = NON-BLOCKING failure by default (the entry's `onFailure` is absent or `continue`): the run continues, and the consumer's hook-runner forwards
1732
1732
  * it as a `hook_non_blocking_failure` notice (a `hook_notice` frame on the task/fleet stream carrying hook name,
1733
1733
  * event, reason, exit code and the stderr text redacted-then-bounded to 512 chars), so the failure is visible
1734
1734
  * from the task's point of view instead of only in server logs. There is no separate "shown to the user"
1735
1735
  * surface: what a user sees is whatever their client renders from that notice frame.
1736
+ * An entry that carries `onFailure: "block"` (a contract field of `command` and `http` entries, see `./hooks-config`) counts exactly those failures as exit 2 instead;
1737
+ * executing that is the consumer's business, and a consumer that ignores it lets through a guarded action whose guard could not run.
1736
1738
  */
1737
1739
  export declare const SyncHookOutput: z.ZodObject<{
1738
1740
  continue: z.ZodOptional<z.ZodBoolean>;
1739
1741
  suppressOutput: z.ZodOptional<z.ZodBoolean>;
1740
1742
  stopReason: z.ZodOptional<z.ZodString>;
1741
1743
  decision: z.ZodOptional<z.ZodEnum<{
1742
- approve: "approve";
1743
1744
  block: "block";
1745
+ approve: "approve";
1744
1746
  }>>;
1745
1747
  reason: z.ZodOptional<z.ZodString>;
1746
1748
  systemMessage: z.ZodOptional<z.ZodString>;
@@ -1935,8 +1937,8 @@ export declare const HookOutput: z.ZodUnion<readonly [z.ZodObject<{
1935
1937
  suppressOutput: z.ZodOptional<z.ZodBoolean>;
1936
1938
  stopReason: z.ZodOptional<z.ZodString>;
1937
1939
  decision: z.ZodOptional<z.ZodEnum<{
1938
- approve: "approve";
1939
1940
  block: "block";
1941
+ approve: "approve";
1940
1942
  }>>;
1941
1943
  reason: z.ZodOptional<z.ZodString>;
1942
1944
  systemMessage: z.ZodOptional<z.ZodString>;
package/dist/hooks.js CHANGED
@@ -546,11 +546,13 @@ export const HookSpecificOutput = z.union([
546
546
  * Synchronous hook JSON output (stdout of a command hook, CC-verbatim). Exit-code semantics ride
547
547
  * alongside: exit 0 = success (stdout parsed as this shape when JSON); exit 2 = BLOCKING error (stderr is fed
548
548
  * back to the model as the block reason / additionalContext, per event); any other exit, a timeout, a spawn
549
- * failure or malformed JSON = NON-BLOCKING failure: the run continues, and the consumer's hook-runner forwards
549
+ * failure or malformed JSON = NON-BLOCKING failure by default (the entry's `onFailure` is absent or `continue`): the run continues, and the consumer's hook-runner forwards
550
550
  * it as a `hook_non_blocking_failure` notice (a `hook_notice` frame on the task/fleet stream carrying hook name,
551
551
  * event, reason, exit code and the stderr text redacted-then-bounded to 512 chars), so the failure is visible
552
552
  * from the task's point of view instead of only in server logs. There is no separate "shown to the user"
553
553
  * surface: what a user sees is whatever their client renders from that notice frame.
554
+ * An entry that carries `onFailure: "block"` (a contract field of `command` and `http` entries, see `./hooks-config`) counts exactly those failures as exit 2 instead;
555
+ * executing that is the consumer's business, and a consumer that ignores it lets through a guarded action whose guard could not run.
554
556
  */
555
557
  export const SyncHookOutput = z.object({
556
558
  /** Whether the run should continue after the hook (default true). */
@@ -4,9 +4,9 @@
4
4
  > 每次全量重算并逐名比对 —— 加 / 删 / 改形一个导出而不重生成本档 ⇒ 红。导出面的每次变动因此以本档的 diff 出现在评审里。
5
5
  > 形:`value` = 运行期拿得到的值;`type` = 只在类型层;`value+type` = 同名的值与类型(zod schema 与它推出的类型)。
6
6
 
7
- 子路径 24 条(资产出口 `./package.json` / `./fleet-paths.json` 不是模块,不列),导出名合计 1497 个(同名在多条子路径上各计一次)。
7
+ 子路径 24 条(资产出口 `./package.json` / `./fleet-paths.json` 不是模块,不列),导出名合计 1501 个(同名在多条子路径上各计一次)。
8
8
 
9
- ## `.`(783)
9
+ ## `.`(785)
10
10
 
11
11
  - `A2aConfig` — value+type
12
12
  - `A2aPeerSpec` — value+type
@@ -215,6 +215,7 @@
215
215
  - `HOOK_EVENT_MATCHER_FIELD` — value
216
216
  - `HOOK_EVENT_OWNER` — value
217
217
  - `HOOK_INJECTED_ENV` — value
218
+ - `HOOK_ON_FAILURE_WORDS` — value
218
219
  - `HOSTS_ARRAY_MAX` — value
219
220
  - `HOST_NAME_MAX` — value
220
221
  - `HTTP_HOOK_ALLOWLIST_KEYS` — value
@@ -230,6 +231,7 @@
230
231
  - `HookInput` — value+type
231
232
  - `HookMatcherEntry` — value+type
232
233
  - `HookMatcherForm` — type
234
+ - `HookOnFailure` — type
233
235
  - `HookOutput` — value+type
234
236
  - `HookOwner` — value+type
235
237
  - `HookPermissionRequestDecision` — value+type
@@ -1287,7 +1289,7 @@
1287
1289
  - `parseSchedulerStore` — value
1288
1290
  - `serializeSchedulerStore` — value
1289
1291
 
1290
- ## `./hooks`(94)
1292
+ ## `./hooks`(96)
1291
1293
 
1292
1294
  - `AgentHookEntry` — value+type
1293
1295
  - `AssistantTurnError` — value+type
@@ -1314,6 +1316,7 @@
1314
1316
  - `HOOK_EVENT_MATCHER_FIELD` — value
1315
1317
  - `HOOK_EVENT_OWNER` — value
1316
1318
  - `HOOK_INJECTED_ENV` — value
1319
+ - `HOOK_ON_FAILURE_WORDS` — value
1317
1320
  - `HookDecision` — value+type
1318
1321
  - `HookEntry` — value+type
1319
1322
  - `HookEntryType` — type
@@ -1323,6 +1326,7 @@
1323
1326
  - `HookInput` — value+type
1324
1327
  - `HookMatcherEntry` — value+type
1325
1328
  - `HookMatcherForm` — type
1329
+ - `HookOnFailure` — type
1326
1330
  - `HookOutput` — value+type
1327
1331
  - `HookOwner` — value+type
1328
1332
  - `HookPermissionRequestDecision` — value+type
@@ -1,7 +1,7 @@
1
1
  # CC 偏离登记(本包)
2
2
 
3
3
  本包镜像或对齐 Claude Code(CC)上游的设置语义时,**有意**不照抄的地方逐条登在这里:差在哪(CC 原形与语料坐标 / 本包的形)、哪个方向、为什么、谁读它。
4
- 锚 = CC 2.1.296 语料 `cli296.js`(只 grep,不解包;坐标写行号,行号属于那一份语料,只引不抄)。
4
+ 锚 = CC 2.1.296 语料 `cli296.js`(只 grep,不解包;坐标写行号,行号属于那一份语料,只引不抄);H7 的两个新进字段另引 `cli309.js` 的坐标。
5
5
 
6
6
  方向只有两种写法:**更严**(本包拦下 / 不放行 / 不装 CC 会放行或会装的)与**更宽**(本包拦下 CC 不拦的 —— 只出现在拒绝方向)。
7
7
  本包不登「比 CC 更松」的偏离;出现了即是缺陷。
@@ -18,7 +18,7 @@
18
18
  | H4 | 已知非守卫事件的值写成一只匹配器(不是数组)⇒ 整份拒 | 剔掉那个键(警告) | 匹配器形本身就是迹象(规则一条,不因事件名分档) |
19
19
  | H5 | 整份文档本身写成一只匹配器(顶层带 `matcher` 键 + 匹配器形)⇒ 整份拒 | 无此判 | 把一只守卫匹配器误贴成整份设置时,CC 剔掉坏的 `hooks` 后照装 |
20
20
  | H6 | `hooks` 是装着**数组**的数组 ⇒ 整份拒(与装对象同判) | 只对装对象 / 装迹象的数组整份拒,装数组的剔掉 | 套一层数组的守卫不该被当成标量剔掉 |
21
- | H7 | 条目字段以本包字段表为准(`HOOK_ENTRY_FIELDS`);CC 独有字段(`args` / `onFailure` / `cloud` / `continueOnBlock` / `rewakeMessage` …)不进装上的那一份,也不算坏块 | 认得这些字段 | 契约只收本包定义的字段;字段级的上游漂移今天没有门(type 集有,见漂移门 ①b) —— 未闭环 |
21
+ | H7 | 条目字段以本包字段表为准(`HOOK_ENTRY_FIELDS`):command 收 `args`(exec 形参数表;元素另判零 NUL —— `spawn()` 对含 NUL 的参数抛,CC 的串数组不判)、command / http 收 `onFailure`(词表 `HOOK_ON_FAILURE_WORDS` = `continue` \| `block`;缺席不补缺省);其余 CC 独有字段(`cloud` / `continueOnBlock` / `rewakeMessage` / `rewakeSummary` …,CC 标 `@internal` 的键与 prompt 条目的 `continueOnBlock`)不进装上的那一份,也不算坏块 —— `args` 写在 command 以外、`onFailure` 写在 command / http 以外的条目上同属这一类(键下装着守卫迹象的除外:H3,整份拒) | 认得这些字段(`cli309.js:252382`:`args` 只在 command 的 schema 上,`onFailure` 在 command 与 http 的 schema 上) | 契约只收本包定义的字段;`args` / `onFailure` 19.0.0 起进表(此前被剥掉:exec 形条目只剩可执行名去跑,`block` 的守卫起不来照放行);字段级的上游漂移今天没有门(type 集有,见漂移门 ①b) —— 未闭环 |
22
22
 
23
23
  不是偏差的(与 CC 同):守卫事件集 {`PreToolUse`, `PermissionRequest`};守卫事件值 `null` = 剔不拒;非守卫事件里坏匹配器 / 坏条目逐个剔;hook 条目 type 五种。
24
24
 
@@ -12,6 +12,7 @@
12
12
  > 中心引用解析口 `resolveCenterRefs` 与记录读器 `readPlainStringRecord`(原因词表 `PLAIN_STRING_RECORD_FAULTS`)在 §14。
13
13
  > **17.2.0** 的 settings 单元读不进处置律在 §15,`./node` 的同步读写口(`config.d` 文档读数、决定文档读 / 写)与根桶的「显式停用」谓词在 §16。
14
14
  > **18.0.0** 的车道词 `none`、`REQUIRE_EXECUTION_LANE` 墓碑、车道集单一判官 `laneSetFlaws` 与便携新域 `a2a` 在 §17。
15
+ > **19.0.0** 的 hook 条目字段 `args`(exec 形参数表)与 `onFailure`(失败按放行还是阻断)在 §18。
15
16
 
16
17
  ---
17
18
 
@@ -651,3 +652,19 @@ fleet 成员对中心公告的可达地址(`FLEET_ADVERTISE_ADDRESS`,中心往
651
652
 
652
653
  凭据参数名(同版):「这个已解码的参数名是不是凭据名」只问包根 `credentialParamWordOf`(返回表词或 `undefined`:整名在 `CREDENTIAL_PARAM_NAMES` 上,或末段 / 尾串收在 `password` `passwd` `pwd` `secret` `apikey` 与 `secret key` / `secret access key` / `api key` / `access token` 上(后两条答各自的整名词 `api_key` / `access_token`);`password_hash` / `secretName` / `passwordless` 一类不是),`isCredentialParamName` / `findUrlCredential` 读同一只 —— 带 `?db_password=` 这类参数的 URL,受理口由受理变拒。
653
654
  读点:server 7.147.0 的日志脱敏器(URL 参数 · JSON 秘密字段 · 自由文本强锚三个名字读者与诊断值位)改读它、拿返回的词去查自己的锚强度列,受理口判官与脱敏器同一版同一天翻;cli 无读点(壳零直调这组判官);client-core 无读点(只有一格探针扫本包 d.ts 的导出名,新名字不命中它的『同类键名判官』扫描式);web-admin、sdk 不读名字判定(sdk 合同测里的固定样例 `?token=` / `?api_key=` 照旧命中)。
655
+
656
+ ## 18. hook 条目的 `args` 与 `onFailure`(19.0.0 起)—— server、web-admin、cli、client-core、sdk 各自的读法
657
+
658
+ - 字段表(`classifyHooksConfig` 与 `HookEntry` 一族 zod 面的唯一声明)多两列:command 条目的可选 `args`(串数组;`[]` 与空串元素都是值,元素含 NUL 为坏)与可选 `onFailure`,http 条目的可选 `onFailure`;
659
+ 词闭集 `HOOK_ON_FAILURE_WORDS` = `continue` | `block`(包根与 `./hooks`),型 `HookOnFailure`。缺席不补缺省(CC 的缺省 = `continue`,读方自己补)。
660
+ - 此前这两个键被分类器当契约外的键剥掉(不算坏块);自此:词表外的 `onFailure`、不合形的 `args` 是坏块(非守卫事件剔那一条,守卫事件整份拒,`field` 点名它);`args` 写在别的四型上、`onFailure` 写在 prompt / agent / mcp_tool 上仍是契约外的键(剥掉,不算坏块;键下装着守卫迹象的除外 —— 那一直是整份拒)。
661
+ `rewakeMessage` / `rewakeSummary` / `cloud` / `continueOnBlock` 仍不承运(`docs/CC-DIVERGENCE.md` H7)。
662
+ - **承运 ≠ 执行**:本包只判形。读到 `onFailure: "block"` 的消费方要么执行它(hook 起不来 / 超时 / 退出码非 0 非 2 / 输出 JSON 非法 ⇒ 按 exit 2 阻断,同步条目、非 Stop 一族事件),要么在自己的受理面明拒 —— 静默不执行等于守卫起不来照放行。
663
+
664
+ | 消费方 | 读点 | 动作 |
665
+ |---|---|---|
666
+ | server 7.147.0 | `CommandHookEntry` / `HttpHookEntry`(`args` · `onFailure`)· `HOOK_ON_FAILURE_WORDS` | 提货同版:`onFailure` 随条目到达,`block` 的失败按 exit 2 执行;`args` 是契约字段(读它的受理面以契约字段为准) |
667
+ | cli | `CommandHookEntry` / `HttpHookEntry` · `classifyHooksConfig` | 壳若自己执行 hook(引擎属主事件 `ENGINE_OWNED_HOOK_EVENTS` 以外):执行 `args` 与 `onFailure`,或明拒;「能不能装」仍只问分类器 |
668
+ | client-core | 由 `classifyHooksConfig` 生成的读器副本 | 闭包多一个私有件 `HOOK_ON_FAILURE_ACCEPTED`(与 `HOOK_SHELLS` 同法随函数抄走),自由名仍只有 `HOOK_EVENTS`;生成器照旧整函数抄 |
669
+ | web-admin | `HookEntry` 一族的编辑面 | 可选:表单承运两列 |
670
+ | sdk | `CommandHookEntry` / `HttpHookEntry`(类型面) | 加性:多两个可选键;对 `keyof` 写穷举表的地方补两列 |
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  <!--
2
2
  llms-full.txt — 自包含全文 · 消费方(使用者)视角 · @sema-agent/settings-schema
3
- 重写日期: 2026-11-03 | 包版本: 18.0.0
3
+ 重写日期: 2026-11-03 | 包版本: 19.0.0
4
4
  生成方式: 无生成脚本——按当前树 src/*.ts 导出面 + package.json + README/CHANGELOG 手写事实版。
5
5
  证据约定: 每条"它怎么工作"的断言附 (文件: 符号) 坐标,刻意不用行号(行号逐版腐烂;符号名由
6
6
  typecheck 与 test/package-surface.test.ts、test/docs-hygiene.test.ts 等常驻门钉住)。无证据推断显式标【推断】。
@@ -337,10 +337,11 @@ entitlement/execution/limits = `none`;`projects` = `salvage`,整域档位仍 `"t
337
337
  MessageDisplay/DirectoryAdded/PreModelSwitch/PostModelSwitch);owner 表 `HOOK_EVENT_OWNER`(engine|shell,单属主)、`ENGINE_OWNED_HOOK_EVENTS`、
338
338
  matcher 语义表 `HOOK_EVENT_MATCHER_FIELD`(null = 无 matcher 轴)。
339
339
  - 逐事件输入形 `*HookInput`(union `HookInput`);输出:`SyncHookOutput`(exit 0 JSON;exit 2 = 阻断,stderr
340
- 作为理由/附加上下文喂回模型;其余 exit/超时/spawn 失败/坏 JSON = 非阻断故障,消费方 hook-runner 铸
340
+ 作为理由/附加上下文喂回模型;其余 exit/超时/spawn 失败/坏 JSON = 非阻断故障〔缺省 `onFailure: "continue"`;command / http 条目写 `onFailure: "block"`(19.0.0 起的契约字段)则这些失败按 exit 2 算,执行是消费方的事〕,消费方 hook-runner 铸
341
341
  `hook_non_blocking_failure` 通知帧上任务/fleet 流,stderr 先脱敏后截断 512 字)、`AsyncHookOutput`、
342
342
  决策形 `HookDecision` / `HookPermissionRequestDecision` / `HookSpecificOutput`。
343
343
  - 配置形(src/hooks-config.ts,经 `./hooks` 整体转出):`HookEntry`(`HOOK_ENTRY_TYPES` 五型 command|prompt|agent|http|mcp_tool)→ `HookMatcherEntry` → `HooksConfig`
344
+ (条目字段只有一张表,分类器、zod 面、条目类型同读它;19.0.0 起 command 条目承运 `args` —— exec 形参数表,串数组、元素零 NUL,`[]` 与空串元素合法;command / http 条目承运 `onFailure` —— 词闭集 `HOOK_ON_FAILURE_WORDS` = continue|block,型 `HookOnFailure`,缺席不补缺省;承运 ≠ 执行;这两个键的坏值是坏块;`rewakeMessage` / `rewakeSummary` / `cloud` / `continueOnBlock` 仍不承运)
344
345
  (`Partial<Record<HookEventName, HookMatcherEntry[]>>`,事件名闭集);三者与逐型五只 zod 面都是 `classifyHooksConfig` 的投影(受理 ⇔ loaded,
345
346
  issue = 码 + 坐标 + 坏字段名)。`classifyHooksConfig(settingsDoc)` = hooks 坏形的唯一读者:结局 `loaded`/`loaded-partial`/`rejected`;剔点五码
346
347
  `HOOKS_DROPPED_PIECE_CODES`(非守卫事件的坏块逐个剔);拒点六码 `HOOKS_REJECTION_CODES`(守卫事件 PreToolUse / PermissionRequest 任何坏处、
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  <!--
2
2
  llms.txt — 消费方(使用者)视角索引 · @sema-agent/settings-schema
3
- 重写日期: 2026-11-03 | 包版本: 18.0.0
3
+ 重写日期: 2026-11-03 | 包版本: 19.0.0
4
4
  生成方式: 无生成脚本——按当前树的 src/*.ts 导出面 + package.json + README/CHANGELOG 手写事实版;
5
5
  每条断言附证据坐标(文件:符号),刻意不用行号(行号随每版腐烂,符号名由 typecheck/test 钉住)。
6
6
  刷新触发: package.json 版号变更(test/docs-hygiene.test.ts D 组钉「包版本:」与 package.json 相等、
@@ -93,7 +93,7 @@ llms.txt — 消费方(使用者)视角索引 · @sema-agent/settings-schema
93
93
  WorkerSpec 的 `runtime.execution` / `runtime.store` 两块按这张表判(写闸 = `WorkerSpec.safeParse`,拒因在 zod issue 里);
94
94
  存量 workers 行由一次性迁移改写(6.x 行 SS-M4、15.x 行 SS-M12;`migrateStoredDocuments` / bin `npx @sema-agent/settings-schema migrate <config.d directory>`),逐行终判是读侧的 `readWorkersRows`。
95
95
  (src/execution-lanes.ts;src/types.ts: `WorkerSpec`;src/one-time-migrations.ts)
96
- - **hooks 契约**:33 个 hook 事件的全集 + owner 表 + 逐事件输入/输出形 + 配置面:hook 条目五种 type(`HOOK_ENTRY_TYPES`,含 `mcp_tool`)与**一只**
96
+ - **hooks 契约**:33 个 hook 事件的全集 + owner 表 + 逐事件输入/输出形 + 配置面:hook 条目五种 type(`HOOK_ENTRY_TYPES`,含 `mcp_tool`;19.0.0 起 command 条目承运 `args`〔exec 形参数表〕、command / http 条目承运 `onFailure`〔`HOOK_ON_FAILURE_WORDS` = continue|block,型 `HookOnFailure`;承运不是执行〕)与**一只**
97
97
  坏形分类器 `classifyHooksConfig(settingsDoc)`(非守卫事件坏块逐个剔、守卫事件 PreToolUse / PermissionRequest 任何坏处或守卫迹象 ⇒ 整份拒;码 + 坐标 + 坏字段名 `field`(坏块是匹配器 / 条目 ⇒ 它上面出问题的键,块不是对象才缺席;一块多个迹象时先该块自身的迹象(自有守卫事件键;条目自己是匹配器形取 `hooks`),再按文档序找嵌套迹象;匹配器下合形条目列表里的迹象归各条目)、零句子、零 zod 自含;
98
98
  闭包里 JS 内建之外只有一个自由名 `HOOK_EVENTS` —— 整函数抄走的生成器(client-core)须把本包运行期导出 `HOOK_EVENTS`(上游常量)注入抄本作用域,不许贴字面事件表),
99
99
  `HooksConfig` / `HookEntry` 等 zod 面是它的投影;事件与 type 两张闭集与上游的漂移由常驻门看住(上游有而本包没有的词 ⇒ 门红并点名)。http 钩子 header 里 `$VAR` / `${VAR}` 的展开也只有一只读法(名单内展开、名单外与凭据名置空并回报)。(src/hooks.ts: `HOOK_EVENTS` / `interpolateHookEnvVars`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "18.0.0",
3
+ "version": "19.0.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-web-admin, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",