@namzu/sdk 62.14.0 → 63.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/connector/mcp/stdio.d.ts.map +1 -1
  3. package/dist/connector/mcp/stdio.js +3 -0
  4. package/dist/connector/mcp/stdio.js.map +1 -1
  5. package/dist/eval/compare.d.ts +54 -0
  6. package/dist/eval/compare.d.ts.map +1 -1
  7. package/dist/eval/compare.js +146 -1
  8. package/dist/eval/compare.js.map +1 -1
  9. package/dist/eval/experiment.d.ts.map +1 -1
  10. package/dist/eval/experiment.js +46 -6
  11. package/dist/eval/experiment.js.map +1 -1
  12. package/dist/eval/index.d.ts +6 -2
  13. package/dist/eval/index.d.ts.map +1 -1
  14. package/dist/eval/index.js +3 -1
  15. package/dist/eval/index.js.map +1 -1
  16. package/dist/eval/report-stats.d.ts +6 -0
  17. package/dist/eval/report-stats.d.ts.map +1 -1
  18. package/dist/eval/report-stats.js +10 -0
  19. package/dist/eval/report-stats.js.map +1 -1
  20. package/dist/eval/run-options.d.ts +25 -0
  21. package/dist/eval/run-options.d.ts.map +1 -1
  22. package/dist/eval/run-options.js +36 -0
  23. package/dist/eval/run-options.js.map +1 -1
  24. package/dist/eval/run-summary.d.ts +8 -0
  25. package/dist/eval/run-summary.d.ts.map +1 -1
  26. package/dist/eval/run-summary.js +13 -3
  27. package/dist/eval/run-summary.js.map +1 -1
  28. package/dist/eval/settings.d.ts +74 -0
  29. package/dist/eval/settings.d.ts.map +1 -0
  30. package/dist/eval/settings.js +161 -0
  31. package/dist/eval/settings.js.map +1 -0
  32. package/dist/eval/split.d.ts +48 -0
  33. package/dist/eval/split.d.ts.map +1 -0
  34. package/dist/eval/split.js +41 -0
  35. package/dist/eval/split.js.map +1 -0
  36. package/dist/eval/types.d.ts +12 -0
  37. package/dist/eval/types.d.ts.map +1 -1
  38. package/dist/execution/local.d.ts.map +1 -1
  39. package/dist/execution/local.js +2 -1
  40. package/dist/execution/local.js.map +1 -1
  41. package/dist/peers/envelope.d.ts +11 -0
  42. package/dist/peers/envelope.d.ts.map +1 -1
  43. package/dist/peers/envelope.js +29 -0
  44. package/dist/peers/envelope.js.map +1 -1
  45. package/dist/peers/index.d.ts +3 -3
  46. package/dist/peers/index.d.ts.map +1 -1
  47. package/dist/peers/index.js +2 -2
  48. package/dist/peers/index.js.map +1 -1
  49. package/dist/peers/protocol.d.ts +26 -0
  50. package/dist/peers/protocol.d.ts.map +1 -1
  51. package/dist/peers/protocol.js +22 -2
  52. package/dist/peers/protocol.js.map +1 -1
  53. package/dist/peers/record.d.ts +2 -0
  54. package/dist/peers/record.d.ts.map +1 -1
  55. package/dist/plugin/hook-context.d.ts +30 -0
  56. package/dist/plugin/hook-context.d.ts.map +1 -0
  57. package/dist/plugin/hook-context.js +51 -0
  58. package/dist/plugin/hook-context.js.map +1 -0
  59. package/dist/plugin/index.d.ts +2 -1
  60. package/dist/plugin/index.d.ts.map +1 -1
  61. package/dist/plugin/index.js +1 -0
  62. package/dist/plugin/index.js.map +1 -1
  63. package/dist/plugin/lifecycle.d.ts +9 -0
  64. package/dist/plugin/lifecycle.d.ts.map +1 -1
  65. package/dist/plugin/lifecycle.js +13 -0
  66. package/dist/plugin/lifecycle.js.map +1 -1
  67. package/dist/plugin/shell-hook.d.ts +87 -12
  68. package/dist/plugin/shell-hook.d.ts.map +1 -1
  69. package/dist/plugin/shell-hook.js +224 -32
  70. package/dist/plugin/shell-hook.js.map +1 -1
  71. package/dist/process/session-child.d.ts +21 -0
  72. package/dist/process/session-child.d.ts.map +1 -0
  73. package/dist/process/session-child.js +32 -0
  74. package/dist/process/session-child.js.map +1 -0
  75. package/dist/public-runtime.d.ts +4 -3
  76. package/dist/public-runtime.d.ts.map +1 -1
  77. package/dist/public-runtime.js +4 -3
  78. package/dist/public-runtime.js.map +1 -1
  79. package/dist/public-types.d.ts +2 -2
  80. package/dist/public-types.d.ts.map +1 -1
  81. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  82. package/dist/runtime/query/executor/tool-call-admission.js +2 -3
  83. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  84. package/dist/runtime/query/executor.d.ts +2 -0
  85. package/dist/runtime/query/executor.d.ts.map +1 -1
  86. package/dist/runtime/query/executor.js +13 -1
  87. package/dist/runtime/query/executor.js.map +1 -1
  88. package/dist/runtime/query/index.d.ts.map +1 -1
  89. package/dist/runtime/query/index.js +3 -2
  90. package/dist/runtime/query/index.js.map +1 -1
  91. package/dist/runtime/system-events.d.ts +3 -3
  92. package/dist/runtime/system-events.d.ts.map +1 -1
  93. package/dist/runtime/system-events.js +2 -1
  94. package/dist/runtime/system-events.js.map +1 -1
  95. package/dist/sandbox/provider/local.d.ts +1 -0
  96. package/dist/sandbox/provider/local.d.ts.map +1 -1
  97. package/dist/sandbox/provider/local.js +5 -2
  98. package/dist/sandbox/provider/local.js.map +1 -1
  99. package/dist/sandbox/terminal.d.ts.map +1 -1
  100. package/dist/sandbox/terminal.js +2 -1
  101. package/dist/sandbox/terminal.js.map +1 -1
  102. package/dist/tools/command-shell.d.ts.map +1 -1
  103. package/dist/tools/command-shell.js +7 -1
  104. package/dist/tools/command-shell.js.map +1 -1
  105. package/dist/types/message/index.d.ts +1 -1
  106. package/dist/types/message/index.d.ts.map +1 -1
  107. package/dist/types/message/index.js +1 -0
  108. package/dist/types/message/index.js.map +1 -1
  109. package/dist/types/plugin/index.d.ts +37 -10
  110. package/dist/types/plugin/index.d.ts.map +1 -1
  111. package/dist/types/plugin/index.js.map +1 -1
  112. package/package.json +1 -1
  113. package/src/connector/mcp/stdio.ts +3 -0
  114. package/src/eval/compare.ts +245 -2
  115. package/src/eval/experiment.ts +58 -4
  116. package/src/eval/index.ts +32 -1
  117. package/src/eval/report-stats.ts +11 -0
  118. package/src/eval/run-options.ts +51 -0
  119. package/src/eval/run-summary.ts +19 -1
  120. package/src/eval/settings.ts +225 -0
  121. package/src/eval/split.ts +83 -0
  122. package/src/eval/types.ts +12 -0
  123. package/src/execution/local.ts +2 -1
  124. package/src/peers/envelope.ts +36 -0
  125. package/src/peers/index.ts +3 -1
  126. package/src/peers/protocol.ts +24 -2
  127. package/src/plugin/hook-context.ts +58 -0
  128. package/src/plugin/index.ts +9 -0
  129. package/src/plugin/lifecycle.ts +13 -0
  130. package/src/plugin/shell-hook.ts +290 -43
  131. package/src/process/session-child.ts +38 -0
  132. package/src/public-runtime.ts +27 -0
  133. package/src/public-types.ts +2 -0
  134. package/src/runtime/query/executor/tool-call-admission.ts +2 -4
  135. package/src/runtime/query/executor.ts +15 -3
  136. package/src/runtime/query/index.ts +3 -3
  137. package/src/runtime/system-events.ts +2 -1
  138. package/src/sandbox/provider/local.ts +5 -1
  139. package/src/sandbox/terminal.ts +3 -1
  140. package/src/tools/command-shell.ts +7 -1
  141. package/src/types/message/index.ts +1 -0
  142. package/src/types/plugin/index.ts +48 -16
