@lucascouts/claude-agent-tui 0.5.1 → 0.6.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 (51) hide show
  1. package/dist/acp-agent.d.ts +187 -13
  2. package/dist/acp-agent.d.ts.map +1 -1
  3. package/dist/acp-agent.js +448 -60
  4. package/dist/agent-catalog.d.ts +96 -0
  5. package/dist/agent-catalog.d.ts.map +1 -0
  6. package/dist/agent-catalog.js +287 -0
  7. package/dist/claude-path.d.ts.map +1 -1
  8. package/dist/claude-path.js +6 -0
  9. package/dist/end-of-turn.d.ts +6 -0
  10. package/dist/end-of-turn.d.ts.map +1 -1
  11. package/dist/end-of-turn.js +8 -1
  12. package/dist/engine-lifecycle.d.ts +66 -1
  13. package/dist/engine-lifecycle.d.ts.map +1 -1
  14. package/dist/engine-lifecycle.js +43 -4
  15. package/dist/engine-pty.d.ts +70 -2
  16. package/dist/engine-pty.d.ts.map +1 -1
  17. package/dist/engine-pty.js +80 -6
  18. package/dist/gate/settings-writer.d.ts +34 -3
  19. package/dist/gate/settings-writer.d.ts.map +1 -1
  20. package/dist/gate/settings-writer.js +62 -7
  21. package/dist/image-input.d.ts +31 -0
  22. package/dist/image-input.d.ts.map +1 -0
  23. package/dist/image-input.js +79 -0
  24. package/dist/image-vision-smoke.d.ts +52 -0
  25. package/dist/image-vision-smoke.d.ts.map +1 -0
  26. package/dist/image-vision-smoke.js +111 -0
  27. package/dist/index.js +6 -0
  28. package/dist/mcp-config-writer.d.ts +61 -0
  29. package/dist/mcp-config-writer.d.ts.map +1 -0
  30. package/dist/mcp-config-writer.js +172 -0
  31. package/dist/model-catalog.d.ts +29 -2
  32. package/dist/model-catalog.d.ts.map +1 -1
  33. package/dist/model-catalog.js +50 -10
  34. package/dist/permissions/gate-wiring.d.ts +13 -1
  35. package/dist/permissions/gate-wiring.d.ts.map +1 -1
  36. package/dist/permissions/gate-wiring.js +158 -40
  37. package/dist/permissions/hook-server.d.ts +15 -0
  38. package/dist/permissions/hook-server.d.ts.map +1 -1
  39. package/dist/permissions/hook-server.js +30 -1
  40. package/dist/permissions/request-permission.d.ts +9 -0
  41. package/dist/permissions/request-permission.d.ts.map +1 -1
  42. package/dist/permissions/request-permission.js +20 -5
  43. package/dist/settings.d.ts.map +1 -1
  44. package/dist/settings.js +9 -0
  45. package/dist/tools.d.ts +10 -2
  46. package/dist/tools.d.ts.map +1 -1
  47. package/dist/tools.js +5 -1
  48. package/dist/usage.d.ts +3 -0
  49. package/dist/usage.d.ts.map +1 -1
  50. package/dist/usage.js +9 -5
  51. package/package.json +8 -8
@@ -30,7 +30,7 @@
30
30
  // OFFLINE: this module spawns NO claude and bills nothing; the http server binds 127.0.0.1 only.
31
31
  import * as os from "node:os";
32
32
  import * as path from "node:path";
33
- import { randomUUID } from "node:crypto";
33
+ import { randomBytes, randomUUID } from "node:crypto";
34
34
  import { findFreePort } from "../gate/port.js";
35
35
  import { injectHook, restore } from "../gate/settings-writer.js";
36
36
  import { startHookServer } from "./hook-server.js";
@@ -86,6 +86,13 @@ export const DEFAULT_CORRELATION_POLL_MS = 50;
86
86
  * fail-closed timeout deny. Tracked separately from the poll interval (the poll is 10-50ms; nudging on
87
87
  * every poll would hammer the pump) — a nudge fires only once ~250ms has elapsed since the last one. */
88
88
  export const DEFAULT_CORRELATION_RENUDGE_MS = 250;
89
+ /**
90
+ * FIX(watchdog-permission): while a permission dialog is pending the claude is BLOCKED and writes
91
+ * nothing to the JSONL, so the end-of-turn watchdog (120s of silence) would mistake a long human
92
+ * decision for a dead turn. Re-arm it every this-many ms via the bound `noteActivity` for as long as
93
+ * the dialog is open. Must stay well under TURN_STALL_WATCHDOG_MS (120_000).
94
+ */
95
+ export const DEFAULT_PERMISSION_HEARTBEAT_MS = 30_000;
89
96
  /** Default window for the native prompt to APPEAR after an allow decision (#52822 sweep, ms).
90
97
  * If no marker renders within it, allow-suppression held (the 2.1.161 case) — nothing to clear. */
91
98
  export const DEFAULT_PROMPT_APPEAR_MS = 1500;
