@humwick/runner 0.130.4 → 0.130.6

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 (217) hide show
  1. package/LICENSE +661 -167
  2. package/agent-bin/humwick +2 -2
  3. package/agent-bin/humwick-browser +4 -0
  4. package/hooks/fff-search-redirect/fff-search-redirect.cjs +1 -1
  5. package/hooks/web-tools-redirect/web-tools-redirect.cjs +2 -2
  6. package/package.json +8 -11
  7. package/plugins/browser/skills/playwright-cli/SKILL.md +8 -6
  8. package/plugins/claude/.claude-plugin/plugin.json +3 -3
  9. package/plugins/claude/claude-statusline-probe.cjs +26 -16
  10. package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +47 -17
  11. package/plugins/claude/hooks/gate-run-guard.sh +3 -3
  12. package/plugins/claude/hooks/gate-run-guard.ts +5 -5
  13. package/plugins/claude/hooks/kill-guard.provisioned.mjs +1 -1
  14. package/plugins/claude/hooks/kill-guard.sh +4 -4
  15. package/plugins/claude/hooks/kill-guard.ts +2 -2
  16. package/plugins/claude/hooks/permission-request.sh +1 -1
  17. package/plugins/claude/hooks/post-compact.sh +1 -1
  18. package/plugins/claude/hooks/post-tool-use.sh +3 -3
  19. package/plugins/claude/hooks/pre-compact.sh +2 -2
  20. package/plugins/claude/hooks/read-only-bash-guard.sh +4 -4
  21. package/plugins/claude/hooks/relay-status.sh +32 -32
  22. package/plugins/claude/hooks/session-end.sh +2 -2
  23. package/plugins/claude/hooks/session-start.sh +3 -3
  24. package/plugins/claude/hooks/shell-trust.ts +1 -1
  25. package/plugins/claude/hooks/stop-failure.sh +2 -2
  26. package/plugins/claude/hooks/stop.sh +6 -6
  27. package/plugins/claude/hooks/subagent-skill-policy.cjs +8 -6
  28. package/plugins/claude/hooks/subagent-stop.sh +3 -3
  29. package/plugins/claude/hooks/user-prompt-submit.sh +1 -1
  30. package/plugins/claude/hooks/wait-loop-guard.ts +3 -3
  31. package/plugins/claude/monitors/monitors.json +2 -2
  32. package/plugins/claude/monitors/relay-monitor.provisioned.mjs +96 -62
  33. package/plugins/claude/monitors/relay-monitor.ts +5 -5
  34. package/plugins/claude/skills/label/SKILL.md +3 -3
  35. package/plugins/claude/skills/message/SKILL.md +5 -5
  36. package/plugins/claude/skills/react/SKILL.md +1 -1
  37. package/plugins/claude/skills/read-message/SKILL.md +4 -4
  38. package/plugins/claude/skills/reply/SKILL.md +5 -5
  39. package/plugins/claude/skills/send-claimable/SKILL.md +5 -5
  40. package/plugins/claude/skills/slice-first/SKILL.md +5 -3
  41. package/plugins/claude/skills/slice-first/references/plan-recipe.md +30 -29
  42. package/plugins/claude/skills/status/SKILL.md +3 -3
  43. package/plugins/claude/skills/tags/SKILL.md +5 -5
  44. package/plugins/claude/skills/task/SKILL.md +10 -10
  45. package/plugins/claude-output-style/.claude-plugin/plugin.json +1 -1
  46. package/plugins/claude-output-style/output-styles/{agent-relay.md → humwick.md} +1 -1
  47. package/plugins/codex/skills/guide/SKILL.md +3 -3
  48. package/plugins/codex/skills/label/SKILL.md +3 -3
  49. package/plugins/codex/skills/message/SKILL.md +5 -5
  50. package/plugins/codex/skills/react/SKILL.md +1 -1
  51. package/plugins/codex/skills/read-message/SKILL.md +4 -4
  52. package/plugins/codex/skills/reply/SKILL.md +5 -5
  53. package/plugins/codex/skills/send-claimable/SKILL.md +5 -5
  54. package/plugins/codex/skills/slice-first/SKILL.md +5 -3
  55. package/plugins/codex/skills/slice-first/references/plan-recipe.md +30 -29
  56. package/plugins/codex/skills/status/SKILL.md +3 -3
  57. package/plugins/codex/skills/tags/SKILL.md +5 -5
  58. package/plugins/codex/skills/task/SKILL.md +11 -10
  59. package/src/adapter.ts +95 -92
  60. package/src/adapters/claude-api.ts +8 -11
  61. package/src/adapters/claude-context-command.ts +1 -1
  62. package/src/adapters/claude-credentials.ts +7 -3
  63. package/src/adapters/claude-delivery-lanes.ts +2 -2
  64. package/src/adapters/claude-delivery.ts +16 -16
  65. package/src/adapters/claude-effort.ts +2 -2
  66. package/src/adapters/claude-exit.ts +2 -2
  67. package/src/adapters/claude-headless-mirror.ts +18 -2
  68. package/src/adapters/claude-headless-refusal.ts +3 -3
  69. package/src/adapters/claude-headless.ts +11 -10
  70. package/src/adapters/claude-launch-exit.ts +1 -1
  71. package/src/adapters/claude-monitor-delivery-plan.ts +36 -16
  72. package/src/adapters/claude-permission.ts +5 -5
  73. package/src/adapters/claude-prompt-gates.ts +9 -9
  74. package/src/adapters/claude-prompt-hook.ts +2 -2
  75. package/src/adapters/claude-session-capture.ts +6 -6
  76. package/src/adapters/claude-session-probe.ts +25 -25
  77. package/src/adapters/claude-shutdown.ts +11 -11
  78. package/src/adapters/claude-status-detectors.ts +31 -31
  79. package/src/adapters/claude-subagents.ts +7 -7
  80. package/src/adapters/claude-submit-confirmation.ts +6 -6
  81. package/src/adapters/claude-tmux.ts +43 -43
  82. package/src/adapters/claude-token-usage-capture.ts +5 -5
  83. package/src/adapters/claude-tool-policy.ts +7 -7
  84. package/src/adapters/claude-transcript.ts +24 -24
  85. package/src/adapters/claude.ts +100 -100
  86. package/src/adapters/codex-agent-message-capture.ts +26 -63
  87. package/src/adapters/codex-app-server.ts +1 -1
  88. package/src/adapters/codex-client.ts +9 -9
  89. package/src/adapters/codex-effective-config.ts +1 -1
  90. package/src/adapters/codex-launch-config.ts +5 -5
  91. package/src/adapters/codex-rollout-context.ts +2 -2
  92. package/src/adapters/codex-skill-discovery.ts +1 -1
  93. package/src/adapters/codex-token-usage-capture.ts +16 -16
  94. package/src/adapters/codex-tool-output.ts +4 -4
  95. package/src/adapters/codex-version.ts +3 -3
  96. package/src/adapters/codex.ts +64 -64
  97. package/src/adapters/opencode-api.ts +1 -1
  98. package/src/adapters/opencode-context.ts +20 -22
  99. package/src/adapters/opencode-model-registry.ts +7 -7
  100. package/src/adapters/opencode-provider.ts +8 -8
  101. package/src/adapters/opencode-provisioning.ts +30 -0
  102. package/src/adapters/opencode-server-retry.ts +1 -1
  103. package/src/adapters/opencode-soft-steer.ts +5 -5
  104. package/src/adapters/opencode-spawn.ts +18 -14
  105. package/src/adapters/opencode-token-usage-capture.ts +12 -12
  106. package/src/adapters/opencode-tool-events.ts +15 -15
  107. package/src/adapters/opencode.ts +232 -232
  108. package/src/adapters/project-shell.ts +11 -11
  109. package/src/adapters/service-access.ts +6 -6
  110. package/src/adapters/tmux-offload.ts +9 -9
  111. package/src/adapters/tool-summary.ts +5 -5
  112. package/src/agent-redact.ts +1 -1
  113. package/src/agent-runner/commands/dispatch.ts +10 -10
  114. package/src/agent-runner/commands/lifecycle.ts +2 -2
  115. package/src/agent-runner/commands/prompt.ts +19 -19
  116. package/src/agent-runner/credentials.ts +5 -5
  117. package/src/agent-runner/inbox.ts +1 -1
  118. package/src/agent-runner/lifecycle-milestones.ts +3 -3
  119. package/src/agent-runner/lifecycle-phase.ts +2 -2
  120. package/src/agent-runner/liveness-exchange.ts +4 -10
  121. package/src/agent-runner/registration.ts +8 -8
  122. package/src/agent-runner/runner-context.ts +9 -14
  123. package/src/agent-runner/session-mirror.ts +12 -12
  124. package/src/agent-runner/task-claims.ts +2 -2
  125. package/src/agent-runner/token-usage-coverage.ts +1 -1
  126. package/src/agent-runner/turn-report.ts +12 -12
  127. package/src/api-budget-accounting.ts +3 -3
  128. package/src/area-memory.ts +6 -6
  129. package/src/artifact-path-materialize.ts +1 -1
  130. package/src/async-guard.ts +5 -5
  131. package/src/attachment-cache.ts +17 -22
  132. package/src/background-script-reconcile.ts +6 -6
  133. package/src/bounded-log.ts +1 -1
  134. package/src/busy-reconciler.ts +22 -22
  135. package/src/claim-tracker.ts +3 -3
  136. package/src/claude-settings.ts +8 -8
  137. package/src/claude-system-prompt.ts +1 -1
  138. package/src/claude-workspace-trust.ts +1 -1
  139. package/src/config.ts +62 -61
  140. package/src/context-boundary.ts +5 -5
  141. package/src/context-command.ts +3 -3
  142. package/src/control-server.ts +32 -32
  143. package/src/delivery-result.ts +2 -2
  144. package/src/effort-command.ts +2 -2
  145. package/src/empty-turn.ts +22 -14
  146. package/src/free-port.ts +1 -1
  147. package/src/gate-run-config.ts +3 -3
  148. package/src/host-asset-plan.ts +24 -17
  149. package/src/host-claude-config-worker.ts +2 -2
  150. package/src/host-sources.ts +2 -2
  151. package/src/index.ts +16 -16
  152. package/src/launch-assembly.ts +94 -96
  153. package/src/liveness.ts +15 -15
  154. package/src/logger.ts +3 -3
  155. package/src/mcp-outbox.ts +3 -3
  156. package/src/mcp-preflight.ts +2 -2
  157. package/src/mcp-source-selection.ts +2 -2
  158. package/src/mcp-tool-deferral.ts +1 -1
  159. package/src/mcp-vault-env.ts +2 -2
  160. package/src/message-body-config/config.ts +4 -4
  161. package/src/mid-turn-delivery.ts +2 -2
  162. package/src/native-memory-lever.ts +8 -8
  163. package/src/outbox.ts +15 -15
  164. package/src/pending-permission-store.ts +1 -1
  165. package/src/profile-home.ts +19 -19
  166. package/src/profile-projection.ts +26 -24
  167. package/src/project-instructions.ts +5 -5
  168. package/src/provider-auth-warning.ts +17 -0
  169. package/src/provider-double.ts +1 -1
  170. package/src/provider-hold.ts +3 -3
  171. package/src/provider-home-marker.ts +1 -1
  172. package/src/provider-home-tombstone.ts +1 -1
  173. package/src/provider-input-queue.ts +2 -2
  174. package/src/provider-integration.ts +15 -13
  175. package/src/provider-setup.ts +15 -3
  176. package/src/providers.ts +1 -1
  177. package/src/provisioning.ts +37 -40
  178. package/src/question-decision.ts +2 -2
  179. package/src/quota.ts +0 -6
  180. package/src/rate-limit.ts +24 -13
  181. package/src/refused-prompt-store.ts +2 -2
  182. package/src/relay-injection-events.ts +2 -2
  183. package/src/relay-instructions.ts +5 -5
  184. package/src/relay-mcp-proxy.long-turn-e2e.ts +13 -13
  185. package/src/relay-mcp-proxy.ts +57 -58
  186. package/src/relay-mcp.ts +55 -69
  187. package/src/reply-obligation-cache.ts +9 -9
  188. package/src/response-capture-report.ts +9 -9
  189. package/src/response-routing.ts +1 -1
  190. package/src/runner-core.ts +209 -222
  191. package/src/runner-exit.ts +1 -1
  192. package/src/runner-helpers.ts +10 -10
  193. package/src/runner-insights.ts +4 -2
  194. package/src/runner-mcp-proxy.ts +3 -3
  195. package/src/runner-spawn-config.ts +3 -3
  196. package/src/runtime-token-file.ts +1 -1
  197. package/src/runtime-token-remint.ts +1 -1
  198. package/src/service-access.ts +2 -2
  199. package/src/session-destroy.ts +1 -1
  200. package/src/session-insights.ts +1 -1
  201. package/src/session-scratch.ts +24 -37
  202. package/src/spawn-input.ts +6 -6
  203. package/src/startup-config-fetch.ts +4 -4
  204. package/src/subreaper.ts +3 -5
  205. package/src/test-run-dedup.ts +13 -13
  206. package/src/transcript-context.ts +2 -2
  207. package/src/transport-errors.ts +1 -1
  208. package/src/turn-opener.ts +1 -1
  209. package/src/turn-reconcile-event.ts +2 -2
  210. package/src/vault-proxy-hop.ts +2 -2
  211. package/src/web-tools.ts +2 -2
  212. package/agent-bin/agent-relay-browser +0 -10
  213. package/plugins/claude/skills/disconnect/SKILL.md +0 -16
  214. package/plugins/claude/skills/pair/SKILL.md +0 -30
  215. package/plugins/codex/skills/disconnect/SKILL.md +0 -15
  216. package/plugins/codex/skills/pair/SKILL.md +0 -29
  217. package/src/adapters/provider-quota-harvest.ts +0 -76