@@ -22,7 +22,10 @@
22
22
  * `permission_mode`. The Namzu names stay, so no existing script changes.
23
23
  * The same facts go out as
24
24
  * same facts as `NAMZU_HOOK_EVENT`, `NAMZU_SESSION_ID`, `NAMZU_TURN_ID`,
25
- * `NAMZU_TOOL_NAME` and, when the input names a file, `NAMZU_TOOL_PATH`.
25
+ * `NAMZU_TOOL_NAME` and, when the input names a file, `NAMZU_TOOL_PATH`;
26
+ * `NAMZU_PROJECT_DIR` is the folder the session was started for.
27
+ * `session_start` also sends `source` (`startup` or `resume`) and `session_end`
28
+ * `reason` (`exit` or `replaced`).
26
29
  *
27
30
  * `session_start` and `session_end` run outside any turn, so they carry no
28
31
  * `turn_id` on stdin and no `NAMZU_TURN_ID` in the environment — not even
@@ -32,8 +35,8 @@
32
35
  * Its answer is the exit code and, on exit `0`, an optional JSON object on
33
36
  * stdout. The exit codes:
34
37
  *
35
- * - `0` — carry on. On `user_prompt_submit`, plain (non-JSON) stdout becomes
36
- * context for the model.
38
+ * - `0` — carry on. On `user_prompt_submit`, `subagent_start` and
39
+ * `session_start`, plain (non-JSON) stdout becomes context for the model.
37
40
  * - `2` — **block**, read per event: before a tool (`pre_tool_use`) the call is
38
41
  * skipped and the model is told why, with the hook's stderr as the reason;
39
42
  * on `user_prompt_submit` the turn ends; on `turn_end` and `subagent_stop`
@@ -68,7 +71,9 @@
68
71
  * `permissionDecisionReason`, `updatedInput` and `additionalContext` for
69
72
  * `pre_tool_use`; `additionalContext` for `user_prompt_submit`,
70
73
  * `post_tool_use`, `post_tool_use_failure`, `turn_end`, `subagent_stop`,