@@ -112,6 +119,8 @@ class SessionGateImpl {
112
119
  constructor(opts) {
113
120
  this.opts = opts;
114
121
  this.port = 0;
122
+ /** Story 055 (R1.3) — set in {@link start} to a crypto-random per-session secret. */
123
+ this.token = "";
115
124
  this.settingsPath = "";
116
125
  this.correlator = new ToolUseCorrelator();
117
126
  /** Rolling tail of recent PTY output + absolute count of chars ever appended (probe offsets). */
@@ -141,14 +150,46 @@ class SessionGateImpl {
141
150
  this.permissionQueue = run.then(() => undefined, () => undefined);
142
151
  return run;
143
152
  }
153
+ /**
154
+ * FIX(watchdog-permission): start a heartbeat that re-arms the end-of-turn watchdog (via the bound
155
+ * `noteActivity`) every {@link DEFAULT_PERMISSION_HEARTBEAT_MS} while a permission dialog is open, so
156
+ * the human decision time is never counted as transcript silence. Self-reschedules through the
157
+ * injectable `schedule` (testable). Returns a stop fn; safe/no-op when `noteActivity` is unset.
158
+ */
159
+ startPermissionHeartbeat() {
160
+ const note = this.noteActivity;
161
+ if (!note)
162
+ return () => { };
163
+ let stopped = false;
164
+ const tick = () => {
165
+ if (stopped || this.torndown)
166
+ return;
167
+ try {
168
+ note();
169
+ }
170
+ catch {
171
+ // heartbeat is best-effort liveness; a failure never affects the decision
172
+ }
173
+ this.schedule(tick, DEFAULT_PERMISSION_HEARTBEAT_MS);
174
+ };
175
+ this.schedule(tick, DEFAULT_PERMISSION_HEARTBEAT_MS);
176
+ return () => {
177
+ stopped = true;
178
+ };
179
+ }
144
180
  /** Start the hook server, then write the scratch settings (server first, so the URL the settings
145
181
  * point at is live before claude can ever read them; settings BEFORE the spawn is the caller's
146
182
  * ordering contract — blocker c). */
147
183
  async start() {
148
184
  const findPort = this.opts.findPort ?? findFreePort;
149
185
  this.port = await findPort();
186
+ // Story 055 (R1.3): a per-session crypto-random secret bound into the hook URL (after the marker
187
+ // path). The hook-server rejects any PreToolUse POST that does not present it — the compensating
188
+ // control for the relaxed JSONL anti-forgery (decide() now seeds the correlator from the payload).
189
+ this.token = randomBytes(24).toString("hex");
150
190
  this.server = await startHookServer({
151
191
  port: this.port,
192
+ token: this.token,
152
193
  deciderTimeoutMs: this.opts.deciderTimeoutMs,
153
194
  onWarn: (m) => this.warn(m),
154
195
  onToolCall: (call) => this.decide(call),
@@ -159,6 +200,7 @@ class SessionGateImpl {
159
200
  this.backup = await injectHook({
160
201
  settingsPath: this.settingsPath,
161
202
  port: this.port,
203
+ token: this.token,
162
204
  timeout: this.opts.hookTimeoutSeconds,
163
205
  });
164
206
  }
@@ -169,10 +211,11 @@ class SessionGateImpl {
169
211
  throw err;
170
212
  }
171
213
  }
172
- bindSession(sessionId, nudge, resolveSubagentRelay) {
214
+ bindSession(sessionId, nudge, resolveSubagentRelay, noteActivity) {
173
215
  this.sessionId = sessionId;
174
216
  this.nudge = nudge;
175
217
  this.resolveSubagentRelay = resolveSubagentRelay;
218
+ this.noteActivity = noteActivity;
176
219
  }
177
220
  bindPty(pty) {
178
221
  this.pty = pty;
@@ -220,31 +263,45 @@ class SessionGateImpl {
220
263
  `was bound to an ACP session — denying (cannot raise session/request_permission).`);
221
264
  return "deny";
222
265
  }
266
+ // FIX(gate-deadlock): the claude flushes the `tool_use` JSONL line only AFTER the hook decision,
267
+ // so correlating against the JSONL deadlocks — the line never reaches the pump during the wait
268
+ // (proven live 2026-06-25: total=9 frozen for 5s, the id only registered post-deny). Seed the
269
+ // correlator from the hook payload, the AUTHORITATIVE tool_use.id source, so the gate decides on
270
+ // the payload (the JSONL stays best-effort enrichment). Mirrors the ACP original's permission
271
+ // callback, which decided directly on the SDK-supplied tool id without a transcript round-trip.
272
+ this.correlator.ensureRegistered(call.toolUseId);
223
273
  await this.waitForCorrelation(call.toolUseId);
224
- // === Story 054 (§9 subagent relay) — AFTER the correlation wait, BEFORE the ACP prompt. ========
225
- // The hook payload carries NO parent id (ForwardedToolCall has no parent_tool_use_id); the ONLY
226
- // source of a subagent inner tool's parent is the session's sidechainParentMap, read lazily via
227
- // the bound resolver. An undefined relay = a MAIN-CHAIN tool both fields stay undefined and the
228
- // requestPermission call below is byte-identical to today (U1). A KNOWN subagent inner tool relays
229
- // its dialog under the parent Task id Zed already rendered (R1/R2) — UNLESS it cannot be safely
230
- // relayed (orphan parent, or it never became a clean JSONL match within the wait window), in which
231
- // case we fail LOUD (R4): a VISIBLE deny through the gate's warn surface (→ this.logger.error),
232
- // naming the subagent + inner tool, and return "deny" WITHOUT raising a dialog against a bogus id.
233
- const relay = this.resolveSubagentRelay?.(call.toolUseId);
274
+ // === Story 054/055 (§9 subagent relay) — AFTER the correlation wait, BEFORE the ACP prompt. =====
275
+ // R2.2 the dialog LABEL is sourced from the payload's `agent_type` (carried by parsePayload,
276
+ // story 055/2.1): transcript-independent and known NOW, so a subagent tool is always attributed
277
+ // even when its parent Task id is not (yet) resolvable. A main-chain payload has no agent_type
278
+ // `subagentLabel` stays undefined and the requestPermission call below is byte-identical to today
279
+ // (U1: bare tool name, dialog under the inner id).
280
+ //
281
+ // R2.3 parent-Task GROUPING is BEST-EFFORT. The hook payload carries NO parent id; the only
282
+ // source is the session's sidechainParentMap, read lazily via the bound resolver and populated by
283
+ // the (transcript-lagging) pump. WHEN it resolves a non-null parent, the dialog attaches under that
284
+ // parent Task id Zed already rendered. For a subagent whose row has NOT landed yet, give the pump a
285
+ // brief re-nudged window to register it mid-wait (the join is pump-fed, like the inner correlation);
286
+ // on expiry we relay the LABELLED dialog under the INNER id. An orphan (parentId === null) or an
287
+ // unresolved subagent is therefore PROMPTED (labelled), NEVER silently denied — this REPLACES the
288
+ // story-054 orphan/uncorrelated visible deny. The only deny here is requestPermission's own
289
+ // fail-closed (transport error / cancelled / duplicate id), propagated unchanged.
290
+ let subagentLabel = call.agentType;
291
+ let relay = this.resolveSubagentRelay?.(call.toolUseId);
292
+ if (!relay && call.agentType !== undefined) {
293
+ // A subagent tool (agent_type present) whose parent row may still be in flight: best-effort wait.
294
+ relay = await this.waitForSubagentParent(call.toolUseId);
295
+ }
234
296
  let dialogToolCallId;
235
- let subagentLabel;
236
297
  if (relay) {
237
- // isCleanMatch PROBES (does not consume); requestPermission's correlator.decide still consumes
238
- // the inner id. The orphan branch short-circuits BEFORE the probe via `||`.
239
- if (relay.parentId === null || !this.correlator.isCleanMatch(call.toolUseId)) {
240
- this.warn(`[gate §9 subagent] FAIL CLOSED: subagent (${relay.subagentLabel}) tool "${call.toolName}" ` +
241
- `(tool_use ${call.toolUseId}) ${relay.parentId === null
242
- ? "is an orphan — no parent Task to attach the permission dialog to"
243
- : "never correlated in the JSONL within the wait window"} — denying (R4 visible deny).`);
244
- return "deny";
298
+ // Prefer the payload label; fall back to the resolver's best-effort label (a pre-055 caller / a
299
+ // subagent payload that somehow omitted agent_type).
300
+ subagentLabel = call.agentType ?? relay.subagentLabel;
301
+ // Group ONLY under a real, resolvable parent; an orphan (null) falls through to the inner-id relay.
302
+ if (relay.parentId !== null) {
303
+ dialogToolCallId = relay.parentId;
245
304
  }
246
- dialogToolCallId = relay.parentId;
247
- subagentLabel = relay.subagentLabel;
248
305
  }
249
306
  // === Story 054 (R5) — SERIALIZE only the raise+inject critical section. ========================
250
307
  // dialogToolCallId/subagentLabel were computed above in the CONCURRENT prelude, so each enqueued
@@ -253,24 +310,33 @@ class SessionGateImpl {
253
310
  // requestPermission + armAllowSweep one at a time — no native-prompt keystroke crossing on the
254
311
  // shared PTY. A single sequential main-chain tool is a no-op through the queue (U1).
255
312
  return this.enqueuePermission(async () => {
256
- const decision = await requestPermission({
257
- client: this.opts.client,
258
- sessionId,
259
- toolCall: {
260
- toolUseId: call.toolUseId,
261
- toolName: call.toolName,
262
- toolInput: call.toolInput,
263
- },
264
- correlator: this.correlator,
265
- onWarn: (m) => this.warn(m),
266
- dialogToolCallId,
267
- subagentLabel,
268
- });
269
- if (decision === "allow") {
270
- // Return the allow body FIRST (claude is blocked on this response); sweep out of band.
271
- this.armAllowSweep(call);
313
+ // FIX(watchdog-permission): the claude is blocked on this response with the JSONL silent, so
314
+ // re-arm the end-of-turn watchdog for as long as the dialog is open (a slow human decision is
315
+ // NOT a dead turn). Always cleared in `finally`, on success or throw.
316
+ const stopHeartbeat = this.startPermissionHeartbeat();
317
+ try {
318
+ const decision = await requestPermission({
319
+ client: this.opts.client,
320
+ sessionId,
321
+ toolCall: {
322
+ toolUseId: call.toolUseId,
323
+ toolName: call.toolName,
324
+ toolInput: call.toolInput,
325
+ },
326
+ correlator: this.correlator,
327
+ onWarn: (m) => this.warn(m),
328
+ dialogToolCallId,
329
+ subagentLabel,
330
+ });
331
+ if (decision === "allow") {
332
+ // Return the allow body FIRST (claude is blocked on this response); sweep out of band.
333
+ this.armAllowSweep(call);
334
+ }
335
+ return decision;
336
+ }
337
+ finally {
338
+ stopHeartbeat();
272
339
  }
273
- return decision;
274
340
  });
275
341
  }
276
342
  /** Bounded poll until the pump has registered `toolUseId` as a clean single JSONL match. On
@@ -318,6 +384,58 @@ class SessionGateImpl {
318
384
  this.schedule(poll, pollMs);
319
385
  });
320
386
  }
387
+ /**
388
+ * Story 055 (R2.3) — BEST-EFFORT parent grouping for a subagent inner tool. The `agent_id → parent
389
+ * tool_use.id` join lives only in the (transcript-lagging) pump-fed `sidechainParentMap`, so the
390
+ * parent may not be registered at decide()-time. Poll the lazy resolver, re-nudging the pump on the
391
+ * same {@link DEFAULT_CORRELATION_RENUDGE_MS} cadence so a sidechain row landing MID-WAIT is caught,
392
+ * and resolve with the relay AS SOON AS it appears. On expiry resolve `undefined` — the caller then
393
+ * relays the LABELLED dialog under the inner id (never a deny). Bounded by the same correlation
394
+ * window since the join is pump-fed exactly like the inner correlation; a torn-down gate resolves
395
+ * `undefined` immediately. Only ever called for a subagent payload (agent_type present), so a
396
+ * main-chain tool never incurs this wait.
397
+ */
398
+ waitForSubagentParent(innerToolUseId) {
399
+ const resolveNow = () => this.resolveSubagentRelay?.(innerToolUseId);
400
+ const immediate = resolveNow();
401
+ if (immediate)
402
+ return Promise.resolve(immediate);
403
+ const waitMs = this.opts.correlationWaitMs ?? DEFAULT_CORRELATION_WAIT_MS;
404
+ const pollMs = this.opts.correlationPollMs ?? DEFAULT_CORRELATION_POLL_MS;
405
+ const renudgeMs = this.opts.correlationRenudgeMs ?? DEFAULT_CORRELATION_RENUDGE_MS;
406
+ return new Promise((resolve) => {
407
+ let elapsed = 0;
408
+ let sinceNudge = 0;
409
+ const poll = () => {
410
+ if (this.torndown) {
411
+ resolve(undefined);
412
+ return;
413
+ }
414
+ const r = resolveNow();
415
+ if (r) {
416
+ resolve(r);
417
+ return;
418
+ }
419
+ elapsed += pollMs;
420
+ sinceNudge += pollMs;
421
+ if (sinceNudge >= renudgeMs) {
422
+ sinceNudge = 0;
423
+ try {
424
+ this.nudge?.();
425
+ }
426
+ catch {
427
+ // best-effort: a nudge failure only widens the grouping window; never rejects or decides
428
+ }
429
+ }
430
+ if (elapsed >= waitMs) {
431
+ resolve(undefined); // best-effort expiry — relay under the inner id (labelled), not a deny
432
+ return;
433
+ }
434
+ this.schedule(poll, pollMs);
435
+ };
436
+ this.schedule(poll, pollMs);
437
+ });
438
+ }
321
439
  /**
322
440
  * #52822 sweep (allow path, best-effort by design): wait a bounded window for the native TUI
323
441
  * prompt to APPEAR in the post-decision PTY output; if it never renders, allow-suppression held
@@ -10,6 +10,9 @@ export interface PreToolUsePayload {
10
10
  tool_name?: string;
11
11
  tool_input?: unknown;
12
12
  tool_use_id?: string;
13
+ /** Story 055 (R2.1) — present ONLY on a subagent-internal tool: the subagent's own id and type. */
14
+ agent_id?: string;
15
+ agent_type?: string;
13
16
  }
14
17
  /** The normalized tool call forwarded to the decider (camelCase, the shape request-permission uses). */
15
18
  export interface ForwardedToolCall {
@@ -18,6 +21,11 @@ export interface ForwardedToolCall {
18
21
  toolUseId: string;
19
22
  sessionId?: string;
20
23
  permissionMode?: string;
24
+ /** Story 055 (R2.1) — the subagent's own id (`agent_id`); undefined for a main-chain tool. */
25
+ agentId?: string;
26
+ /** Story 055 (R2.1/R2.2) — the subagent's type (`agent_type`); the dialog label source. Undefined
27
+ * for a main-chain tool. There is NO parent tool_use.id in the payload (grouping is best-effort). */
28
+ agentType?: string;
21
29
  }
22
30
  /** The decider the server forwards each tool call to; returns the enforced `'allow'`/`'deny'`. */
23
31
  export type ToolCallDecider = (call: ForwardedToolCall) => Promise<"allow" | "deny"> | "allow" | "deny";
@@ -46,6 +54,13 @@ export interface StartHookServerOptions {
46
54
  createHttpServer?: () => Server;
47
55
  /** Optional diagnostics sink for fail-closed events (defaults to no-op). */
48
56
  onWarn?: (message: string) => void;
57
+ /**
58
+ * Story 055 (R1.3) — per-session secret token. WHEN set, a POST whose request path is not exactly
59
+ * `${FORK_HOOK_MARKER_PATH}/<token>` FAILS CLOSED (deny) WITHOUT invoking the decider — the
60
+ * compensating control for the relaxed JSONL anti-forgery. WHEN undefined, no token is enforced
61
+ * (the pre-055 behavior, used by the unit tests that construct the server directly).
62
+ */
63
+ token?: string;
49
64
  }
50
65
  /** Default decider timeout (ms) — long enough for an async Zed decision relay, short of a hang. */
51
66
  export declare const DEFAULT_DECIDER_TIMEOUT_MS = 600000;
@@ -1 +1 @@
1
- {"version":3,"file":"hook-server.d.ts","sourceRoot":"","sources":["../../src/permissions/hook-server.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAIjG;sGACsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wGAAwG;AACxG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,iBAAiB,KACpB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;IAChC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAsBlD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAyBlE;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkFjF"}
1
+ {"version":3,"file":"hook-server.d.ts","sourceRoot":"","sources":["../../src/permissions/hook-server.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAMjG;sGACsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wGAAwG;AACxG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;0GACsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kGAAkG;AAClG,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,iBAAiB,KACpB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;IAChC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAsBlD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA6BlE;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8GjF"}
@@ -18,7 +18,9 @@
18
18
  // user's real config — that wiring is the engine-integration concern; this module is the standalone
19
19
  // server seam.
20
20
  import { createServer } from "node:http";
21
+ import { appendFileSync } from "node:fs";
21
22
  import { LOOPBACK_HOST } from "../gate/port.js";
23
+ import { FORK_HOOK_MARKER_PATH } from "../gate/settings-writer.js";
22
24
  import { allowDecision, denyDecision } from "./deny.js";
23
25
  /** Default decider timeout (ms) — long enough for an async Zed decision relay, short of a hang. */
24
26
  export const DEFAULT_DECIDER_TIMEOUT_MS = 600_000;
@@ -70,6 +72,10 @@ export function parsePayload(raw) {
70
72
  toolUseId,
71
73
  sessionId: typeof parsed.session_id === "string" ? parsed.session_id : undefined,
72
74
  permissionMode: typeof parsed.permission_mode === "string" ? parsed.permission_mode : undefined,
75
+ // R2.1 — carry the subagent attribution the §9 payload already ships (today they are dropped). A
76
+ // main-chain payload omits them → both stay undefined.
77
+ agentId: typeof parsed.agent_id === "string" ? parsed.agent_id : undefined,
78
+ agentType: typeof parsed.agent_type === "string" ? parsed.agent_type : undefined,
73
79
  };
74
80
  }
75
81
  /** Run the decider with a timeout; on timeout or throw, resolve `'deny'` (fail closed). */
@@ -110,7 +116,7 @@ async function decideWithTimeout(decider, call, timeoutMs, onWarn) {
110
116
  * fails fast rather than spawning claude with an ungated hook URL).
111
117
  */
112
118
  export function startHookServer(opts) {
113
- const { port, deciderTimeoutMs = DEFAULT_DECIDER_TIMEOUT_MS, createHttpServer = createServer, onWarn, } = opts;
119
+ const { port, deciderTimeoutMs = DEFAULT_DECIDER_TIMEOUT_MS, createHttpServer = createServer, onWarn, token, } = opts;
114
120
  let decider = opts.onToolCall;
115
121
  const server = createHttpServer();
116
122
  server.on("request", (req, res) => {
@@ -119,6 +125,29 @@ export function startHookServer(opts) {
119
125
  let call = null;
120
126
  try {
121
127
  const raw = await readBody(req);
128
+ // DIAGNOSTIC (env-gated, off by default): capture EVERY raw PreToolUse POST the fork's
129
+ // hook-server receives — including the subagent-attributed fields (agent_id/agent_type) that
130
+ // parsePayload drops — so a live run can verify whether subagent-internal tools POST at all.
131
+ if (process.env.FORK_HOOK_LOG_RAW) {
132
+ try {
133
+ appendFileSync(process.env.FORK_HOOK_LOG_RAW, raw + "\n");
134
+ }
135
+ catch {
136
+ // best-effort diagnostic — never let logging fail the request
137
+ }
138
+ }
139
+ // R1.3 (story 055): validate the per-session token from the request path BEFORE the decider.
140
+ // A POST that does not present `${FORK_HOOK_MARKER_PATH}/<token>` is denied without ever
141
+ // reaching the decider — the forge-resistance that compensates the payload-seeded correlation.
142
+ if (token !== undefined) {
143
+ const urlPath = (req.url ?? "").split("?")[0];
144
+ if (urlPath !== `${FORK_HOOK_MARKER_PATH}/${token}`) {
145
+ onWarn?.(`[gate §9] FAIL CLOSED: PreToolUse POST presented a missing/incorrect per-session token ` +
146
+ `(url path "${urlPath}") — denying WITHOUT invoking the decider.`);
147
+ writeDecision(res, denyDecision({ toolName: "(unauthorized)", toolUseId: "(unauthorized)" }));
148
+ return;
149
+ }
150
+ }
122
151
  call = parsePayload(raw);
123
152
  if (call === null) {
124
153
  onWarn?.(`[gate §9] FAIL CLOSED: unparseable or identity-less PreToolUse payload — denying.`);
@@ -70,6 +70,15 @@ export declare class ToolUseCorrelator {
70
70
  * observation marks it duplicate. Returns true on the FIRST registration, false on a duplicate.
71
71
  */
72
72
  register(toolUseId: string): boolean;
73
+ /**
74
+ * FIX(gate-deadlock): seed the correlator from the AUTHORITATIVE PreToolUse hook payload when the
75
+ * JSONL has not observed the id yet. The claude buffers the `tool_use` line and only flushes it to
76
+ * the transcript AFTER the hook decision, so waiting on the JSONL deadlocks (the line never arrives
77
+ * during the wait). Idempotent and safe: marks count=1 ONLY when unseen (0) — it never pushes an
78
+ * already-clean (1) id to duplicate (2), nor touches a genuine duplicate (>1). The pump's later
79
+ * observation of the same id (post-decision) is harmless: the id is already consumed by then.
80
+ */
81
+ ensureRegistered(toolUseId: string): void;
73
82
  /** True iff `toolUseId` was observed EXACTLY once in the JSONL and has not yet been consumed. */
74
83
  isCleanMatch(toolUseId: string): boolean;
75
84
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"request-permission.d.ts","sourceRoot":"","sources":["../../src/permissions/request-permission.ts"],"names":[],"mappings":"AAoBA,yEAAyE;AACzE,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,eAAe,CAAC;AAEnG,oGAAoG;AACpG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,oBAAoB,CAAC;CAC5B;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,kEAAkE;AAClE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,MAAM,wBAAwB,GAChC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,8DAA8D;AAC9D,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtF;AAED,iGAAiG;AACjG,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wFAAwF;AACxF,eAAO,MAAM,eAAe,UAAU,CAAC;AACvC,eAAO,MAAM,cAAc,SAAS,CAAC;AAErC,gFAAgF;AAChF,wBAAgB,sBAAsB,IAAI,gBAAgB,EAAE,CAK3D;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC5B,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,qGAAqG;IACrG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAE9C;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMpC,iGAAiG;IACjG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC;;;;;OAKG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAS9C;AAED,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sFAAsF;IACtF,UAAU,EAAE,iBAAiB,CAAC;IAC9B,sGAAsG;IACtG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;oGACgG;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAuDjG"}
1
+ {"version":3,"file":"request-permission.d.ts","sourceRoot":"","sources":["../../src/permissions/request-permission.ts"],"names":[],"mappings":"AAoBA,yEAAyE;AACzE,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,eAAe,CAAC;AAEnG,oGAAoG;AACpG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,oBAAoB,CAAC;CAC5B;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,kEAAkE;AAClE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,MAAM,wBAAwB,GAChC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,8DAA8D;AAC9D,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtF;AAED,iGAAiG;AACjG,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wFAAwF;AACxF,eAAO,MAAM,eAAe,UAAU,CAAC;AACvC,eAAO,MAAM,cAAc,SAAS,CAAC;AAErC,gFAAgF;AAChF,wBAAgB,sBAAsB,IAAI,gBAAgB,EAAE,CAK3D;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC5B,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,qGAAqG;IACrG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAE9C;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMpC;;;;;;;OAOG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzC,iGAAiG;IACjG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC;;;;;OAKG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAS9C;AAED,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,sFAAsF;IACtF,UAAU,EAAE,iBAAiB,CAAC;IAC9B,sGAAsG;IACtG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;oGACgG;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CA0DjG"}
@@ -52,6 +52,18 @@ export class ToolUseCorrelator {
52
52
  this.seen.set(toolUseId, count + 1);
53
53
  return count === 0;
54
54
  }
55
+ /**
56
+ * FIX(gate-deadlock): seed the correlator from the AUTHORITATIVE PreToolUse hook payload when the
57
+ * JSONL has not observed the id yet. The claude buffers the `tool_use` line and only flushes it to
58
+ * the transcript AFTER the hook decision, so waiting on the JSONL deadlocks (the line never arrives
59
+ * during the wait). Idempotent and safe: marks count=1 ONLY when unseen (0) — it never pushes an
60
+ * already-clean (1) id to duplicate (2), nor touches a genuine duplicate (>1). The pump's later
61
+ * observation of the same id (post-decision) is harmless: the id is already consumed by then.
62
+ */
63
+ ensureRegistered(toolUseId) {
64
+ if ((this.seen.get(toolUseId) ?? 0) === 0)
65
+ this.seen.set(toolUseId, 1);
66
+ }
55
67
  /** True iff `toolUseId` was observed EXACTLY once in the JSONL and has not yet been consumed. */
56
68
  isCleanMatch(toolUseId) {
57
69
  return this.seen.get(toolUseId) === 1 && !this.consumed.has(toolUseId);
@@ -96,12 +108,15 @@ export async function requestPermission(opts) {
96
108
  result = await client.requestPermission({
97
109
  sessionId,
98
110
  toolCall: {
99
- // Story 054: a subagent inner tool relays under the PARENT Task id Zed already rendered
100
- // (dialogToolCallId), naming the inner tool + subagent in the title. A main-chain call (no
101
- // dialogToolCallId) is byte-identical to today: toolCallId = inner id, title = bare tool name.
111
+ // Story 054/055: a subagent inner tool relays under the PARENT Task id Zed already rendered
112
+ // (dialogToolCallId) WHEN the parent is resolvable; otherwise it keeps the inner id. Story 055
113
+ // (R2.2) DECOUPLES the attributed title from dialogToolCallId: the title is labelled whenever
114
+ // `subagentLabel` is set (sourced from the payload's agent_type), so a fast subagent whose
115
+ // parent has not yet been resolved still renders `<tool> · from the <agent_type> agent` under
116
+ // the inner id. A main-chain call (no subagentLabel) stays byte-identical: bare tool name.
102
117
  toolCallId: dialogToolCallId ?? toolCall.toolUseId,
103
- title: dialogToolCallId !== undefined
104
- ? `${toolCall.toolName} · from the ${subagentLabel ?? "subagent"} agent`
118
+ title: subagentLabel !== undefined
119
+ ? `${toolCall.toolName} · from the ${subagentLabel} agent`
105
120
  : toolCall.toolName,
106
121
  rawInput: toolCall.toolInput,
107
122
  },
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAU7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAU7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAgCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAuB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
package/dist/settings.js CHANGED
@@ -106,6 +106,13 @@ export class SettingsManager {
106
106
  this.handleSettingsChange();
107
107
  }
108
108
  });
109
+ // Story 059: a best-effort settings watcher must NOT keep the Node event loop alive on its
110
+ // own. A settingsManager that escapes dispose() (an initialize/re-spawn race — see the
111
+ // pre-existing-leak note in acp-agent.ts createSession) otherwise leaves a live fs.watch
112
+ // handle that hangs `node:test` on process exit (the live ACP process is held open by its
113
+ // stdio, never by this watcher). unref() detaches it from the loop's keep-alive set while
114
+ // leaving the subscription fully functional.
115
+ watcher.unref?.();
109
116
  watcher.on("error", (error) => {
110
117
  this.logger.error(`Settings watcher error for ${filePath}:`, error);
111
118
  });
@@ -139,6 +146,8 @@ export class SettingsManager {
139
146
  this.logger.error("Failed to reload settings:", error);
140
147
  }
141
148
  }, 100);
149
+ // Story 059: same rationale as the watcher unref — a pending debounce timer must not block exit.
150
+ this.debounceTimer?.unref?.();
142
151
  }
143
152
  /**
144
153
  * Returns the current merged settings
package/dist/tools.d.ts CHANGED
@@ -50,8 +50,16 @@ export declare function toDisplayPath(filePath: string, cwd?: string): string;
50
50
  export declare function toolCallIdFor(toolUseId: string, sessionId: string, options?: {
51
51
  namespaced?: boolean;
52
52
  }): string;
53
- export declare function toolInfoFromToolUse(toolUse: any, supportsTerminalOutput?: boolean, cwd?: string): ToolInfo;
54
- export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined, supportsTerminalOutput?: boolean): ToolUpdate;
53
+ /** Minimal shape of a raw JSONL `tool_use` block consumed by the tool mappers. */
54
+ interface RawToolUse {
55
+ name: string;
56
+ id: string;
57
+ input?: unknown;
58
+ }
59
+ export declare function toolInfoFromToolUse(toolUse: RawToolUse, supportsTerminalOutput?: boolean, cwd?: string): ToolInfo;
60
+ export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: {
61
+ name?: string;
62
+ } | undefined, supportsTerminalOutput?: boolean): ToolUpdate;
55
63
  export type ClaudePlanEntry = {
56
64
  content: string;
57
65
  status: "pending" | "in_progress" | "completed";
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -325,7 +325,11 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
325
325
  if ("is_error" in toolResult &&
326
326
  toolResult.is_error &&
327
327
  toolResult.content &&
328
- toolResult.content.length > 0) {
328
+ toolResult.content.length > 0 &&
329
+ // Story 056 (#776): a FAILED Bash with a negotiated terminal must still render as terminal
330
+ // output (terminal_output + terminal_exit exit_code 1 via `case "Bash"`), not markdown — so it
331
+ // is excluded from this error-only early-return. Every other errored result is unchanged.
332
+ !(toolUse?.name === "Bash" && supportsTerminalOutput)) {
329
333
  // Only return errors
330
334
  return toAcpContentUpdate(toolResult.content, true);
331
335
  }
package/dist/usage.d.ts CHANGED
@@ -32,6 +32,9 @@ export interface UsageOptions {
32
32
  * • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
33
33
  * the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
34
34
  * so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
35
+ * Story 059 EVIDENCE (2026-06-28): an audit of 5169 local transcripts found 0 carrying
36
+ * `total_cost_usd` as a JSON key — it lives only in the SDK `result` envelope (`claude -p`), never
37
+ * in the interactive PTY+tail JSONL mapped here (the 68 substring hits were all conversation text).
35
38
  */
36
39
  export declare function toUsageUpdate(message: UsageCarrier, options?: UsageOptions): UsageUpdateNotification | undefined;
37
40
  export interface UsageFlagOptions extends UsageOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,iFAAiF;AACjF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,aAAa,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAEhG,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,YAAiB,GACzB,uBAAuB,GAAG,SAAS,CAUrC;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,wFAAwF;IACxF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,gBAAqB,GAC7B,uBAAuB,EAAE,CAI3B"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,iFAAiF;AACjF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,aAAa,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAEhG,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,YAAiB,GACzB,uBAAuB,GAAG,SAAS,CAUrC;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,wFAAwF;IACxF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,gBAAqB,GAC7B,uBAAuB,EAAE,CAI3B"}
package/dist/usage.js CHANGED
@@ -1,9 +1,10 @@
1
- // Story 025 / Group 3 (R3.1, R3.2) — the optional, default-OFF UNSTABLE `usage_update`
2
- // mapping. `usage_update` exists ONLY in the @agentclientprotocol/sdk 0.22.1 schema and is
1
+ // Story 025 / Group 3 (R3.1, R3.2) — the optional UNSTABLE `usage_update` mapping.
2
+ // `usage_update` exists ONLY in the @agentclientprotocol/sdk 0.22.1 schema and is
3
3
  // best-effort per §1; it maps the JSONL message-event `usage.{input,output}_tokens` into the
4
- // SDK's { sessionUpdate:"usage_update", size, used } shape. The feature defaults OFF and stays
5
- // OFF in production until the live-Zed acceptance probe (Task 3.3, R8) confirms the user's Zed
6
- // tolerates it. A rejected emission is suppressed by the pump's per-session latch (Task 5.1).
4
+ // SDK's { sessionUpdate:"usage_update", size, used } shape. Story 042 FLIPPED the default to ON
5
+ // (story 039 confirmed the user's Zed ACCEPTS+RENDERS it by code); only the explicit opt-out
6
+ // USAGE_UPDATE=0/false disables it (see usage-env.ts). A rejected emission is still suppressed by
7
+ // the pump's per-session reject latch (Task 5.1, R8).
7
8
  //
8
9
  // Kept a self-contained module (cf. diff-source.ts) imported by the acp-agent pump; it does
9
10
  // NOT go through lib.ts (the frozen upstream export surface).
@@ -21,6 +22,9 @@
21
22
  * • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
22
23
  * the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
23
24
  * so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
25
+ * Story 059 EVIDENCE (2026-06-28): an audit of 5169 local transcripts found 0 carrying
26
+ * `total_cost_usd` as a JSON key — it lives only in the SDK `result` envelope (`claude -p`), never
27
+ * in the interactive PTY+tail JSONL mapped here (the 68 substring hits were all conversation text).
24
28
  */
25
29
  export function toUsageUpdate(message, options = {}) {
26
30
  const input = message.usage?.input_tokens;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.1",
6
+ "version": "0.6.0",
7
7
  "description": "Run the official Claude Code TUI in Zed's agent panel on your Claude Pro/Max subscription — an ACP-over-PTY bridge created for Anthropic's billing split (paused June 15; see README).",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",
@@ -60,20 +60,20 @@
60
60
  "author": "lucascouts",
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
- "@agentclientprotocol/sdk": "0.28.1",
64
- "@anthropic-ai/claude-agent-sdk": "0.3.183",
63
+ "@agentclientprotocol/sdk": "1.0.0",
64
+ "@anthropic-ai/claude-agent-sdk": "0.3.191",
65
65
  "node-pty": "1.1.0",
66
66
  "zod": "^3.25.0 || ^4.0.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@anthropic-ai/sdk": "0.105.0",
69
+ "@anthropic-ai/sdk": "0.106.0",
70
70
  "@eslint/js": "10.0.1",
71
- "@types/node": "26.0.0",
72
- "@typescript-eslint/eslint-plugin": "8.61.1",
73
- "@typescript-eslint/parser": "8.61.1",
71
+ "@types/node": "26.0.1",
72
+ "@typescript-eslint/eslint-plugin": "8.62.0",
73
+ "@typescript-eslint/parser": "8.62.0",
74
74
  "eslint": "10.5.0",
75
75
  "eslint-config-prettier": "10.1.8",
76
- "globals": "17.6.0",
76
+ "globals": "17.7.0",
77
77
  "prettier": "3.8.4",
78
78
  "ts-node": "10.9.2",
79
79
  "typescript": "6.0.3"