@@ -19,7 +19,7 @@ export const claudePermissionPromptHandler: ProviderPermissionPromptHandler = {
19
19
  const RECOMMENDED_LABEL = /\(recommended\)\s*$/i;
20
20
  const DECISION_TITLE_MAX = 200;
21
21
 
22
- // #2914: AskUserQuestion → operator decision, 1:1. Claude marks its pick with a "(Recommended)" label.
22
+ // AskUserQuestion → operator decision, 1:1. Claude marks its pick with a "(Recommended)" label.
23
23
  function claudeDecisionRequest(body: Record<string, unknown>): OperatorDecisionFileInput | undefined {
24
24
  const toolInput = isRecord(body.tool_input) ? body.tool_input : {};
25
25
  if (body.tool_name !== "AskUserQuestion" || !Array.isArray(toolInput.questions)) return undefined;
@@ -116,7 +116,7 @@ function claudePermissionHookResponse(decision: ProviderPermissionDecisionInput,
116
116
  hookSpecificOutput: {
117
117
  hookEventName: "PreToolUse",
118
118
  permissionDecision: "deny",
119
- permissionDecisionReason: decisionReasonMessage(decision, "Dismissed from Agent Relay dashboard"),
119
+ permissionDecisionReason: decisionReasonMessage(decision, "Dismissed from Humwick dashboard"),
120
120
  },
121
121
  };
122
122
  }
@@ -150,7 +150,7 @@ function claudePermissionHookResponse(decision: ProviderPermissionDecisionInput,
150
150
  hookEventName,
151
151
  decision: {
152
152
  behavior: "deny",
153
- message: decisionReasonMessage(decision, "Denied from Agent Relay dashboard"),
153
+ message: decisionReasonMessage(decision, "Denied from Humwick dashboard"),
154
154
  interrupt: decision.decision === "abort",
155
155
  },
156
156
  },
@@ -188,7 +188,7 @@ function claudeResolvedPermissionPrompt(view: InteractivePrompt, decision: Provi
188
188
  };
189
189
  }