71
- * `session_start`, `notification`; and for `permission_request` a `decision`
74
+ * `session_start` (context for the whole session), `notification`;
75
+ * `updatedToolOutput` for `post_tool_use` (the text the model reads instead
76
+ * of the tool's result); and for `permission_request` a `decision`
72
77
  * `{ behavior: "allow" }` or `{ behavior: "deny", message, interrupt }`.
73
78
  *
74
79
  * ## What a hook may decide, and what it may not
@@ -117,8 +122,10 @@ interface ShellHookEntryBase {
117
122
  * What this applies to. On a tool event (and `permission_request`) the tool
118
123
  * name; on `notification` the notification type. `*` or absent matches
119
124
  * everything; a list of names (`a|b`, `a,b`, each optionally ending in `*`
120
- * as a prefix) matches those; anything else is a regular expression tested
121
- * against the name.
125
+ * as a prefix) matches those without regard to case, and on a tool event
126
+ * also by the names skills write (`Bash`, `Edit`); anything else is a
127
+ * regular expression tested against the name (and, on a tool event, the
128
+ * names skills write for that tool).
122
129
  */
123
130
  readonly matcher?: string;
124
131
  /** Deadline in milliseconds; default `DEFAULT_SHELL_HOOK_TIMEOUT_MS`, capped at `MAX_SHELL_HOOK_TIMEOUT_MS`. */
@@ -138,10 +145,19 @@ interface ShellHookEntryBase {
138
145
  * What the hook's own failure does: it could not start, it timed out, it
139
146
  * exited with a code other than `0` or `2`, or it printed JSON that is not
140
147
  * valid. `continue` (the default) reports it and lets the action go ahead;
141
- * `block` counts it as exit `2`, so the action the event guards is refused.
148
+ * `block` counts it as exit `2`, so the action the event guards is refused;
149
+ * on `post_tool_use` and `post_tool_use_failure`, where the call has already
150
+ * run, it withholds the result from the model instead (set it on a hook that
151
+ * redacts).
142
152
  * Not read on a `prompt` hook or an `async` command.
143
153
  */
144
154
  readonly onFailure?: ShellHookOnFailure;
155
+ /**
156
+ * Where the entry was declared, as a label a host shows (a file's path).
157
+ * It has no effect on how the hook runs, and two entries that differ only
158
+ * here are the same entry.
159
+ */
160
+ readonly source?: string;
145
161
  }
146
162
  /** One shell command at one event. */
147
163
  export interface ShellHookEntry extends ShellHookEntryBase {
@@ -202,10 +218,29 @@ export declare const MAX_SHELL_HOOK_TIMEOUT_MS: number;
202
218
  * matches exactly, each part optionally ending in `*` as a prefix (the
203
219
  * spelling this adapter has always taken). Anything else is a regular
204
220
  * expression, tested unanchored against the name; one that does not compile
205
- * matches nothing. Case-sensitive, because tool names are. A matcher on an
206
- * event that has no name (`turn_start`) matches nothing unless it is `*`.
221
+ * matches nothing. A matcher on an event that has no name (`turn_start`)
222
+ * matches nothing unless it is `*`.
223
+ *
224
+ * Names in a list compare without regard to case. With `tool: true` (the
225
+ * event's name is a tool: the tool events and `permission_request`) they also
226
+ * take the names skills write, the way an entry's `if` does: `Bash` matches
227
+ * the tool `bash`, `MultiEdit` matches `edit`, and a prefix such as `Web*`
228
+ * matches `web_fetch` by its written name too. Where the name is not a tool
229
+ * (a notification's type, an agent's name, a session's `source` or `reason`)
230
+ * no tool alias applies, since those words are not tool names; case is still
231
+ * ignored there, because a guard that silently never runs over a capital
232
+ * letter is worse than one that also runs for a name differing only in case.
233
+ *
234
+ * A list part ending in `.*` is also tried as a regular expression.
235
+ *
236
+ * A regular expression is tested, case-sensitively as written, against the
237
+ * real name and, with `tool: true`, against each name skills write for that
238
+ * tool, so one written for those names still fires. It never matches less
239
+ * than it did against the real name alone.
207
240
  */
208
- export declare function shellHookMatches(matcher: string | undefined, toolName: string | undefined): boolean;
241
+ export declare function shellHookMatches(matcher: string | undefined, toolName: string | undefined, options?: {
242
+ readonly tool?: boolean;
243
+ }): boolean;
209
244
  /**
210
245
  * A hook's `if` against one tool call: `Tool` or `Tool(pattern)`. The tool
211
246
  * part is case-insensitive and takes the names skills write (`Bash`,
@@ -213,13 +248,23 @@ export declare function shellHookMatches(matcher: string | undefined, toolName:
213
248
  * there; for a tool with no command line the filter cannot be read, which
214
249
  * runs the hook.
215
250
  */
216
- export declare function shellHookIfMatches(filter: string | undefined, toolName: string | undefined, toolInput: unknown): boolean;
251
+ export declare function shellHookIfMatches(filter: string | undefined, toolName: string | undefined, toolInput: unknown, pathFilter?: ShellHookPathFilter): boolean;
252
+ /**
253
+ * A host's reading of a path pattern in a hook's `if`: whether the call's path
254
+ * is named by `pattern` the way the host's permission rules read it. `tool` is
255
+ * the filter's tool (`edit`, `read`), `callTool` the tool being called.
256
+ * `undefined` means the host does not read this tool's paths, and the
257
+ * filter is then read as it is for any other tool.
258
+ */
259
+ export type ShellHookPathFilter = (tool: string, pattern: string, callTool: string, toolInput: unknown) => boolean | undefined;
217
260
  /** What one hook run came back with, before it is mapped to a verdict. */
218
261
  export interface ShellHookOutcome {
219
262
  readonly exitCode: number | null;
220
263
  readonly timedOut: boolean;
221
264
  readonly stdout: string;
222
265
  readonly stderr: string;
266
+ /** Set when the hook printed more on stdout than is kept (`MAX_CAPTURE_BYTES`). */
267
+ readonly stdoutTruncated?: boolean;
223
268
  /** Set when the process could not be started at all. */
224
269
  readonly spawnError?: string;
225
270
  }
@@ -230,6 +275,12 @@ export interface ShellHookInput {
230
275
  readonly sessionId: string;
231
276
  /** The turn the hook fired in. Absent on `session_start` and `session_end`. */
232
277
  readonly turnId?: string;
278
+ /** Why the session started, on `session_start`. Sent as `source`. */
279
+ readonly sessionSource?: string;
280
+ /** Why the session closed, on `session_end`. Sent as `reason`. */
281
+ readonly endReason?: string;
282
+ /** The folder the session was started for. Sent as `NAMZU_PROJECT_DIR`; absent, `cwd`. */
283
+ readonly projectDir?: string;
233
284
  /** The delegating session, on `subagent_start` and `subagent_stop`. */
234
285
  readonly parentSessionId?: string;
235
286
  /** The parent turn whose tool call spawned the child, on `subagent_start` and `subagent_stop`. */
@@ -299,11 +350,35 @@ export interface ShellHookOptions {
299
350
  readonly transcriptPath?: (sessionId: string) => string | undefined;
300
351
  /** The permission mode in force now, sent as `permission_mode`. Called at each run. */
301
352
  readonly permissionMode?: () => string | undefined;
353
+ /**
354
+ * The folder the session was started for, sent to every command as
355
+ * `NAMZU_PROJECT_DIR`. Absent, `cwd`.
356
+ */
357
+ readonly projectDir?: string;
358
+ /** How a path pattern in an entry's `if` is read. Absent, such a filter runs the hook. */
359
+ readonly pathFilter?: ShellHookPathFilter;
360
+ /**
361
+ * A deadline, in milliseconds, for entries of an event that set no
362
+ * `timeoutMs` of their own (an entry's own is honoured as written, up to
363
+ * the cap). Absent, or an event not listed, the ordinary default applies.
364
+ */
365
+ readonly defaultTimeoutMs?: {
366
+ readonly [event in ShellHookEvent]?: number;
367
+ };
368
+ /**
369
+ * Called when a hook of this adapter ran out its deadline and was ended.
370
+ * `hook` is the command, URL or prompt text the entry names.
371
+ */
372
+ readonly onTimeout?: (info: {
373
+ readonly event: ShellHookEvent;
374
+ readonly hook: string;
375
+ readonly timeoutMs: number;
376
+ }) => void;
302
377
  }
303
378
  /** The deadline for `prompt` hooks when they name none. */
304
379
  export declare const DEFAULT_PROMPT_HOOK_TIMEOUT_MS = 30000;
305
380
  /** One entry as a hook definition the lifecycle manager accepts. */
306
- export declare function createShellHook(event: ShellHookEvent, entry: ShellHookHandler, options: ShellHookOptions): PluginHookDefinition;
381
+ export declare function createShellHook(event: ShellHookEvent, configured: ShellHookHandler, options: ShellHookOptions): PluginHookDefinition;
307
382
  /**
308
383
  * Register every entry on the manager, in file order within an event. The
309
384
  * manager starts all of an event's hooks together and reads their answers in
@@ -1 +1 @@
1
- {"version":3,"file":"shell-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/shell-hook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shell-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/shell-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAWH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EACX,oBAAoB,EAEpB,oBAAoB,EACpB,eAAe,EAEf,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,EACnB,MAAM,0BAA0B,CAAA;AAIjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAG5D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CACnC,eAAe,EACb,oBAAoB,GACpB,eAAe,GACf,aAAa,GACb,cAAc,GACd,eAAe,GACf,uBAAuB,GACvB,oBAAoB,GACpB,cAAc,GACd,aAAa,GACb,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,iBAAiB,GACjB,mBAAmB,GACnB,cAAc,GACd,YAAY,GACZ,UAAU,CACZ,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAkBtD,CAAA;AAED,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,OAAO,CAAA;AAErD,2CAA2C;AAC3C,UAAU,kBAAkB;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,gHAAgH;IAChH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAA;IACvC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,sCAAsC;AACtC,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,2FAA2F;IAC3F,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3C;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IAC1D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACvB;AAED,yCAAyC;AACzC,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,aAAa,GAAG,eAAe,CAAA;AAE/E,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,KAAK,IAAI,cAAc,CAAC,CAAC,EAAE,SAAS,gBAAgB,EAAE;CAChE,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,EAAE,QAA6D,CAAA;AAEjG,eAAO,MAAM,6BAA6B,SAAU,CAAA;AACpD,eAAO,MAAM,yBAAyB,QAAc,CAAA;AAiDpD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,OAAO,CA4BT;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,EAClB,UAAU,CAAC,EAAE,mBAAmB,GAC9B,OAAO,CAuCT;AAED;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,KACd,OAAO,GAAG,SAAS,CAAA;AAExB,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,mFAAmF;IACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,wDAAwD;IACxD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,kGAAkG;IAClG,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAA;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE;QACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;IACnD,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAA;IAC9C,yFAAyF;IACzF,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAkD9D;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAC3B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,cAAc,EACrB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,gBAAgB,CAAC,CA0I3B;AA0LD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAoB,GACvB,gBAAgB,CA4OlB;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAIjE;AAMD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAC5B,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;AAEjC,MAAM,WAAW,gBAAgB;IAChC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAA;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACnE,uFAAuF;IACvF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAClD;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,EAAE,KAAK,IAAI,cAAc,CAAC,CAAC,EAAE,MAAM;KAAE,CAAA;IAC3E;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAC3B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;QAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAC1B,KAAK,IAAI,CAAA;CACV;AAED,2DAA2D;AAC3D,eAAO,MAAM,8BAA8B,QAAS,CAAA;AA+FpD,oEAAoE;AACpE,wBAAgB,eAAe,CAC9B,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,gBAAgB,GACvB,oBAAoB,CA+HtB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,EACrD,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,GAC1D,MAAM,CAkBR"}
@@ -22,7 +22,10 @@
22
22
  * `permission_mode`. The Namzu names stay, so no existing script changes.
23
23
  * The same facts go out as
24
24
  * same facts as `NAMZU_HOOK_EVENT`, `NAMZU_SESSION_ID`, `NAMZU_TURN_ID`,
25
- * `NAMZU_TOOL_NAME` and, when the input names a file, `NAMZU_TOOL_PATH`.
25
+ * `NAMZU_TOOL_NAME` and, when the input names a file, `NAMZU_TOOL_PATH`;
26
+ * `NAMZU_PROJECT_DIR` is the folder the session was started for.
27
+ * `session_start` also sends `source` (`startup` or `resume`) and `session_end`
28
+ * `reason` (`exit` or `replaced`).
26
29
  *
27
30
  * `session_start` and `session_end` run outside any turn, so they carry no
28
31
  * `turn_id` on stdin and no `NAMZU_TURN_ID` in the environment — not even
@@ -32,8 +35,8 @@
32
35
  * Its answer is the exit code and, on exit `0`, an optional JSON object on
33
36
  * stdout. The exit codes:
34
37
  *
35
- * - `0` — carry on. On `user_prompt_submit`, plain (non-JSON) stdout becomes
36
- * context for the model.
38
+ * - `0` — carry on. On `user_prompt_submit`, `subagent_start` and
39
+ * `session_start`, plain (non-JSON) stdout becomes context for the model.
37
40
  * - `2` — **block**, read per event: before a tool (`pre_tool_use`) the call is
38
41
  * skipped and the model is told why, with the hook's stderr as the reason;
39
42
  * on `user_prompt_submit` the turn ends; on `turn_end` and `subagent_stop`
@@ -68,7 +71,9 @@
68
71
  * `permissionDecisionReason`, `updatedInput` and `additionalContext` for
69
72
  * `pre_tool_use`; `additionalContext` for `user_prompt_submit`,
70
73
  * `post_tool_use`, `post_tool_use_failure`, `turn_end`, `subagent_stop`,
71
- * `session_start`, `notification`; and for `permission_request` a `decision`
74
+ * `session_start` (context for the whole session), `notification`;
75
+ * `updatedToolOutput` for `post_tool_use` (the text the model reads instead
76
+ * of the tool's result); and for `permission_request` a `decision`
72
77
  * `{ behavior: "allow" }` or `{ behavior: "deny", message, interrupt }`.
73
78
  *
74
79
  * ## What a hook may decide, and what it may not
@@ -101,10 +106,12 @@
101
106
  import { spawn } from 'node:child_process';
102
107
  import { decodedCommands, decomposeCommandLine } from '../authorization/command-line.js';
103
108
  import { SKILL_TOOL_NAME_ALIASES, permissionPatternToRegExpSource, } from '../authorization/skill-grant.js';
109
+ import { withSessionChildMarker } from '../process/session-child.js';
104
110
  import { spawnHostShell } from '../tools/command-shell.js';
105
111
  import { RENAMED_PLUGIN_HOOK_EVENTS } from '../types/plugin/index.js';
106
112
  import { asPluginId } from '../utils/id.js';
107
113
  import { NOOP_LOGGER } from '../utils/log/create-logger.js';
114
+ import { boundHookReplacement } from './hook-context.js';
108
115
  import { postHookJson } from './shell-hook-http.js';
109
116
  export const SHELL_HOOK_EVENTS = [
110
117
  'user_prompt_submit',
@@ -131,6 +138,51 @@ export const DEFAULT_SHELL_HOOK_TIMEOUT_MS = 600_000;
131
138
  export const MAX_SHELL_HOOK_TIMEOUT_MS = 10 * 60_000;
132
139
  /** Captured output bound per stream. */
133
140
  const MAX_CAPTURE_BYTES = 64 * 1024;
141
+ /**
142
+ * How a skill writes each name in `SKILL_TOOL_NAME_ALIASES` (the alias table
143
+ * is keyed lower-case). A regular-expression matcher is also tested against
144
+ * these, so `^(Edit|Write)$` written for those names still fires.
145
+ */
146
+ const WRITTEN_TOOL_NAMES = Object.freeze({
147
+ read: 'Read',
148
+ write: 'Write',
149
+ edit: 'Edit',
150
+ multiedit: 'MultiEdit',
151
+ bash: 'Bash',
152
+ grep: 'Grep',
153
+ glob: 'Glob',
154
+ ls: 'LS',
155
+ webfetch: 'WebFetch',
156
+ websearch: 'WebSearch',
157
+ skill: 'Skill',
158
+ askuserquestion: 'AskUserQuestion',
159
+ task: 'Task',
160
+ agent: 'Agent',
161
+ lsp: 'LSP',
162
+ toolsearch: 'ToolSearch',
163
+ bashoutput: 'BashOutput',
164
+ taskoutput: 'TaskOutput',
165
+ killshell: 'KillShell',
166
+ killbash: 'KillBash',
167
+ taskstop: 'TaskStop',
168
+ taskcreate: 'TaskCreate',
169
+ taskupdate: 'TaskUpdate',
170
+ tasklist: 'TaskList',
171
+ });
172
+ /** The names a skill writes for the tool called `toolName` here. */
173
+ function writtenNamesFor(toolName) {
174
+ const lower = toolName.toLowerCase();
175
+ const names = [];
176
+ for (const [written, tool] of Object.entries(SKILL_TOOL_NAME_ALIASES)) {
177
+ if (tool !== lower)
178
+ continue;
179
+ names.push(written);
180
+ const spelled = WRITTEN_TOOL_NAMES[written];
181
+ if (spelled !== undefined)
182
+ names.push(spelled);
183
+ }
184
+ return names;
185
+ }
134
186
  /**
135
187
  * `matcher` against a name (a tool's, or a notification's type).
136
188
  *
@@ -138,22 +190,56 @@ const MAX_CAPTURE_BYTES = 64 * 1024;
138
190
  * matches exactly, each part optionally ending in `*` as a prefix (the
139
191
  * spelling this adapter has always taken). Anything else is a regular
140
192
  * expression, tested unanchored against the name; one that does not compile
141
- * matches nothing. Case-sensitive, because tool names are. A matcher on an
142
- * event that has no name (`turn_start`) matches nothing unless it is `*`.
193
+ * matches nothing. A matcher on an event that has no name (`turn_start`)
194
+ * matches nothing unless it is `*`.
195
+ *
196
+ * Names in a list compare without regard to case. With `tool: true` (the
197
+ * event's name is a tool: the tool events and `permission_request`) they also
198
+ * take the names skills write, the way an entry's `if` does: `Bash` matches
199
+ * the tool `bash`, `MultiEdit` matches `edit`, and a prefix such as `Web*`
200
+ * matches `web_fetch` by its written name too. Where the name is not a tool
201
+ * (a notification's type, an agent's name, a session's `source` or `reason`)
202
+ * no tool alias applies, since those words are not tool names; case is still
203
+ * ignored there, because a guard that silently never runs over a capital
204
+ * letter is worse than one that also runs for a name differing only in case.
205
+ *
206
+ * A list part ending in `.*` is also tried as a regular expression.
207
+ *
208
+ * A regular expression is tested, case-sensitively as written, against the
209
+ * real name and, with `tool: true`, against each name skills write for that
210
+ * tool, so one written for those names still fires. It never matches less
211
+ * than it did against the real name alone.
143
212
  */
144
- export function shellHookMatches(matcher, toolName) {
213
+ export function shellHookMatches(matcher, toolName, options = {}) {
145
214
  if (matcher === undefined || matcher.trim() === '' || matcher.trim() === '*')
146
215
  return true;
147
216
  if (toolName === undefined)
148
217
  return false;
218
+ const tool = options.tool === true;
149
219
  const parts = matcher.split(/[|,]/).map((part) => part.trim());
150
220
  if (parts.every((part) => /^[A-Za-z0-9_.:-]+\*?$/.test(part))) {
151
- return parts.some((candidate) => candidate.endsWith('*')
152
- ? toolName.startsWith(candidate.slice(0, -1))
153
- : toolName === candidate);
221
+ const subject = toolName.toLowerCase();
222
+ const listed = parts.some((candidate) => {
223
+ const prefix = candidate.endsWith('*');
224
+ const written = (prefix ? candidate.slice(0, -1) : candidate).toLowerCase();
225
+ const spellings = [written];
226
+ if (tool) {
227
+ const alias = SKILL_TOOL_NAME_ALIASES[written];
228
+ if (alias !== undefined)
229
+ spellings.push(alias);
230
+ }
231
+ return spellings.some((name) => (prefix ? subject.startsWith(name) : subject === name));
232
+ });
233
+ // `Bash.*` reads as the prefix `bash.` above, which no tool has. A part
234
+ // ending in `.*` is also a regular expression, so it is tried as one.
235
+ if (listed || !matcher.includes('.*'))
236
+ return listed;
154
237
  }
155
238
  try {
156
- return new RegExp(matcher).test(toolName);
239
+ const expression = new RegExp(matcher);
240
+ if (expression.test(toolName))
241
+ return true;
242
+ return tool && writtenNamesFor(toolName).some((name) => expression.test(name));
157
243
  }
158
244
  catch {
159
245
  return false;
@@ -166,7 +252,7 @@ export function shellHookMatches(matcher, toolName) {
166
252
  * there; for a tool with no command line the filter cannot be read, which
167
253
  * runs the hook.
168
254
  */
169
- export function shellHookIfMatches(filter, toolName, toolInput) {
255
+ export function shellHookIfMatches(filter, toolName, toolInput, pathFilter) {
170
256
  if (filter === undefined || filter.trim() === '')
171
257
  return true;
172
258
  if (toolName === undefined)
@@ -176,10 +262,18 @@ export function shellHookIfMatches(filter, toolName, toolInput) {
176
262
  return true;
177
263
  const written = (shape[1] ?? '').toLowerCase();
178
264
  const tool = SKILL_TOOL_NAME_ALIASES[written] ?? written;
265
+ const specifier = shape[2]?.trim();
266
+ const hasPattern = specifier !== undefined && specifier !== '' && specifier.replaceAll('*', '') !== '';
267
+ // A path pattern is the permission rules' own: the host that has them says
268
+ // whether this call's path is named. It decides for the tools it knows.
269
+ if (hasPattern && pathFilter !== undefined) {
270
+ const answer = pathFilter(tool, specifier, toolName, toolInput);
271
+ if (answer !== undefined)
272
+ return answer;
273
+ }
179
274
  if (tool !== toolName.toLowerCase())
180
275
  return false;
181
- const specifier = shape[2]?.trim();
182
- if (specifier === undefined || specifier === '' || specifier.replaceAll('*', '') === '')
276
+ if (!hasPattern)
183
277
  return true;
184
278
  if (tool !== 'bash')
185
279
  return true;
@@ -214,6 +308,7 @@ export function shellHookPayload(input) {
214
308
  ...(input.transcriptPath !== undefined ? { transcript_path: input.transcriptPath } : {}),
215
309
  ...(input.permissionMode !== undefined ? { permission_mode: input.permissionMode } : {}),
216
310
  ...(input.turnId !== undefined ? { turn_id: input.turnId } : {}),
311
+ ...(input.sessionSource !== undefined ? { source: input.sessionSource } : {}),
217
312
  ...(input.parentSessionId !== undefined ? { parent_session_id: input.parentSessionId } : {}),
218
313
  ...(input.parentTurnId !== undefined ? { parent_turn_id: input.parentTurnId } : {}),
219
314
  ...(input.agentType !== undefined
@@ -240,7 +335,11 @@ export function shellHookPayload(input) {
240
335
  })),
241
336
  }
242
337
  : {}),
243
- ...(input.reason !== undefined ? { reason: input.reason } : {}),
338
+ ...(input.reason !== undefined
339
+ ? { reason: input.reason }
340
+ : input.endReason !== undefined
341
+ ? { reason: input.endReason }
342
+ : {}),
244
343
  ...(input.event === 'turn_failure' && input.error !== undefined ? { error: input.error } : {}),
245
344
  ...(input.notification !== undefined
246
345
  ? {
@@ -266,6 +365,7 @@ export function runShellHook(entry, input, signal) {
266
365
  ...inherited,
267
366
  NAMZU_HOOK_EVENT: input.event,
268
367
  NAMZU_SESSION_ID: input.sessionId,
368
+ NAMZU_PROJECT_DIR: input.projectDir ?? input.cwd,
269
369
  ...(input.turnId !== undefined ? { NAMZU_TURN_ID: input.turnId } : {}),
270
370
  ...(input.toolName !== undefined ? { NAMZU_TOOL_NAME: input.toolName } : {}),
271
371
  ...(toolPath !== undefined ? { NAMZU_TOOL_PATH: toolPath } : {}),
@@ -282,7 +382,7 @@ export function runShellHook(entry, input, signal) {
282
382
  ? // The exec form: no shell reads `command` or any argument.
283
383
  spawn(entry.command, [...entry.args], {
284
384
  cwd: input.cwd,
285
- env,
385
+ env: withSessionChildMarker(env),
286
386
  stdio: ['pipe', 'pipe', 'pipe'],
287
387
  detached,
288
388
  })
@@ -311,16 +411,26 @@ export function runShellHook(entry, input, signal) {
311
411
  }
312
412
  let stdout = '';
313
413
  let stderr = '';
414
+ let stdoutTruncated = false;
314
415
  let timedOut = false;
315
416
  let settled = false;
316
- const capture = (current, chunk) => current.length >= MAX_CAPTURE_BYTES
317
- ? current
318
- : (current + chunk.toString('utf8')).slice(0, MAX_CAPTURE_BYTES);
417
+ const capture = (current, chunk) => {
418
+ if (current.length >= MAX_CAPTURE_BYTES)
419
+ return { text: current, cut: chunk.length > 0 };
420
+ const joined = current + chunk.toString('utf8');
421
+ return {
422
+ text: joined.slice(0, MAX_CAPTURE_BYTES),
423
+ cut: joined.length > MAX_CAPTURE_BYTES,
424
+ };
425
+ };
319
426
  child.stdout?.on('data', (chunk) => {
320
- stdout = capture(stdout, chunk);
427
+ const next = capture(stdout, chunk);
428
+ stdout = next.text;
429
+ if (next.cut)
430
+ stdoutTruncated = true;
321
431
  });
322
432
  child.stderr?.on('data', (chunk) => {
323
- stderr = capture(stderr, chunk);
433
+ stderr = capture(stderr, chunk).text;
324
434
  });
325
435
  const finish = (outcome) => {
326
436
  if (settled)
@@ -355,11 +465,18 @@ export function runShellHook(entry, input, signal) {
355
465
  timedOut,
356
466
  stdout,
357
467
  stderr,
468
+ ...(stdoutTruncated ? { stdoutTruncated } : {}),
358
469
  spawnError: error.message,
359
470
  });
360
471
  });
361
472
  child.on('close', (code) => {
362
- finish({ exitCode: code, timedOut, stdout, stderr });
473
+ finish({
474
+ exitCode: code,
475
+ timedOut,
476
+ stdout,
477
+ stderr,
478
+ ...(stdoutTruncated ? { stdoutTruncated } : {}),
479
+ });
363
480
  });
364
481
  child.on('exit', () => {
365
482
  // After a kill, a survivor that still holds the pipes must not hold
@@ -518,6 +635,12 @@ function readShellHookJson(event, stdout) {
518
635
  reading.updatedInput = specific.updatedInput;
519
636
  }
520
637
  }
638
+ if (event === 'post_tool_use' && specific.updatedToolOutput !== undefined) {
639
+ if (typeof specific.updatedToolOutput !== 'string') {
640
+ return { invalid: 'hookSpecificOutput: `updatedToolOutput` must be a string' };
641
+ }
642
+ reading.replacement = boundHookReplacement(specific.updatedToolOutput);
643
+ }
521
644
  if (event === 'permission_request') {
522
645
  const decision = specific.decision;
523
646
  if (!isRecord(decision)) {
@@ -552,9 +675,25 @@ export function shellHookVerdict(event, entry, outcome, log = NOOP_LOGGER) {
552
675
  };
553
676
  // A failure of the hook itself is "carry on" unless the entry asked for
554
677
  // it to count as a refusal, which is exit 2 with the failure as the reason.
555
- const failed = (reason) => entry.onFailure === 'block' && entry.type !== 'prompt' && !isAsyncShellHook(entry)
556
- ? shellHookVerdict(event, { ...entry, onFailure: 'continue' }, { exitCode: 2, timedOut: false, stdout: '', stderr: `hook failed: ${reason}` }, log)
557
- : { action: 'continue' };
678
+ const failed = (reason, shown = reason) => {
679
+ if (entry.onFailure !== 'block' || entry.type === 'prompt' || isAsyncShellHook(entry)) {
680
+ return { action: 'continue' };
681
+ }
682
+ // The call has run and a refusal cannot undo it, but the hook may have
683
+ // been the one thing keeping something out of the result (a redaction),
684
+ // so a failed guard withholds the result instead of passing it on. The
685
+ // kernel marks the record as it does for any replaced result.
686
+ if (event === 'post_tool_use' || event === 'post_tool_use_failure') {
687
+ const line = `A hook guarding this result failed, so the result is not shown (${shown}). The tool call itself has already run.`;
688
+ return {
689
+ action: 'replace',
690
+ output: line,
691
+ content: [{ type: 'text', text: line }],
692
+ disclose: true,
693
+ };
694
+ }
695
+ return shellHookVerdict(event, { ...entry, onFailure: 'continue' }, { exitCode: 2, timedOut: false, stdout: '', stderr: `hook failed: ${reason}` }, log);
696
+ };
558
697
  if (outcome.spawnError !== undefined) {
559
698
  log.warn('hook could not start', {
560
699
  ...attrs,
@@ -602,12 +741,17 @@ export function shellHookVerdict(event, entry, outcome, log = NOOP_LOGGER) {
602
741
  }
603
742
  // Exit 0.
604
743
  const text = outcome.stdout.trim();
605
- const reading = readShellHookJson(event, outcome.stdout);
744
+ const reading = outcome.stdoutTruncated === true && text.startsWith('{')
745
+ ? {
746
+ invalid: `answer is too large: the hook printed more than the ${MAX_CAPTURE_BYTES} characters of stdout that are read`,
747
+ }
748
+ : readShellHookJson(event, outcome.stdout);
606
749
  if (reading === undefined) {
607
750
  // On the prompt event, what the hook printed is context for the
608
751
  // model — the branch, the time, a ticket. Everywhere else stdout is
609
752
  // the operator's to read and the model's business not at all.
610
- if ((event === 'user_prompt_submit' || event === 'subagent_start') && text.length > 0)
753
+ if ((event === 'user_prompt_submit' || event === 'subagent_start' || event === 'session_start') &&
754
+ text.length > 0)
611
755
  return { action: 'annotate', text };
612
756
  return { action: 'continue' };
613
757
  }
@@ -616,7 +760,9 @@ export function shellHookVerdict(event, entry, outcome, log = NOOP_LOGGER) {
616
760
  ...attrs,
617
761
  'namzu.hook.problem': reading.invalid,
618
762
  });
619
- return failed(reading.invalid);
763
+ // The parser's own detail can quote a piece of the answer; the line the
764
+ // model reads keeps only the lead.
765
+ return failed(reading.invalid, reading.invalid.split(' (')[0]);
620
766
  }
621
767
  if (!reading.suppressOutput) {
622
768
  log.debug('hook output', {
@@ -667,11 +813,34 @@ export function shellHookVerdict(event, entry, outcome, log = NOOP_LOGGER) {
667
813
  const context = [reading.block, reading.context].filter(Boolean).join('\n');
668
814
  return context.length > 0 ? { action: 'annotate', text: context } : { action: 'continue' };
669
815
  }
816
+ case 'session_start': {
817
+ // Nothing here can be refused. What the hook said for the model becomes
818
+ // the session's context; a stop request and a warning still count.
819
+ return reading.context !== undefined
820
+ ? { action: 'annotate', text: reading.context }
821
+ : {
822
+ action: 'continue',
823
+ ...(reading.halt ? { halt: reading.halt } : {}),
824
+ ...(reading.notice ? { notice: reading.notice } : {}),
825
+ };
826
+ }
670
827
  case 'post_tool_use':
671
828
  case 'post_tool_use_failure': {
672
829
  // The call has run, so a "block" cannot undo it: its reason is what the
673
830
  // model is told about the result.
674
831
  const context = [reading.block, reading.context].filter(Boolean).join('\n');
832
+ // A replacement is the hook's text in place of the tool's: the record
833
+ // says so beside it (the kernel adds the mark).
834
+ if (reading.replacement !== undefined) {
835
+ return {
836
+ action: 'replace',
837
+ output: reading.replacement,
838
+ disclose: true,
839
+ ...(context.length > 0 ? { context } : {}),
840
+ ...(reading.halt ? { halt: reading.halt } : {}),
841
+ ...(reading.notice ? { notice: reading.notice } : {}),
842
+ };
843
+ }
675
844
  return {
676
845
  action: 'continue',
677
846
  ...(context.length > 0 ? { context } : {}),
@@ -817,8 +986,17 @@ function readJudgement(reply) {
817
986
  }
818
987
  }
819
988
  /** One entry as a hook definition the lifecycle manager accepts. */
820
- export function createShellHook(event, entry, options) {
989
+ export function createShellHook(event, configured, options) {
821
990
  const log = options.log ?? NOOP_LOGGER;
991
+ const eventDefault = options.defaultTimeoutMs?.[event];
992
+ const entry = configured.timeoutMs === undefined && eventDefault !== undefined
993
+ ? { ...configured, timeoutMs: eventDefault }
994
+ : configured;
995
+ const reportTimeout = (timedOut) => {
996
+ if (!timedOut)
997
+ return;
998
+ options.onTimeout?.({ event, hook: describeShellHook(entry), timeoutMs: hookTimeout(entry) });
999
+ };
822
1000
  // `once` is per definition, and a definition is per session: the manager
823
1001
  // is built for one session and its hooks are attached to it.
824
1002
  let spent = false;
@@ -831,14 +1009,23 @@ export function createShellHook(event, entry, options) {
831
1009
  if (spent)
832
1010
  return { action: 'continue' };
833
1011
  // A notification is matched on its type; every other event on the tool.
1012
+ const namesATool = !(event === 'notification' ||
1013
+ event === 'subagent_start' ||
1014
+ event === 'subagent_stop' ||
1015
+ event === 'session_start' ||
1016
+ event === 'session_end');
834
1017
  const subject = event === 'notification'
835
1018
  ? context.notification?.type
836
1019
  : event === 'subagent_start' || event === 'subagent_stop'
837
1020
  ? context.agentType
838
- : context.toolName;
839
- if (!shellHookMatches(entry.matcher, subject))
1021
+ : event === 'session_start'
1022
+ ? context.source
1023
+ : event === 'session_end'
1024
+ ? context.endReason
1025
+ : context.toolName;
1026
+ if (!shellHookMatches(entry.matcher, subject, { tool: namesATool }))
840
1027
  return { action: 'continue' };
841
- if (!shellHookIfMatches(entry.if, context.toolName, context.toolInput)) {
1028
+ if (!shellHookIfMatches(entry.if, context.toolName, context.toolInput, options.pathFilter)) {
842
1029
  return { action: 'continue' };
843
1030
  }
844
1031
  if (entry.once === true)
@@ -848,7 +1035,10 @@ export function createShellHook(event, entry, options) {
848
1035
  const hookInput = {
849
1036
  event,
850
1037
  cwd: options.cwd,
1038
+ ...(options.projectDir !== undefined ? { projectDir: options.projectDir } : {}),
851
1039
  sessionId: String(context.sessionId),
1040
+ ...(context.source !== undefined ? { sessionSource: context.source } : {}),
1041
+ ...(context.endReason !== undefined ? { endReason: context.endReason } : {}),
852
1042
  ...(context.turnId !== undefined ? { turnId: String(context.turnId) } : {}),
853
1043
  ...(context.parentSessionId !== undefined
854
1044
  ? { parentSessionId: String(context.parentSessionId) }
@@ -889,6 +1079,7 @@ export function createShellHook(event, entry, options) {
889
1079
  timeoutMs: hookTimeout(entry),
890
1080
  ...(context.signal !== undefined ? { signal: context.signal } : {}),
891
1081
  });
1082
+ reportTimeout(response.timedOut);
892
1083
  const ok = response.status !== null && response.status >= 200 && response.status < 300;
893
1084
  return shellHookVerdict(event, entry, {
894
1085
  exitCode: ok ? 0 : (response.status ?? null),
@@ -912,6 +1103,7 @@ export function createShellHook(event, entry, options) {
912
1103
  return { action: 'continue' };
913
1104
  }
914
1105
  const outcome = await runShellHook(entry, hookInput, context.signal);
1106
+ reportTimeout(outcome.timedOut);
915
1107
  return shellHookVerdict(event, entry, outcome, log);
916
1108
  },
917
1109
  };