190
190
 
191
- // #1173: AskUserQuestion's PreToolUse hook is answered with `deny` immediately (see
191
+ // AskUserQuestion's PreToolUse hook is answered with `deny` immediately (see
192
192
  // control-server.ts) so Claude flushes its buffered reasoning instead of blocking on the human.
193
193
  // The user's actual answer arrives afterward as a normal injected prompt, not as a hook
194
194
  // response — this renders that prompt's text from the same view/decision shape the old
@@ -199,7 +199,7 @@ function claudeAnswerInjectionPrompt(view: InteractivePrompt, decision: Provider
199
199
  const rows = resolvedQuestionRows(questions, decision.answers, "(no answer given)");
200
200
  const qa = rows.map((row) => `Q: ${row.question}\nA: ${row.answer}`).join("\n\n");
201
201
  return [
202
- "This was answered by the user via the Agent Relay dashboard — the AskUserQuestion tool call itself was intercepted and will not resolve; it will not return a result. Here is their answer:",
202
+ "This was answered by the user via the Humwick dashboard — the AskUserQuestion tool call itself was intercepted and will not resolve; it will not return a result. Here is their answer:",
203
203
  qa,
204
204
  "Continue the task using their answer(s) above — do not call AskUserQuestion again for the same question(s), and do not wait for the original tool call to return.",
205
205
  ].join("\n\n");
@@ -18,16 +18,16 @@ export interface ClaudeLaunchPromptGatePrevention {
18
18
  type PaneGateMatch = { message?: string };
19
19
 
20
20
  /**
21
- * #1651 — how much authority a pane match is allowed to carry.
21
+ * how much authority a pane match is allowed to carry.
22
22
  *
23
23
  * `advisory` is the only correct value for a gate detected purely from TUI geometry. It
24
24
  * produces a {@link PaneObservation} and NOTHING else: no keystroke (the concept no longer
25
25
  * exists in this file), no ProviderStatusUpdate, hence no status, no `clear`, no
26
- * providerState. #1611/#1615/#1621/#1637/#1642 each tried to make one of these gates safe by
26
+ * providerState. Each tried to make one of these gates safe by
27
27
  * tightening its string matcher; the matcher was never the problem — granting a string match
28
28
  * the authority to mutate state was.
29
29
  *
30
- * The retired usage-limit pane gate (#1664) is deliberately not represented here: provider
30
+ * The retired usage-limit pane gate is deliberately not represented here: provider
31
31
  * state comes from structured provider failures, never a pane scrape.
32
32
  */
33
33
 
@@ -53,7 +53,7 @@ export interface ClaudePromptGatePaneContext {
53
53
  }
54
54
 
55
55
  export interface ClaudePromptGatePaneResult {
56
- /** #1651 — always present when a gate reached minDetections. Diagnostic only. */
56
+ /** always present when a gate reached minDetections. Diagnostic only. */
57
57
  observation?: PaneObservation;
58
58
  /** Kept for callers which assert absence; `never` makes a status impossible to emit. */
59
59
  status?: never;
@@ -87,7 +87,7 @@ export const claudeConfigPromptGatePreventions: ClaudeConfigPromptGatePrevention
87
87
  id: "workspace-trust",
88
88
  description: "Trust the managed spawn cwd and clear project onboarding.",
89
89
  apply(seed, { config }) {
90
- // #2254 — seed EVERY key CC may resolve this cwd under (lexical, realpath, git
90
+ // seed EVERY key CC may resolve this cwd under (lexical, realpath, git
91
91
  // canonical root), not just the lexical worktree path. Host-base launches, which
92
92
  // never reach this seed, get the same keys written into the shared config by
93
93
  // ensureClaudeWorkspaceTrust on the launch path.
@@ -117,7 +117,7 @@ export const claudeLaunchPromptGatePreventions: ClaudeLaunchPromptGatePrevention
117
117
  const WORKSPACE_TRUST_PROMPT = /do you trust (?:the files in )?(?:this folder|this project|this directory)|accessing workspace|quick safety check|yes,? ?I trust this folder/i;
118
118
 
119
119
  const panePromptGates: ClaudePanePromptGate[] = [
120
- // #1651 — the five gates below are ADVISORY. Each used to type a key into a live pane the
120
+ // the five gates below are ADVISORY. Each used to type a key into a live pane the
121
121
  // instant `claudePaneIsBusy` happened to read false, then publish a fabricated
122
122
  // idle + clear:["subagent"]. Two independent hazards, both realized:
123
123
  // - the four Enter gates SUBMIT whatever text is already sitting in the composer;
@@ -135,7 +135,7 @@ const panePromptGates: ClaudePanePromptGate[] = [
135
135
  label: "Workspace trust",
136
136
  policy: "accept-trusted-workspace",
137
137
  minDetections: 2,
138
- // #2254 — CC 2.1.233 reworded this modal ("Accessing workspace" / "Quick safety check:
138
+ // CC 2.1.233 reworded this modal ("Accessing workspace" / "Quick safety check:
139
139
  // Is this a project you created or one you trust?" / "Yes, I trust this folder"), so the
140
140
  // old "do you trust this folder" matcher never fired and the wedge was silent. Both
141
141
  // wordings are matched now. Still ADVISORY (see the block comment above): the actual
@@ -180,14 +180,14 @@ const panePromptGates: ClaudePanePromptGate[] = [
180
180
  ? { message: lineMatching(text, /welcome to Claude Code/i) }
181
181
  : null,
182
182
  },
183
- // #1514 (review finding 3) — the "Exit anyway / Move to background / Stay"
183
+ // (review finding 3) — the "Exit anyway / Move to background / Stay"
184
184
  // confirmation is deliberately NOT a live pane-watcher gate. Auto-pressing Enter
185
185
  // on a session that did NOT initiate shutdown is unsafe: it could submit stray
186
186
  // input or dismiss an unrelated modal on an attached/live session. This dialog is
187
187
  // only ever the right thing to answer under an explicit Relay shutdown, so it is
188
188
  // handled solely by the runner's shutdown grace loop (confirmClaudeExitDialogUnderGrant
189
189
  // in claude-exit.ts) — which runs only after this runner sent `/exit` under a one-shot
190
- // grant. #1647 made that structural rather than advisory: this file cannot even import
190
+ // grant. Made that structural rather than advisory: this file cannot even import
191
191
  // the exit writers (claude-exit.guard.test.ts fails the build if it ever does). The
192
192
  // orphan safety net for a session already wedged mid-shutdown is the orchestrator's
193
193
  // wedged-session reaper, not a keypress into a live pane.
@@ -1,10 +1,10 @@
1
- // #2664 — Claude's UserPromptSubmit hook fires only when Claude accepts an Enter: at queue time
1
+ // Claude's UserPromptSubmit hook fires only when Claude accepts an Enter: at queue time
2
2
  // for a busy agent and never again on dequeue, and not at all for a swallowed Enter. A matching
3
3
  // hook fired after our Enter is therefore the acceptance proof; the composer read is only a fallback.
4
4
 
5
5
  // Transport changes observed on Claude Code 2.1.284-2.1.285, and nothing else: image paths it attached
6
6
  // become leading [Image #N] chips, and a long paste arrives inside a <pasted_content> wrapper, even
7
- // a single-line one (#3081). The whitespace around the wrapper and its body varies by release, so
7
+ // a single-line one. The whitespace around the wrapper and its body varies by release, so
8
8
  // both are compared trimmed.
9
9
  // Image pastes also collapse blank-line runs (including the runner's attachment separator).
10
10
  const LEADING_IMAGE_CHIPS = /^(?:\[Image #\d+\])+/;
@@ -31,13 +31,13 @@ const REASONING_POLL_MS = 1_200;
31
31
  // Bounded the same way as control-server.ts's resolvedApprovals so a long session's dedup
32
32
  // guard (see emittedStepKeys) can't grow without limit.
33
33
  const EMITTED_STEP_KEY_LIMIT = 2048;
34
- // #2919 — a prompt still queued at a Stop was enqueued after the turn's last tool boundary, so it
34
+ // a prompt still queued at a Stop was enqueued after the turn's last tool boundary, so it
35
35
  // sits in the transcript's last entries; a bounded tail read finds it.
36
36
  const QUEUE_SCAN_BYTES = 256 * 1024;
37
37
  const HOOKED_PROMPT_LIMIT = 8;
38
38
 
39
39
  export class ClaudeSessionCapture {
40
- // #2664 — every UserPromptSubmit hook; the proof that Claude accepted a submitted prompt.
40
+ // every UserPromptSubmit hook; the proof that Claude accepted a submitted prompt.
41
41
  readonly promptHooks = new ClaudePromptHookLedger();
42
42
  private narrativeFlushEntryCount = 0;
43
43
  private reasoningTail?: ReasoningTailState;
@@ -50,7 +50,7 @@ export class ClaudeSessionCapture {
50
50
  // it can re-derive the exact same step signatures the earlier tail already emitted. Seeding
51
51
  // each new tail's `seen` set from this closes that gap regardless of the exact timing.
52
52
  private emittedStepKeys = new Set<string>();
53
- // #2919 — recent UserPromptSubmit prompts. Claude hooks a prompt submitted mid-turn when it queues
53
+ // recent UserPromptSubmit prompts. Claude hooks a prompt submitted mid-turn when it queues
54
54
  // it and never again when it runs it. Pruned at every Stop to the ones still queued.
55
55
  private hookedPrompts: string[] = [];
56
56
  private sessionEventCb: (event: ProviderSessionEvent) => void = () => {};
@@ -93,7 +93,7 @@ export class ClaudeSessionCapture {
93
93
  const flushCount = this.narrativeFlushEntryCount;
94
94
  this.narrativeFlushEntryCount = 0;
95
95
 
96
- // #2840 — the reasoning tail already mirrors each narration line as its own row, so the
96
+ // the reasoning tail already mirrors each narration line as its own row, so the
97
97
  // turn-final (and the report built from it) is the closing text only.
98
98
  const closingOnly = ctx.chatCaptureMode === "final" || ctx.reasoningCapture !== false;
99
99
  let body = extractHookAssistantMessage(input.lastAssistantMessage);
@@ -145,7 +145,7 @@ export class ClaudeSessionCapture {
145
145
  ctx.log(input.firedAt === undefined ? "hook check-in carried no firedAt; submits stay on composer confirmation" : "hook check-in: stamped hook path verified");
146
146
  }
147
147
 
148
- /** #2919 — whether a prompt Claude already hooked is still queued, so it runs next with no hook of its own. */
148
+ /** whether a prompt Claude already hooked is still queued, so it runs next with no hook of its own. */
149
149
  private async hookedPromptStillQueued(transcriptPath: string): Promise<boolean> {
150
150
  if (!this.hookedPrompts.length) return false;
151
151
  const queued = pendingQueuedPrompts(await readTranscriptTail(transcriptPath)).map((prompt) => prompt.trim());
@@ -174,7 +174,7 @@ export class ClaudeSessionCapture {
174
174
  }
175
175
  }
176
176
 
177
- // #3411 — runs whatever reasoningCapture says: its tool steps carry the paths area memory matches,
177
+ // runs whatever reasoningCapture says: its tool steps carry the paths area memory matches,
178
178
  // and the session mirror drops the steps from chat when reasoningCapture is off.
179
179
  private startReasoningTail(transcriptPath: string, ctx: ProviderSessionTurnContext): void {
180
180
  this.stopSessionTrace();
@@ -1,4 +1,4 @@
1
- // #766/#768 — Claude Code's intrinsic, machine-readable session probe.
1
+ // Claude Code's intrinsic, machine-readable session probe.
2
2
  //
3
3
  // Claude Code ≥2.1.119 writes `$CLAUDE_CONFIG_DIR/sessions/<pid>.json` and rewrites
4
4
  // it on every status transition. It carries an authoritative `status` field
@@ -33,7 +33,7 @@ export const CLAUDE_PROBE_MIN_VERSION = "2.1.119";
33
33
  /**
34
34
  * The probe half of an activity read: the mapped 3-valued activity (undefined when the probe is
35
35
  * absent/old/unparseable → the caller falls back to the pane) plus `busyStale`: the busy verdict
36
- * predates a turn-cancelling keystroke relay itself sent (#1621). Age never discredits it (#3026):
36
+ * predates a turn-cancelling keystroke relay itself sent. Age never discredits it:
37
37
  * Claude writes neither probe nor transcript during a long think, so a frozen busy is held and the
38
38
  * notify-only prolonged-hold watchdog surfaces a turn that really died out of band.
39
39
  */
@@ -45,7 +45,7 @@ export function claudeProbeVerdict(
45
45
  if (activity !== "busy" || !reading || opts.paneInterruptAt === undefined || reading.updatedAt === null) {
46
46
  return { activity, busyStale: false };
47
47
  }
48
- // #1642 — a transcript append after the interrupt means the cancel did not take.
48
+ // a transcript append after the interrupt means the cancel did not take.
49
49
  const cancelDidNotTake = opts.lastActivityAt !== undefined && opts.lastActivityAt > opts.paneInterruptAt;
50
50
  return { activity, busyStale: reading.updatedAt <= opts.paneInterruptAt && !cancelDidNotTake };
51
51
  }
@@ -61,9 +61,9 @@ export interface ClaudeSessionReading {
61
61
  procStart: string | null;
62
62
  /** `updatedAt` epoch-ms of the last probe write, or null. */
63
63
  updatedAt: number | null;
64
- /** Claude's own session UUID (#1235) — names the transcript file. Null when absent. */
64
+ /** Claude's own session UUID — names the transcript file. Null when absent. */
65
65
  sessionId: string | null;
66
- /** The session's cwd (#1235) — slugified into the transcript's project dir. Null when absent. */
66
+ /** The session's cwd — slugified into the transcript's project dir. Null when absent. */
67
67
  cwd: string | null;
68
68
  /** True iff a present, parseable, version-gated probe produced `status`. */
69
69
  available: boolean;
@@ -72,7 +72,7 @@ export interface ClaudeSessionReading {
72
72
  const UNAVAILABLE: ClaudeSessionReading = { status: "unknown", waitingFor: null, version: null, procStart: null, updatedAt: null, sessionId: null, cwd: null, available: false };
73
73
 
74
74
  /**
75
- * Process-liveness seam for the staleness guard (#769 P1-B), injectable in tests.
75
+ * Process-liveness seam for the staleness guard, injectable in tests.
76
76
  * procStartOf — the start-time token of a LIVE pid (to compare against the probe's
77
77
  * `procStart`), or null when the pid is dead/unreadable. Catches both a frozen
78
78
  * probe from a SIGKILLed claude AND a sub-second pid-reuse window (a reused pid
@@ -89,7 +89,7 @@ function procStatStartTime(stat: string): string | null {
89
89
  return stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/)[19] ?? null;
90
90
  }
91
91
 
92
- // #1838 rows 14/15 — off-loop: the 4s reconciler / 1.5s turn-watch tick never blocks on the read.
92
+ // rows 14/15 — off-loop: the 4s reconciler / 1.5s turn-watch tick never blocks on the read.
93
93
  async function readProcStartTimeAsync(pid: number): Promise<string | null> {
94
94
  try {
95
95
  return procStatStartTime(await readFile(`/proc/${pid}/stat`, "utf8"));
@@ -98,7 +98,7 @@ async function readProcStartTimeAsync(pid: number): Promise<string | null> {
98
98
  }
99
99
  }
100
100
 
101
- // #3151 — off Linux, Claude stamps procStart as `ps -o lstart=` in UTC ("Sun Oct 25 13:01:19 2026").
101
+ // off Linux, Claude stamps procStart as `ps -o lstart=` in UTC ("Sun Oct 25 13:01:19 2026").
102
102
  const PS_LSTART_TIMEOUT_MS = 5_000;
103
103
  const psLstartArgv = (pid: number): string[] => ["ps", "-o", "lstart=", "-p", String(pid)];
104
104
  const psLstartEnv = (): Record<string, string | undefined> => ({ ...process.env, TZ: "UTC", LC_ALL: "C" });
@@ -125,9 +125,9 @@ export async function readPsLstartUtcAsync(pid: number): Promise<string | null>
125
125
  // the probe on a status *transition*, so a legitimately busy turn keeps a frozen
126
126
  // `updatedAt` for its whole duration (verified: a 90s+ busy turn never rewrote it).
127
127
  // A staleness threshold would force a long busy turn back to the pane-scrape and
128
- // reintroduce the #766 idle-while-working bug; pid-liveness + procStart cover the
128
+ // reintroduce the idle-while-working bug; pid-liveness + procStart cover the
129
129
  // SIGKILL-frozen and pid-reuse cases without that hazard. `available` is not
130
- // time-gated, and neither is the busy verdict (#3026, see claudeProbeVerdict).
130
+ // time-gated, and neither is the busy verdict.
131
131
  /** The off-loop probe liveness check for `platform`: `/proc` on Linux, UTC `ps` lstart elsewhere. */
132
132
  export function claudeProbeLivenessAsync(
133
133
  platform: string = process.platform,
@@ -208,7 +208,7 @@ export function parseClaudeSessionProbe(raw: string, minVersion = CLAUDE_PROBE_M
208
208
  /**
209
209
  * Read + parse the probe for `pid` under `configHome`. Missing file (older CC, a config home the
210
210
  * runner can't resolve, or claude not yet started) → unavailable, so the caller falls back to the
211
- * pane-scrape. Off-loop (#1838): every caller is a timer tick or the async `/exit` correlator.
211
+ * pane-scrape. Off-loop: every caller is a timer tick or the async `/exit` correlator.
212
212
  */
213
213
  export async function readClaudeSessionStatusAsync(
214
214
  configHome: string,
@@ -223,7 +223,7 @@ export async function readClaudeSessionStatusAsync(
223
223
  }
224
224
  const reading = parseClaudeSessionProbe(raw, opts.minVersion ?? CLAUDE_PROBE_MIN_VERSION);
225
225
  if (!reading.available) return reading;
226
- // #769 P1-B: a frozen probe from a SIGKILLed claude (or a reused pid) would report a stale
226
+ // P1-B: a frozen probe from a SIGKILLed claude (or a reused pid) would report a stale
227
227
  // busy/shell forever. Verify the pid still belongs to the same process.
228
228
  const live = opts.liveness ?? DEFAULT_LIVENESS_ASYNC;
229
229
  if (reading.procStart) {
@@ -252,7 +252,7 @@ export function claudeProbeArgsFor(process: ManagedProcess, tmuxSession: string,
252
252
  }
253
253
 
254
254
  /**
255
- * #2261 — a clear-commit probe reading: Claude's own session UUID together with the pid it was
255
+ * a clear-commit probe reading: Claude's own session UUID together with the pid it was
256
256
  * read for. A committed `/clear` mints a NEW session, so a rotation of `sessionId` OBSERVED FOR
257
257
  * THE SAME PID is machine-readable proof the loaded conversation is gone.
258
258
  */
@@ -261,7 +261,7 @@ export interface ClaudeClearSessionReading { sessionId: string; pid: number }
261
261
  /**
262
262
  * Read one, or null for every "could not find out" outcome: no resolvable pid, no probe file, a
263
263
  * version-gated-out or liveness-failed reading (`available === false`, which is what rules out a
264
- * SIGKILL-frozen probe and a reused pid — #769 P1-B), or a probe carrying no sessionId at all.
264
+ * SIGKILL-frozen probe and a reused pid — P1-B), or a probe carrying no sessionId at all.
265
265
  * Deliberately total (never throws, never distinguishes flavours of absence): its only consumer
266
266
  * treats anything short of a confirmed same-pid rotation as no evidence.
267
267
  */
@@ -276,7 +276,7 @@ export async function readClaudeClearSessionAsync(args: ManagedClaudeProbeArgs):
276
276
  }
277
277
 
278
278
  /**
279
- * #2261 — bind a clear-commit reader to ONE pane pid, resolved up front.
279
+ * bind a clear-commit reader to ONE pane pid, resolved up front.
280
280
  *
281
281
  * The barrier polls ~50 times inside its 10s budget. Re-resolving the pane pid per poll spawned a
282
282
  * `tmux display-message` each time (the pid is undefined for a tmux launch), and — worse — let the
@@ -299,7 +299,7 @@ export async function bindClaudeClearSessionReader(
299
299
  export type ClaudeClearSessionVerdict = "rotated" | "unchanged" | "unknown";
300
300
 
301
301
  /**
302
- * #2261 — compare a pre-clear reading against a current one. Both must be present AND describe the
302
+ * compare a pre-clear reading against a current one. Both must be present AND describe the
303
303
  * SAME pid; anything else is `unknown`, never a verdict. A `rotated` verdict is positive proof the
304
304
  * clear committed; an `unchanged` one is positive proof it has NOT (yet) — the probe is
305
305
  * authoritative in both directions whenever it is readable on both sides.
@@ -327,10 +327,10 @@ export async function readManagedClaudeStatusAsync(
327
327
  }
328
328
 
329
329
  /**
330
- * The transcript JSONL path Claude Code writes for a session (#1235). Claude stores
330
+ * The transcript JSONL path Claude Code writes for a session. Claude stores
331
331
  * per-cwd transcripts at `<configHome>/projects/<cwd-slug>/<sessionId>.jsonl`, where
332
332
  * the slug replaces every non-alphanumeric char in the absolute cwd with `-` (verified
333
- * live on CC 2.1.205: `/home/u/p/.agent-relay/x` → `-home-u-p--agent-relay-x`). This is
333
+ * live on CC 2.1.205: `/home/u/p/.humwick/x` → `-home-u-p--humwick-x`). This is
334
334
  * the hookless path the monitorless turn-watch feeds to the transcript reasoning tail so
335
335
  * isolated/lean Claude workers (no Stop/UserPromptSubmit hooks) still mirror chat.
336
336
  */
@@ -344,16 +344,16 @@ export function claudeTranscriptPath(configHome: string, cwd: string, sessionId:
344
344
  }
345
345
 
346
346
  /**
347
- * #1642 — the transcript's last-modified time in epoch-ms, or undefined when it can't be read
347
+ * the transcript's last-modified time in epoch-ms, or undefined when it can't be read
348
348
  * (absent/old probe with no sessionId+cwd, or the file not yet created). Claude appends to this
349
349
  * JSONL on every turn event, so its mtime is the runner's hookless "events still flowing"
350
350
  * heartbeat — the signal that keeps an actively-streaming turn's frozen busy probe from being
351
351
  * aged out to the pane. A failed stat is silence, never a claim the turn ended: undefined leaves
352
- * #1621 untouched.
352
+ * untouched.
353
353
  *
354
- * #1838 rows 14/15 — the only caller (the busy reconciler / turn watch, via activityFromProbeOrPane)
354
+ * rows 14/15 — the only caller (the busy reconciler / turn watch, via activityFromProbeOrPane)
355
355
  * is now off the event loop, so this is the sole variant and uses `fs/promises`. (The synchronous
356
- * version was removed in the #1838 round-3 review: it had no production callers once the hot path
356
+ * version was removed in the round-3 review: it had no production callers once the hot path
357
357
  * went async — see the critical-loop ratchet note for claude-session-probe.ts.)
358
358
  */
359
359
  export async function claudeTranscriptMtimeMsAsync(
@@ -371,7 +371,7 @@ export async function claudeTranscriptMtimeMsAsync(
371
371
  }
372
372
 
373
373
  /**
374
- * Resolve the live transcript path for the managed claude via its session probe (#1235): the
374
+ * Resolve the live transcript path for the managed claude via its session probe: the
375
375
  * probe carries `sessionId` + `cwd`, and the runner knows the config home. undefined when the
376
376
  * probe is absent/old or the pid can't be resolved (graceful degrade: no native capture).
377
377
  */
@@ -389,13 +389,13 @@ export async function resolveManagedClaudeTranscriptPathAsync(args: {
389
389
  }
390
390
 
391
391
  /**
392
- * Map the intrinsic probe status to the 3-valued activity. The whole point of #766:
392
+ * Map the intrinsic probe status to the 3-valued activity. The whole point:
393
393
  * busy → busy — a turn is running, foreground tools included; the old
394
394
  * pane-scrape misread it as idle and force-cleared.
395
395
  * idle/shell → idle — `shell` is Claude's `idle` while a background local_bash task
396
396
  * is alive (2.1.310: `status==="idle" && hasLiveLocalBash`).
397
397
  * Relay's own monitor is one, so a managed session at rest
398
- * reads `shell`, including right after an ESC (#2202).
398
+ * reads `shell`, including right after an ESC.
399
399
  * waiting → unknown — a permission/input dialog is a parked (not done) turn, so
400
400
  * we neither assert compute-busy nor let the backstop clear
401
401
  * the live turn. `waitingFor` is surfaced separately for
@@ -1,10 +1,10 @@
1
- // #1647 — the Claude tmux shutdown sequence: observe, correlate, and only then
2
- // (maybe) exit gracefully. Split out of ./claude at the #300 ratchet; the adapter
1
+ // the Claude tmux shutdown sequence: observe, correlate, and only then
2
+ // (maybe) exit gracefully. Split out of ./claude at the ratchet; the adapter
3
3
  // keeps the watcher teardown and delegates the rest here.
4
4
  //
5
5
  // The policy itself lives in ./claude-exit. This module is the caller that samples
6
6
  // live state and drives the loop.
7
- import { managedTmuxSocketName, prepareTmuxPaneStop, prepareTmuxStop, tmuxHasSession, type PreparedTmuxStop } from "@humwick/sdk/tmux-utils";
7
+ import { prepareTmuxPaneStop, prepareTmuxStop, tmuxHasSession, type PreparedTmuxStop } from "@humwick/sdk/tmux-utils";
8
8
  import { tmuxOwnerIdFromEnv } from "../config";
9
9
  import type { ManagedProcess, ShutdownOutcome } from "../adapter";
10
10
  import { claudeProbeActivity, claudeProbeArgsFor, readManagedClaudeStatusAsync, type ClaudeProbeLivenessAsync } from "./claude-session-probe";
@@ -43,10 +43,10 @@ export async function observeClaudeExitState(
43
43
  fallbackEpoch: number,
44
44
  seams: { liveness?: ClaudeProbeLivenessAsync; capturePane?: typeof captureTmuxPaneAsync } = {},
45
45
  ): Promise<ClaudeExitObservation> {
46
- // #3152 — the shared off-loop reader, so a macOS `ps` lstart procStart binds this reading too.
46
+ // the shared off-loop reader, so a macOS `ps` lstart procStart binds this reading too.
47
47
  const managed = await readManagedClaudeStatusAsync(claudeProbeArgsFor(process, sessionName, socketName), seams.liveness);
48
48
  const reading = managed?.reading;
49
- // #3186 — a managed Claude at rest reads `shell` (Relay's monitor is a live background task).
49
+ // a managed Claude at rest reads `shell` (Relay's monitor is a live background task).
50
50
  const probeIdle = reading?.available === true && claudeProbeActivity(reading.status) === "idle";
51
51
  let turn: ClaudeTurnState = probeIdle ? "ended" : reading?.available === true ? "live" : "unknown";
52
52
  if (turn === "ended") {
@@ -75,7 +75,7 @@ export interface ClaudeShutdownDeps {
75
75
  observe?: (process: ManagedProcess, sessionName: string, socketName?: string) => ClaudeExitObservation | Promise<ClaudeExitObservation>;
76
76
  submitExit?: (grant: ClaudeExitGrant) => Promise<unknown>;
77
77
  confirmExit?: (grant: ClaudeExitGrant) => unknown;
78
- // #1746 — the reap now returns a terminal outcome (it verifies death, it does not merely
78
+ // the reap now returns a terminal outcome (it verifies death, it does not merely
79
79
  // fire a kill-session). Tests that only care that the reap ran may still return void.
80
80
  reap?: (sessionName: string, socketName?: string) => ShutdownOutcome | void | Promise<ShutdownOutcome | void>;
81
81
  sleep?: (ms: number) => Promise<unknown>;
@@ -119,7 +119,7 @@ export async function shutdownClaudeTmux(
119
119
  // Session disappearance still requires verification of the captured descendants.
120
120
  if (!hasSession(sessionName, socketName)) return stop();
121
121
  if (!grant) {
122
- // #1647 — the guard that would have saved the coordinator. A turn that is live
122
+ // the guard that would have saved the coordinator. A turn that is live
123
123
  // (or that we cannot positively confirm has ended) authorizes nothing, so we
124
124
  // keep waiting and let the out-of-band reap below terminate the process if the
125
125
  // turn never settles.
@@ -134,7 +134,7 @@ export async function shutdownClaudeTmux(
134
134
  }
135
135
  } else {
136
136
  // `/exit` while a background task is running raises Claude's "Exit anyway /
137
- // Move to background / Stay" confirmation (#1514). Answering it is allowed ONLY
137
+ // Move to background / Stay" confirmation. Answering it is allowed ONLY
138
138
  // through the same grant that submitted that `/exit`, and only for a dialog that
139
139
  // was not already on screen beforehand.
140
140
  confirmExit(grant);
@@ -143,9 +143,9 @@ export async function shutdownClaudeTmux(
143
143
  }
144
144
  }
145
145
 
146
- // The out-of-band backstop, and the path #1647 prefers: it terminates the process
146
+ // The out-of-band backstop, and the path prefers: it terminates the process
147
147
  // without typing anything into the pane, so it is always reached when the guard above
148
- // declines to authorize a graceful exit. #1746 — this is now a VERIFIED, escalating
148
+ // declines to authorize a graceful exit. This is now a VERIFIED, escalating
149
149
  // force-kill that returns a terminal outcome, not a fire-and-forget kill-session.
150
150
  return stop();
151
151
  }
@@ -156,7 +156,7 @@ async function prepareClaudeStop(sessionName: string, socketName: string | undef
156
156
  at: new Date().toISOString(), name, socket, ...failure,
157
157
  })),
158
158
  })))(sessionName, socketName);
159
- const preparePane = async () => socketName === managedTmuxSocketName(sessionName)
159
+ const preparePane = async () => socketName !== undefined
160
160
  ? (deps.preparePaneStop ?? prepareTmuxPaneStop)({ name: sessionName, socket: socketName, owner: tmuxOwnerIdFromEnv() }).catch(() => undefined)
161
161
  : undefined;
162
162
  let fallback = await preparePane();