@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
@@ -3,7 +3,7 @@ import type { PaneObservation, ProviderStatusUpdate } from "../adapter";
3
3
  import type { ClaudeClearSessionVerdict, ClaudeProbeActivity } from "./claude-session-probe";
4
4
  import { buildRateLimitProviderState, parseClaudeRateLimitPane } from "../rate-limit";
5
5
 
6
- // LEGACY PANE-SCRAPE FALLBACK (#766/#768) — busy/idle is now driven primarily by
6
+ // LEGACY PANE-SCRAPE FALLBACK — busy/idle is now driven primarily by
7
7
  // Claude Code's intrinsic session probe (readClaudeSessionStatusAsync, ./claude-session-probe):
8
8
  // `$CLAUDE_CONFIG_DIR/sessions/<pid>.json` carries a machine-readable `status` field
9
9
  // on CC ≥2.1.119. These two pane heuristics are the SOLE fallback for pre-2.1.119 /
@@ -61,7 +61,7 @@ export function claudeBypassDialogDisplayGate(sessionName: string, observedAt: n
61
61
  }
62
62
 
63
63
  // The live spinner: "✶ Perambulating… (2m 17s · ↓ 8.7k tokens)", "… (", optional hook or
64
- // tool status ("running PostToolUse hook · ", #3026), then "[Nh ][Nm ]Ns". Anchored on the
64
+ // tool status, then "[Nh ][Nm ]Ns". Anchored on the
65
65
  // gerund ellipsis so it can't match the "… +N lines (ctrl+o to expand)" truncation marker,
66
66
  // the idle input box, or the "/btw … without interrupting Claude's current work" hint.
67
67
  const CLAUDE_BUSY_SPINNER_RE = /…\s*\((?:[^()\n]*·\s*)?(?:\d+h\s+)?(?:\d+m\s+)?\d+s\b/;
@@ -88,7 +88,7 @@ interface ClaudePaneRegions {
88
88
  activity?: string[];
89
89
  }
90
90
 
91
- // #2202 — find the composer instead of counting rows from the bottom. Inline rendering
91
+ // find the composer instead of counting rows from the bottom. Inline rendering
92
92
  // (CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN) pads the capture with blank rows below it, and a
93
93
  // completed "Thought for …" row can sit within any fixed window above it.
94
94
  function claudePaneRegions(text: string): ClaudePaneRegions {
@@ -120,7 +120,7 @@ function claudeRegionsAreBusy({ footer, activity }: ClaudePaneRegions): boolean
120
120
  // the pane veto works across those formats.
121
121
  if (footer.includes("esc to interrupt")) return true;
122
122
  if (!activity) return CLAUDE_BUSY_SPINNER_RE.test(footer) || CLAUDE_BUSY_THOUGHT_RE.test(footer);
123
- // Rows rejoin so a counter Claude wrapped at a narrow width still reads (#3026). A thinking
123
+ // Rows rejoin so a counter Claude wrapped at a narrow width still reads. A thinking
124
124
  // row is live only as the block's head: a completed one stays above "⎿ Interrupted" or output.
125
125
  return CLAUDE_BUSY_SPINNER_RE.test(activity.join(" ")) || CLAUDE_BUSY_THOUGHT_RE.test(claudeActivityHead(activity));
126
126
  }
@@ -131,7 +131,7 @@ function claudeActivityHead(activity: string[]): string {
131
131
  return (tip >= 0 ? activity.slice(0, tip) : activity).at(-1) ?? "";
132
132
  }
133
133
 
134
- // #1588 — persistent TUI chrome that is present at the prompt regardless of whether a
134
+ // persistent TUI chrome that is present at the prompt regardless of whether a
135
135
  // conversation is loaded, so it must NEVER be picked as a pre-clear conversation anchor.
136
136
  // (Includes the claudePaneLooksReady footer markers plus the box/hint chrome around them.)
137
137
  const CLAUDE_CHROME_MARKERS = [
@@ -148,14 +148,14 @@ const CLAUDE_CHROME_MARKERS = [
148
148
  "Tips for getting started",
149
149
  ];
150
150
 
151
- // #1588 — pick a distinctive line from the PRE-clear pane that belongs to the loaded
151
+ // pick a distinctive line from the PRE-clear pane that belongs to the loaded
152
152
  // conversation body (not the persistent chrome/footer), so its DISAPPEARANCE after `/clear`
153
153
  // is positive evidence the conversation was actually wiped. claudePaneLooksReady alone can't
154
154
  // tell "before clear" from "after clear" — its footer markers are present in both — so the
155
155
  // self-resume barrier (waitForClaudeClearCommitted) keys off this anchor instead. Returns the
156
156
  // longest content-rich, chrome-free line, or undefined when the pane has no such line (e.g. an
157
157
  // already-empty conversation) — in which case the clear can't be positively confirmed and the
158
- // caller falls to continuationDelivered:false so the #1023 backstop covers it.
158
+ // caller falls to continuationDelivered:false so the backstop covers it.
159
159
  export function claudeConversationAnchor(pane: string): string | undefined {
160
160
  let best: string | undefined;
161
161
  for (const raw of claudeConversationRows(pane)) {
@@ -175,7 +175,7 @@ const CLAUDE_BANNER_RE = /Claude Code v\d/;
175
175
  // A turn opens with the user's prompt or an assistant, tool or event bullet.
176
176
  const CLAUDE_TURN_ROW_RE = /^\s*[❯>⏺●]/;
177
177
 
178
- // #3151 — the rows `/clear` wipes: none of the startup header (banner, model, cwd, tip), which it
178
+ // the rows `/clear` wipes: none of the startup header (banner, model, cwd, tip), which it
179
179
  // repaints, and nothing from the composer down, whose status rows change on their own.
180
180
  function claudeConversationRows(pane: string): string[] {
181
181
  const lines = pane.trimEnd().split("\n");
@@ -186,32 +186,32 @@ function claudeConversationRows(pane: string): string[] {
186
186
  return start < 0 ? [] : lines.slice(start, end);
187
187
  }
188
188
 
189
- // #1588 — CLEAR-AWARE completion check for the self-resume barrier. The `/clear` keystroke is
189
+ // CLEAR-AWARE completion check for the self-resume barrier. The `/clear` keystroke is
190
190
  // async in the Claude TUI: submitTextToTmux returns as soon as the keys are typed, and the
191
191
  // persistent footer (claudePaneLooksReady) matches the STILL-UNCLEARED pane, so accepting the
192
- // first ready match let the continuation race the async clear and get wiped (the #1588 window).
192
+ // first ready match let the continuation race the async clear and get wiped.
193
193
  // A clear is treated as COMMITTED only with positive evidence: the pane is (a) not busy (the
194
194
  // clear/redraw finished processing), (b) back at the input-ready footer, and (c) no longer shows
195
195
  // the pre-clear conversation anchor (the loaded conversation is gone). Any of these missing →
196
196
  // NOT committed → keep waiting (and, on timeout, the caller reports continuationDelivered:false
197
197
  // so the backstop re-injects — uncertainty resolves toward the backstop, never toward a wipe).
198
198
  //
199
- // #2261 — the anchor test above is the FALLBACK signal, not the primary one, and `currentPane`
199
+ // the anchor test above is the FALLBACK signal, not the primary one, and `currentPane`
200
200
  // MUST be a VISIBLE-SCREEN capture (captureTmuxVisiblePaneAsync). Claude's `/clear` repaints only
201
201
  // the visible screen, so an anchor that had already scrolled into tmux history stays in a
202
202
  // scrollback capture forever and the barrier can never confirm a clear that plainly committed.
203
203
  export interface ClaudeClearEvidence {
204
204
  /**
205
- * #2261 — the intrinsic probe's verdict on Claude's session id (./claude-session-probe), ALREADY
205
+ * the intrinsic probe's verdict on Claude's session id (./claude-session-probe), ALREADY
206
206
  * SETTLED by the caller across a poll interval. Default `unknown` (no probe / pre-2.1.119 /
207
207
  * unreadable / liveness-failed / pid changed) keeps the pure anchor behavior.
208
208
  */
209
209
  session?: ClaudeClearSessionVerdict;
210
- /** #2849 — pre-clear pane captured AND anchorless: vacuous clear, so idle+ready
210
+ /** pre-clear pane captured AND anchorless: vacuous clear, so idle+ready
211
211
  * suffices. Never set from a failed capture — that stays fail-closed. */
212
212
  preClearEmpty?: boolean;
213
- /** #2849r1/#2850 — the poller saw a CLAUDE_VACUOUS_CLEAR_READY_POLLS idle+ready streak.
214
- * A ready pane can predate the /clear commit (#1588), and with no probe verdict this
213
+ /** the poller saw a CLAUDE_VACUOUS_CLEAR_READY_POLLS idle+ready streak.
214
+ * A ready pane can predate the /clear commit, and with no probe verdict this
215
215
  * streak is the only commit evidence. Set by the poller. */
216
216
  settledReady?: boolean;
217
217
  }
@@ -223,7 +223,7 @@ export function claudeClearCommitted(
223
223
  ): boolean {
224
224
  if (claudePaneIsBusy(currentPane)) return false;
225
225
  if (!claudePaneLooksReady(currentPane)) return false;
226
- // #2261 PRIMARY signal — machine-readable and version-robust: committing `/clear` mints a NEW
226
+ // PRIMARY signal — machine-readable and version-robust: committing `/clear` mints a NEW
227
227
  // Claude session (fresh sessionId + fresh transcript file), so a same-pid session rotation is
228
228
  // direct proof the loaded conversation is gone, independent of any TUI chrome.
229
229
  //
@@ -231,31 +231,31 @@ export function claudeClearCommitted(
231
231
  // a relaxation. `unchanged` means the probe positively says the clear has not committed, so it
232
232
  // must VETO — never let the (visible-screen-only) anchor heuristic confirm over it, e.g. when
233
233
  // unrelated output has merely scrolled the anchor off the screen. The busy/ready gates above
234
- // still gate both verdicts, so the #1588 ordering property (never inject while the clear is
234
+ // still gate both verdicts, so the ordering property (never inject while the clear is
235
235
  // still processing) is untouched. `unknown` is no evidence: it falls through to the anchor test,
236
- // whose failure still resolves to false → the #1023 backstop re-injects.
236
+ // whose failure still resolves to false → the backstop re-injects.
237
237
  if (evidence.session === "rotated") return true;
238
238
  if (evidence.session === "unchanged") return false;
239
- // #2849r1 — vacuous clear, BELOW the session veto: an unchanged session still
240
- // refuses, and a lone ready poll may be the pre-clear footer (#1588), so both
241
- // settledReady (a long consecutive-ready streak, #2850) and preClearEmpty are required.
239
+ // vacuous clear, BELOW the session veto: an unchanged session still
240
+ // refuses, and a lone ready poll may be the pre-clear footer, so both
241
+ // settledReady (a long consecutive-ready streak) and preClearEmpty are required.
242
242
  if (evidence.preClearEmpty === true && evidence.settledReady === true) return true;
243
243
  if (!preClearAnchor) return false;
244
244
  return !currentPane.includes(preClearAnchor);
245
245
  }
246
246
 
247
247
  export interface ClaudeActivityReadOptions {
248
- /** #1621: the probe's busy assertion predates an interrupt relay sent (claudeProbeVerdict),
248
+ /** the probe's busy assertion predates an interrupt relay sent (claudeProbeVerdict),
249
249
  * so it may describe a turn that was cancelled. */
250
250
  probeBusyStale?: boolean;
251
251
  /**
252
- * #1980 U2 — when the intrinsic session probe is UNAVAILABLE (pre-2.1.119 / unresolvable
252
+ * U2 — when the intrinsic session probe is UNAVAILABLE (pre-2.1.119 / unresolvable
253
253
  * config-home), may a bare input-ready-looking pane be read as `idle`?
254
254
  * true (default) — legacy pane-scrape turn-boundary detection (the monitorless turn-watch):
255
255
  * an input-ready footer ends the turn.
256
256
  * false — the busy BACKSTOP path: an input-ready-looking footer is NOT positive evidence a
257
257
  * turn ended — a long foreground tool renders the exact same prompt box while fully
258
- * alive (#766/#1980) — so with no probe to corroborate it is SILENCE, reported as
258
+ * alive — so with no probe to corroborate it is SILENCE, reported as
259
259
  * `unknown` (the backstop holds the turn) rather than `idle` (which force-cancels it).
260
260
  * This is what stops the `no-busy-observed` false-cancel from ever being reached.
261
261
  */
@@ -272,7 +272,7 @@ export function claudeActivityFromProbeAndPane(
272
272
  if (claudeRegionsAreBusy(regions)) return "busy";
273
273
  const ready = claudePaneLooksReady(regions.footer);
274
274
  if (probe === "busy") {
275
- // Probe-busy is authoritative (#766, #3026). Only after relay's own interrupt (#1621) may a
275
+ // Probe-busy is authoritative. Only after relay's own interrupt may a
276
276
  // positively input-ready footer overrule it; a shelled-out or unreadable pane keeps it busy.
277
277
  return opts.probeBusyStale && ready ? "idle" : "busy";
278
278
  }
@@ -280,14 +280,14 @@ export function claudeActivityFromProbeAndPane(
280
280
  // input-ready pane corroborates it, an ambiguous pane holds as unknown.
281
281
  if (probe === "idle") return ready ? "idle" : "unknown";
282
282
  // probe is undefined/unknown here (unavailable, or Claude `waiting`): the pane-scrape fallback.
283
- // #1980 U2 — an input-ready footer is only trustworthy as turn-end when the caller opts in
283
+ // U2 — an input-ready footer is only trustworthy as turn-end when the caller opts in
284
284
  // (paneFallbackIsIdle, the turn-boundary path). For the busy backstop (opt-out) it is silence,
285
285
  // held as `unknown`, so a live foreground tool the probe cannot see is never force-cancelled.
286
286
  if (ready && (opts.paneFallbackIsIdle ?? true)) return "idle";
287
287
  return "unknown";
288
288
  }
289
289
 
290
- // #286: detect the subscription usage-limit modal in the pane and turn it into the
290
+ // detect the subscription usage-limit modal in the pane and turn it into the
291
291
  // provider-neutral rate-limit hold (idle + blocked) that the relay's resume sweep lifts
292
292
  // at reset. Unlike the pane model-rejection observation below, it remains a legacy status path.
293
293
  export function claudeRateLimitStatus(text: string, sessionName?: string): ProviderStatusUpdate | null {
@@ -305,7 +305,7 @@ export function claudeRateLimitStatus(text: string, sessionName?: string): Provi
305
305
  };
306
306
  }
307
307
 
308
- // #769 — detect the API connection-retry banner in the live terminal footer. Three stable
308
+ // detect the API connection-retry banner in the live terminal footer. Three stable
309
309
  // anchors from the banner text: "Unable to connect to API", "Retrying in <N>s",
310
310
  // "attempt <N>/<M>". Gated on the LAST N lines so agent output that merely QUOTES these
311
311
  // strings (e.g. quoting this comment, or a discussion of retry logic) doesn't false-positive —
@@ -314,7 +314,7 @@ export function claudeRateLimitStatus(text: string, sessionName?: string): Provi
314
314
  // fast fix once the new footer wording is known, and one with no correctness consequence now
315
315
  // that this is advisory.
316
316
  //
317
- // #1651 — this returns a DIAGNOSTIC, not a status. It used to return
317
+ // this returns a DIAGNOSTIC, not a status. It used to return
318
318
  // `status: "idle"` + `clear: ["subagent"]` + a blocked `conn_retry` providerState. The runner
319
319
  // treats an idle provider-turn as the turn ENDING (runner-core setProviderStatus), which
320
320
  // clears the work record and calls completeObservedTaskClaims(): retry chrome rendered in a
@@ -362,14 +362,14 @@ export function claudeConnectionRetryClearedObservation(sessionName?: string, no
362
362
  };
363
363
  }
364
364
 
365
- // #1651 — this returns a DIAGNOSTIC, not a status. It used to return `status: "error"` +
365
+ // this returns a DIAGNOSTIC, not a status. It used to return `status: "error"` +
366
366
  // `clear: ["provider-turn", "subagent"]` + a TERMINAL failed provider state + suppressed
367
367
  // restart from a single pane match — reproduced live: the harmless sentence "The test fixture
368
368
  // says: not an available model" triggered every one of those, from a pane, where the matched
369
369
  // text may simply be an agent quoting a fixture, a code comment, or this very file.
370
370
  //
371
371
  // A pane match is never terminal. Only a correlated StopFailure whose native error type
372
- // explicitly denotes model-not-found/unavailable (not yet wired — see #1651's Direction
372
+ // explicitly denotes model-not-found/unavailable (not yet wired — the Direction
373
373
  // section) may end a turn this way. This detector only notices.
374
374
  //
375
375
  // Scoped to the trailing modal/composer structural block: the same positional technique
@@ -15,22 +15,22 @@ export function claudeSubagentPolicy(config: Pick<RunnerSpawnConfig, "harnessSub
15
15
 
16
16
  export function claudeSubagentEnv(config: Pick<RunnerSpawnConfig, "harnessSubagents" | "agentProfile">): Record<string, string> {
17
17
  const policy = claudeSubagentPolicy(config);
18
- const enabled = writeChildEnv({}, { AGENT_RELAY_HARNESS_SUBAGENTS: harnessSubagentsEnabled(config) ? "1" : "0" });
18
+ const enabled = writeChildEnv({}, { HUMWICK_HARNESS_SUBAGENTS: harnessSubagentsEnabled(config) ? "1" : "0" }, []);
19
19
  return policy ? writeChildEnv({}, { ...enabled,
20
20
  CLAUDE_CODE_SUBAGENT_MODEL: policy.model,
21
21
  CLAUDE_CODE_SUBAGENT_MODEL_FORCE: "1",
22
22
  CLAUDE_CODE_FORK_SUBAGENT: "0",
23
23
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "0",
24
24
  CLAUDE_CODE_DISABLE_WORKFLOWS: "1",
25
- AGENT_RELAY_CLAUDE_SUBAGENT_CAP: "1",
26
- }) : writeChildEnv({}, { ...enabled, AGENT_RELAY_CLAUDE_SUBAGENT_CAP: "0" });
25
+ HUMWICK_CLAUDE_SUBAGENT_CAP: "1",
26
+ }, []) : writeChildEnv({}, { ...enabled, HUMWICK_CLAUDE_SUBAGENT_CAP: "0" }, []);
27
27
  }
28
28
 
29
29
  export function claudeSubagentSettings(config: Pick<RunnerSpawnConfig, "harnessSubagents" | "agentProfile" | "model">): Record<string, unknown> {
30
30
  const policy = claudeSubagentPolicy(config);
31
31
  if (!policy) return harnessSubagentsEnabled(config) ? {} : { disableAllHooks: false };
32
32
  const mainFamily = claudeModelFamily(config.model);
33
- // Model settings apply to both parent and children, including canonicalized aliases (#3424).
33
+ // Model settings apply to both parent and children, including canonicalized aliases.
34
34
  const modelSettings = mainFamily && mainFamily !== policy.model
35
35
  ? { [policy.model]: { maxEffortLevel: policy.effort, autoCompactWindow: policy.contextTokens } } : undefined;
36
36
  return { disableAllHooks: false, ...(modelSettings ? { modelSettings } : {}), autoCompactEnabled: true };
@@ -83,11 +83,11 @@ export function configureClaudeSubagentLaunch(config: Pick<RunnerSpawnConfig, "h
83
83
  if (!sources.includes("user")) throw new Error("Capped Claude profiles require user settings to load mandatory hooks");
84
84
  const settings = extractCallerSettings([launch.args], launch.cwd)?.settings ?? {};
85
85
  const callerEnv = settings.env as Record<string, unknown> | undefined;
86
- const protectedEnv = [...Object.keys(claudeSubagentEnv(config)), "CLAUDE_CODE_SIMPLE", "CLAUDE_CODE_SAFE_MODE", "CLAUDE_CODE_AUTO_COMPACT_WINDOW", "DISABLE_AUTO_COMPACT", "AGENT_RELAY_CLAUDE_SKILL_SOURCES"];
86
+ const protectedEnv = [...Object.keys(claudeSubagentEnv(config)), "CLAUDE_CODE_SIMPLE", "CLAUDE_CODE_SAFE_MODE", "CLAUDE_CODE_AUTO_COMPACT_WINDOW", "DISABLE_AUTO_COMPACT", "HUMWICK_CLAUDE_SKILL_SOURCES"];
87
87
  if (callerEnv && protectedEnv.some((key) => key in callerEnv)) throw new Error("Capped Claude profiles cannot override child policy through --settings env");
88
- writeChildEnv(launch.env, { AGENT_RELAY_CLAUDE_SKILL_SOURCES: JSON.stringify({ cwd: launch.cwd,
88
+ writeChildEnv(launch.env, { HUMWICK_CLAUDE_SKILL_SOURCES: JSON.stringify({ cwd: launch.cwd,
89
89
  sources,
90
90
  directories: values("--add-dir", true), plugins: values("--plugin-dir", true),
91
91
  settings: { enabledPlugins: settings.enabledPlugins, skillOverrides: settings.skillOverrides },
92
- }) });
92
+ }) }, []);
93
93
  }
@@ -1,7 +1,7 @@
1
1
  // A tmux command exiting 0 only proves that tmux accepted a key. Claude can still
2
2
  // have the pasted prompt in its composer (for example when Enter lands in copy mode).
3
3
  // Claude's UserPromptSubmit hook proves acceptance; the composer only decides whether
4
- // another Enter is safe, and confirms alone only on hookless launches (#2664).
4
+ // another Enter is safe, and confirms alone only on hookless launches.
5
5
  import { ProviderSubmitFailure } from "../provider-submit-failure";
6
6
  import type { ClaudePromptHookExpectation } from "./claude-prompt-hook";
7
7
  import { claudeHookPromptMatches } from "./claude-prompt-hook";
@@ -11,7 +11,7 @@ import { IncrementalClaudeTranscriptTail, type IncrementalTranscriptPollResult }
11
11
 
12
12
  export const CLAUDE_SUBMIT_MAX_ENTERS = 3;
13
13
  export const CLAUDE_SUBMIT_HOOK_WAIT_MS = 3_000;
14
- /** Extra hook wait once our Enter left the composer: a slow hook is still proof (#3081). */
14
+ /** Extra hook wait once our Enter left the composer: a slow hook is still proof. */
15
15
  export const CLAUDE_SUBMIT_LATE_HOOK_WAIT_MS = 10_000;
16
16
  const PASTE_RENDER_TIMEOUT_MS = 3_000;
17
17
  const SUBMIT_SETTLE_MS = 1_200;
@@ -83,7 +83,7 @@ export class ClaudeSubmitTranscriptProbe {
83
83
  }
84
84
 
85
85
  async confirms(text: string, offeredAt: number, imagePaths: readonly string[] = []): Promise<boolean> {
86
- // A consumption check must leave its read available to submit recovery (#2691).
86
+ // A consumption check must leave its read available to submit recovery.
87
87
  const result = this.recoveryPoll = (await this.poll()) ?? undefined;
88
88
  return result?.userPrompts.some((prompt, i) =>
89
89
  (result.userPromptOccurredAts[i] ?? -Infinity) >= offeredAt && claudeHookPromptMatches(prompt, text, imagePaths)) === true;
@@ -96,7 +96,7 @@ export class ClaudeSubmitFence {
96
96
 
97
97
  async run(session: string, submit: (markPasteStarted: () => void) => Promise<unknown>, recovery: (turnStarted: boolean) => Promise<boolean>): Promise<void> {
98
98
  // Probe here too: a dashboard prompt reaches the fence directly, and the turn that would
99
- // otherwise trigger recovery may have started before the failure was recorded (#2664).
99
+ // otherwise trigger recovery may have started before the failure was recorded.
100
100
  if (this.uncertain.has(session) && !await this.recover(session)) {
101
101
  throw new ProviderSubmitFailure("Claude prompt submit remains unconfirmed from an earlier attempt; inspect the TUI before retrying", false);
102
102
  }
@@ -140,7 +140,7 @@ export interface ClaudeComposerReadDeps {
140
140
  now?: () => number;
141
141
  }
142
142
 
143
- // Hookless (degraded) composer acceptance (#2664): empty only after a readable empty composer
143
+ // Hookless (degraded) composer acceptance: empty only after a readable empty composer
144
144
  // has lasted SUBMIT_SETTLE_MS from its FIRST empty frame, with no input re-shown meanwhile.
145
145
  // A torn frame neither confirms nor withdraws, but the window must end on a readable empty
146
146
  // frame; an unsettled or unreadable end is "unreadable", so no key goes into a dialog.
@@ -174,7 +174,7 @@ async function readComposer(deps: ClaudeComposerReadDeps, timeoutMs: number): Pr
174
174
 
175
175
  // Claude renders composer placeholders ("Press up to edit queued messages" after a
176
176
  // mid-turn submit, "Try …") dim; typed and pasted input never is. Reading a styled
177
- // capture keeps a placeholder from looking like an unsubmitted prompt (#2664).
177
+ // capture keeps a placeholder from looking like an unsubmitted prompt.
178
178
  const TERMINAL_ESCAPE = /\x1b\[([0-9;:]*)m|\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
179
179
 
180
180
  function sgrDim(params: string, dim: boolean): boolean {
@@ -2,7 +2,7 @@ import { relayEnv, writeChildEnv } from "@humwick/sdk/env";
2
2
  import { envPipeSource, prepareEnvPipe, providerSecretEnvKeys } from "@humwick/sdk/child-env";
3
3
  import { PROVIDER_SCRATCH_ENV_KEYS } from "@humwick/sdk/provider-scratch";
4
4
  // Claude tmux transport plumbing (extracted from ./claude for the file-size
5
- // ratchet, #300/#291): session/socket naming, pane capture, input-ready gating,
5
+ // ratchet): session/socket naming, pane capture, input-ready gating,
6
6
  // paste/submit injection, launcher-script materialization, and env-key selection.
7
7
  // The adapter (./claude) keeps the lifecycle decisions; this module owns the
8
8
  // mechanical tmux interactions they ride on. Public symbols are re-exported from
@@ -43,7 +43,7 @@ export function seedClaudeInitialPrompt(args: string[], prompt: string, options:
43
43
  if (!options.headless || options.lifecycle !== "transient") {
44
44
  throw new Error("oversized Claude initial prompt requires a transient headless launch; refusing a racy or role-changing fallback");
45
45
  }
46
- if (!options.scratchDir) throw new Error("oversized Claude initial prompt requires AGENT_RELAY_SCRATCH_DIR");
46
+ if (!options.scratchDir) throw new Error("oversized Claude initial prompt requires HUMWICK_SCRATCH_DIR");
47
47
  const promptFile = join(options.scratchDir, "claude-initial-assignment.md");
48
48
  const exitCodeFile = join(options.scratchDir, "claude-initial-exit-code");
49
49
  args.push("-p", "--input-format", "text");
@@ -60,7 +60,7 @@ export function tmuxSessionName(prefix: string, instanceId: string, label?: stri
60
60
  // separates the session/window/pane portions of a target). All later `-t` lookups
61
61
  // must use the rendered name or `has-session` misses the session tmux just created.
62
62
  // Delegates to the shared SDK renderer so the runner and orchestrator (which must match
63
- // the rendered name when resolving a shutdown target, #1746 RC-1) can never diverge.
63
+ // the rendered name when resolving a shutdown target, RC-1) can never diverge.
64
64
  export function tmuxSessionTargetName(sessionName: string): string {
65
65
  return renderTmuxSessionTarget(sessionName);
66
66
  }
@@ -76,7 +76,7 @@ export function tmuxSocketName(sessionName: string): string {
76
76
  return socketName;
77
77
  }
78
78
 
79
- // Pre-#1353 socket selector, used only for one-way cleanup when a runner first
79
+ // Previous socket selector, used only for one-way cleanup when a runner first
80
80
  // starts after upgrading. Do not assert its path length: an overlong legacy
81
81
  // name is exactly what the new fixed-size selector is designed to escape.
82
82
  export function legacyTmuxSocketName(sessionName: string): string {
@@ -88,7 +88,7 @@ export function createOrReuseTmuxSession(sessionName: string, socketName: string
88
88
  spawnSyncBounded(tmuxCommand(socketName, "kill-session", "-t", targetSessionName), {
89
89
  stdin: "ignore", stdout: "ignore", stderr: "ignore",
90
90
  });
91
- // Upgrade cleanup: a pre-#1353 runner used the readable session name as its
91
+ // Upgrade cleanup: a previous runner used the readable session name as its
92
92
  // selector. Best-effort removal prevents a legacy pane surviving beside its
93
93
  // replacement; an overlong legacy selector can fail here harmlessly.
94
94
  spawnSyncBounded(tmuxCommand(legacyTmuxSocketName(sessionName), "kill-session", "-t", targetSessionName), {
@@ -105,7 +105,7 @@ export function createOrReuseTmuxSession(sessionName: string, socketName: string
105
105
  throw new Error(`tmux session creation failed: ${stderr || (result.signalCode ? `signal ${result.signalCode}` : `exit code ${result.exitCode}`)}`);
106
106
  }
107
107
  if (result.exitCode === 0) promptSubmitFence.clear(promptSubmitSessionKey(targetSessionName, socketName));
108
- // #1514 — stamp the owning orchestrator's state-home id (passed to this runner via
108
+ // stamp the owning orchestrator's state-home id (passed to this runner via
109
109
  // env) onto the session so the orchestrator's wedged-session reaper reaps ONLY the
110
110
  // sessions it positively owns, never a co-tenant orchestrator's on the same host.
111
111
  setTmuxSessionOwner(targetSessionName, tmuxOwnerIdFromEnv(), socketName);
@@ -135,7 +135,7 @@ export function tmuxSessionCreationFailureIsReusable(
135
135
  && hasSession(tmuxSessionTargetName(sessionName), socketName);
136
136
  }
137
137
 
138
- // #1514 — stamp the owning orchestrator's state-home id onto the tmux session as a
138
+ // stamp the owning orchestrator's state-home id onto the tmux session as a
139
139
  // user-option. The orchestrator's wedged-session reaper reads it back and reaps ONLY
140
140
  // sessions whose owner id matches its own, so it can never reap a session belonging
141
141
  // to a different orchestrator that happens to share the UID + tmux prefix on this
@@ -166,7 +166,7 @@ export async function confirmTmuxSessionGone(
166
166
  sessionName: string,
167
167
  socketName?: string,
168
168
  delayMs = 250,
169
- // #1838 row 12 — off the event loop via the tri-state `tmuxSessionPresence` (#1838 review
169
+ // row 12 — off the event loop via the tri-state `tmuxSessionPresence` (review
170
170
  // HIGH-2). Death is confirmed ONLY when BOTH the immediate and the delayed read are the
171
171
  // positive `absent` (tmux exited 1); a `present` OR an `unreadable` (timeout/abort on a
172
172
  // wedged-but-live server) at either point means NOT gone, so a slow read can never be
@@ -195,15 +195,15 @@ export function captureTmuxPane(sessionName: string, socketName?: string): strin
195
195
  return result.stdout.toString();
196
196
  }
197
197
 
198
- // #1514's exit-dialog confirmation used to live here as
198
+ // exit-dialog confirmation used to live here as
199
199
  // `confirmClaudeExitDialogIfPresent(sessionName, socketName)` — an Enter writer that
200
- // any caller holding a session name could invoke. #1647 removed it: taking a session
200
+ // any caller holding a session name could invoke. Removed it: taking a session
201
201
  // name meant nothing in the type system stopped a pane detector from calling it, and
202
202
  // the production kill was exactly that Enter answering an "Exit anyway" modal on a
203
203
  // live pane. It now lives in ./claude-exit, where both it and the `/exit` writer take
204
204
  // an unforgeable one-shot ClaudeExitGrant instead.
205
205
 
206
- // #1838 review BLOCKER + round-3 MED — this readiness poller runs on the hot delivery path
206
+ // review BLOCKER + round-3 MED — this readiness poller runs on the hot delivery path
207
207
  // (every deliver / deliverInitialPrompt / monitor self-resume settle) and used to call the
208
208
  // SYNCHRONOUS tmuxHasSession + captureTmuxPane every 200ms; a single wedged tmux call froze the
209
209
  // whole runner event loop for the full 10s deadline. Both reads now go OFF the loop and use the
@@ -243,11 +243,11 @@ export async function waitForClaudeInputReady(sessionName: string, timeoutMs = C
243
243
 
244
244
  export const CLAUDE_CLEAR_COMMIT_POLL_MS = 200;
245
245
 
246
- /** #2850 — consecutive idle+ready polls (~1s) a vacuous clear needs when the probe can't attest. */
246
+ /** consecutive idle+ready polls (~1s) a vacuous clear needs when the probe can't attest. */
247
247
  export const CLAUDE_VACUOUS_CLEAR_READY_POLLS = 5;
248
248
 
249
249
  /**
250
- * #2261 — the capture scope the clear-commit barrier reads, pinned as a named export so the
250
+ * the capture scope the clear-commit barrier reads, pinned as a named export so the
251
251
  * invariant is assertable. It MUST be the visible-screen capture: `/clear` repaints only the
252
252
  * visible screen (no `ESC[3J`), so a scrollback capture keeps returning the wiped conversation —
253
253
  * and with it the anchor — for the entire timeout, making a committed clear unconfirmable.
@@ -260,23 +260,23 @@ export interface ClaudeClearCommitDeps {
260
260
  capture?: (sessionName: string, socketName?: string, opts?: TmuxReadOpts) => string | Promise<string>;
261
261
  presence?: (sessionName: string, socketName?: string, opts?: TmuxReadOpts) => TmuxSessionPresence | Promise<TmuxSessionPresence>;
262
262
  sleep?: (ms: number) => Promise<unknown>;
263
- /** #2261 — the probe reading taken immediately BEFORE the `/clear` submit (null = unavailable). */
263
+ /** the probe reading taken immediately BEFORE the `/clear` submit (null = unavailable). */
264
264
  preClearSession?: ClaudeClearSessionReading | null;
265
- /** #2261 — re-read it each poll, pid-pinned by the caller. Absent → anchor-only (legacy). */
265
+ /** re-read it each poll, pid-pinned by the caller. Absent → anchor-only (legacy). */
266
266
  readSession?: () => Promise<ClaudeClearSessionReading | null>;
267
- /** #2849 — true only when `preClearPane` is a successful capture (not the "" fallback
267
+ /** true only when `preClearPane` is a successful capture (not the "" fallback
268
268
  * left by a throw). Lets a provably-empty pre-clear pane confirm via idle+ready. */
269
269
  preClearCaptured?: boolean;
270
270
  }
271
271
 
272
- // #1588 — CLEAR-AWARE settle barrier for the self-resume clear+inject coupling. Unlike
272
+ // CLEAR-AWARE settle barrier for the self-resume clear+inject coupling. Unlike
273
273
  // waitForClaudeInputReady (which returns on the first persistent-footer match — present BOTH
274
274
  // before and after `/clear`, the F2 race), this requires POSITIVE evidence the `/clear` actually
275
275
  // committed: it snapshots a distinctive pre-clear conversation anchor and only reports success
276
276
  // once the pane is idle, input-ready, AND that anchor is gone (the loaded conversation was wiped).
277
277
  // Returns a BOOLEAN rather than throwing: `true` = clear positively confirmed committed; `false` =
278
278
  // timed out / session vanished / capture failed / no anchor derivable (uncertain). The caller MUST
279
- // treat `false` as "not confirmed" and report continuationDelivered:false so the #1023 backstop
279
+ // treat `false` as "not confirmed" and report continuationDelivered:false so the backstop
280
280
  // re-injects — never as a wipe. A settle timeout is therefore surfaced, not swallowed.
281
281
  export async function waitForClaudeClearCommitted(
282
282
  sessionName: string,
@@ -285,12 +285,12 @@ export async function waitForClaudeClearCommitted(
285
285
  socketName?: string,
286
286
  deps: ClaudeClearCommitDeps = {},
287
287
  ): Promise<boolean> {
288
- // #1838 review — same off-loop + tri-state + ABSOLUTE-deadline treatment as
288
+ // review — same off-loop + tri-state + ABSOLUTE-deadline treatment as
289
289
  // waitForClaudeInputReady (this poller also ran sync captureTmuxPane on the clear path, and had
290
290
  // the same between-calls-only deadline, round-3 MED). A positive `absent` returns false (clear
291
- // not confirmed → the #1023 backstop re-injects, the safe outcome); an `unreadable` read or a
291
+ // not confirmed → the backstop re-injects, the safe outcome); an `unreadable` read or a
292
292
  // transient capture failure retries until the deadline instead of blocking or giving up early.
293
- // #2261 — the VISIBLE screen, never the `-S -80` scrollback window: `/clear` repaints only the
293
+ // the VISIBLE screen, never the `-S -80` scrollback window: `/clear` repaints only the
294
294
  // visible screen, so a scrollback capture keeps returning the wiped conversation (and with it the
295
295
  // anchor) for the whole timeout. That single mismatch made a committed clear unconfirmable on any
296
296
  // pane with history — proven live: the clear-inject at 22:15:10.185Z minted its new Claude
@@ -302,18 +302,18 @@ export async function waitForClaudeClearCommitted(
302
302
  const sleep = deps.sleep ?? Bun.sleep;
303
303
  const anchor = claudeConversationAnchor(preClearPane);
304
304
  const { preClearSession, readSession } = deps;
305
- // #2849 — a positively-captured pane with no anchor held no conversation, so the
305
+ // a positively-captured pane with no anchor held no conversation, so the
306
306
  // clear is vacuous. A failed capture ("") also yields no anchor but must stay
307
307
  // fail-closed (preClearCaptured false). Confirmation still needs the settle below.
308
308
  const preClearEmpty = deps.preClearCaptured === true && anchor === undefined;
309
- // #2849r1/#2850 — with no probe verdict a ready pane may predate the commit (#1588),
309
+ // with no probe verdict a ready pane may predate the commit,
310
310
  // so a vacuous clear needs a CLAUDE_VACUOUS_CLEAR_READY_POLLS streak. A failed capture,
311
311
  // a busy/non-ready pane or an `unchanged` probe reading resets it.
312
312
  let readyStreak = 0;
313
- // #2261 — the new session is minted ~112ms BEFORE the TUI finishes repainting (measured on the
313
+ // the new session is minted ~112ms BEFORE the TUI finishes repainting (measured on the
314
314
  // live repro), and inside that window an un-cleared pane still reads non-busy + input-ready. So
315
315
  // a rotation only counts once it has been observed on TWO CONSECUTIVE polls, which lands the
316
- // confirmation after the repaint rather than inside the #1588 injection window. One poll
316
+ // confirmation after the repaint rather than inside the injection window. One poll
317
317
  // interval (200ms) out of a 10s budget.
318
318
  let rotationSeen = false;
319
319
  const deadline = Date.now() + timeoutMs;
@@ -331,7 +331,7 @@ export async function waitForClaudeClearCommitted(
331
331
  pane = await capture(sessionName, socketName, readOpts);
332
332
  } catch {
333
333
  readyStreak = 0;
334
- // #1838 round-4 MED — clamp the poll sleep to the remaining budget so a wait started
334
+ // round-4 MED — clamp the poll sleep to the remaining budget so a wait started
335
335
  // with < CLAUDE_CLEAR_COMMIT_POLL_MS left can't overshoot the absolute deadline.
336
336
  await sleep(Math.min(CLAUDE_CLEAR_COMMIT_POLL_MS, Math.max(0, deadline - Date.now())));
337
337
  continue;
@@ -344,7 +344,7 @@ export async function waitForClaudeClearCommitted(
344
344
  readyStreak = verdict !== "unchanged" && !claudePaneIsBusy(pane) && claudePaneLooksReady(pane) ? readyStreak + 1 : 0;
345
345
  const settledReady = readyStreak >= CLAUDE_VACUOUS_CLEAR_READY_POLLS;
346
346
  if (claudeClearCommitted(anchor, pane, { session: settled, preClearEmpty, settledReady })) return true;
347
- // #1838 round-4 MED — same clamp on the steady-state poll.
347
+ // round-4 MED — same clamp on the steady-state poll.
348
348
  await sleep(Math.min(CLAUDE_CLEAR_COMMIT_POLL_MS, Math.max(0, deadline - Date.now())));
349
349
  }
350
350
  return false;
@@ -353,7 +353,7 @@ export async function waitForClaudeClearCommitted(
353
353
  }
354
354
  }
355
355
 
356
- // #1838 row 16 — the provider input transport (up to 50 spawns per delivered payload).
356
+ // row 16 — the provider input transport (up to 50 spawns per delivered payload).
357
357
  // Wave 2a moves every tmux subprocess here OFF the event loop via the offload runner, and the
358
358
  // payload rides on STDIN (`load-buffer -`) rather than in argv.
359
359
  //
@@ -367,7 +367,7 @@ export async function waitForClaudeClearCommitted(
367
367
  // User prompts opt into visible-composer confirmation. Context commands retain their own
368
368
  // command-specific commit barriers; the tmux transport still sends those once.
369
369
  async function deleteTmuxBuffer(bufferName: string, socketName?: string): Promise<void> {
370
- // Best-effort: a failed delete must never fail (or mask) the delivery outcome. #1838 review
370
+ // Best-effort: a failed delete must never fail (or mask) the delivery outcome. Review
371
371
  // LOW — always runs, so a buffer loaded-but-not-cleaned (e.g. a paste timeout) can't leak.
372
372
  await runTmuxInput("tmux-delete-buffer", tmuxCommand(socketName, "delete-buffer", "-b", bufferName)).catch(() => {});
373
373
  }
@@ -387,7 +387,7 @@ export function releaseClaudeSubmitFence(sessionName: string, socketName?: strin
387
387
  }
388
388
 
389
389
  export async function submitTextToTmux(sessionName: string, text: string, socketName?: string, options: { confirmSubmit?: boolean; transcriptPath?: string; resolveTranscriptPath?: () => Promise<string | undefined>; promptHooks?: ClaudePromptHookLedger; imagePaths?: readonly string[]; inputObserver?: ProviderInputObserver } = {}): Promise<void> {
390
- const bufferName = `agent-relay-${crypto.randomUUID()}`;
390
+ const bufferName = `humwick-${crypto.randomUUID()}`;
391
391
  const paste = async () => {
392
392
  try {
393
393
  // Load via stdin so large prompts never hit the platform argv limit.
@@ -451,7 +451,7 @@ export async function submitTextToTmux(sessionName: string, text: string, socket
451
451
  return;
452
452
  }
453
453
  // Submit the pasted input with a SINGLE send-keys C-m: one send, throw-on-failure, no retry —
454
- // delivery-acknowledgment semantics identical to main (see the header note + #1844).
454
+ // delivery-acknowledgment semantics identical to main (see the header note +).
455
455
  await paste();
456
456
  await Bun.sleep(CLAUDE_TMUX_SUBMIT_DELAY_MS);
457
457
  options.inputObserver?.offered(Date.now(), false);
@@ -461,7 +461,7 @@ export async function submitTextToTmux(sessionName: string, text: string, socket
461
461
  // The script keeps the provider command out of tmux argv. Environment values
462
462
  // arrive through a private FIFO; only the recovery identity is written here.
463
463
  // `gateFifo`, when given, is read from (blocking) before the real command execs
464
- // (#1317): the caller creates the FIFO before the tmux session exists, attaches
464
+ // the caller creates the FIFO before the tmux session exists, attaches
465
465
  // `pipe-pane` once the pane is up, then writes to the FIFO to release the gate.
466
466
  // That guarantees no provider output can be produced before pipe-pane is capturing
467
467
  // it — a process that dies in milliseconds previously produced a 0-byte log because
@@ -473,10 +473,10 @@ export function writeLauncherScript(sessionName: string, env: Record<string, str
473
473
  const scriptPath = launcherScriptPathForSession(sessionName);
474
474
  const launcherDir = dirname(scriptPath); mkdirSync(launcherDir, { recursive: true, mode: 0o700 }); chmodSync(launcherDir, 0o700);
475
475
  const exports = Object.entries(env)
476
- .filter(([key]) => key === "AGENT_RELAY_ID")
476
+ .filter(([key]) => key === "HUMWICK_ID")
477
477
  .map(([key, value]) => `export ${shellEnvKey(key)}=${shellQuote(value)}`)
478
478
  .join("\n");
479
- // #2513 — exports only ADD to the tmux server's inherited environment; a key that must
479
+ // exports only ADD to the tmux server's inherited environment; a key that must
480
480
  // not reach the pane (an ambient credential a second-subscription launch must shed)
481
481
  // needs an explicit unset, absence from `env` is not enough.
482
482
  const unsets = unsetKeys.map((key) => `unset ${shellEnvKey(key)}`).join("\n");
@@ -484,7 +484,7 @@ export function writeLauncherScript(sessionName: string, env: Record<string, str
484
484
  const stdin = stdinFile ? `exec < ${shellQuote(stdinFile)}\n` : "";
485
485
  const codeFile = exitCodeFile ?? `${scriptPath}.exit-code`;
486
486
  // Interactive Claude must own pane_pid for its session probe. execfail lets a
487
- // failed exec return to EXIT; print mode keeps a parent to record completion (#3830).
487
+ // failed exec return to EXIT; print mode keeps a parent to record completion.
488
488
  const recordExit = `trap ${shellQuote(`exit_code=$?; printf '%s' "$exit_code" > ${shellQuote(codeFile)}; exit "$exit_code"`)} EXIT`;
489
489
  const command = exitCodeFile ? `( ${shellCmd} )` : `exec ${shellCmd}`;
490
490
  writeFileSync(scriptPath, `#!/usr/bin/env bash\numask 077\nshopt -s execfail\nrm -f -- ${shellQuote(codeFile)} ${shellQuote(`${codeFile}.stderr`)}\n${recordExit}\nexec 2> >(tee ${shellQuote(`${codeFile}.stderr`)} >&2)\n${exports ? `${exports}\n` : ""}${Object.keys(env).length ? `${envPipeSource()}\n` : ""}${unsets ? `${unsets}\n` : ""}rm -f -- "$0"\n${gate}${stdin}${command}\n`, { mode: 0o700 });
@@ -517,7 +517,7 @@ export async function releaseLauncherGate(gateFifo: string, timeoutMs = 5_000):
517
517
  try { proc.kill(); } catch {}
518
518
  }
519
519
  } catch {
520
- // best-effort — a stuck launcher gate is no worse than the pre-#1317 race.
520
+ // best-effort — a stuck launcher gate is no worse than the previous race.
521
521
  } finally {
522
522
  try { rmSync(gateFifo, { force: true }); } catch {}
523
523
  }
@@ -529,14 +529,14 @@ function shellEnvKey(key: string): string {
529
529
  }
530
530
 
531
531
  export function tmuxEnvKeys(env: Record<string, string>, providerEnv: Record<string, string>, provider = "claude"): string[] {
532
- // #1222 — PATH is always carried through, not just AGENT_RELAY_/CLAUDE_ vars, so the
532
+ // PATH is always carried through, not just HUMWICK_/CLAUDE_ vars, so the
533
533
  // launcher script's exported session PATH (built by buildSpawnArgs) reaches
534
534
  // the tmux pane instead of whatever ambient PATH the tmux server happened to inherit.
535
- // #3084 — likewise the agent's private TMPDIR/TMP/TEMP: without them the pane got the tmux
535
+ // likewise the agent's private TMPDIR/TMP/TEMP: without them the pane got the tmux
536
536
  // server's host-shared TMPDIR while only CLAUDE_CODE_TMPDIR was per-agent.
537
537
  const keys = new Set<string>(["PATH", ...PROVIDER_SCRATCH_ENV_KEYS, ...providerSecretEnvKeys(provider)]);
538
538
  for (const key of Object.keys(env)) {
539
- if ((key.startsWith("AGENT_RELAY_") || key.startsWith("HUMWICK_")) || key.startsWith("CLAUDE_")) {
539
+ if (key.startsWith("HUMWICK_") || key.startsWith("CLAUDE_")) {
540
540
  keys.add(key);
541
541
  }
542
542
  }
@@ -558,12 +558,12 @@ export function buildClaudeTmuxArgs(config: RunnerSpawnConfig, spawnArgs: SpawnA
558
558
 
559
559
  const envKeys = tmuxEnvKeys(spawnArgs.env, config.providerConfig.env, provider);
560
560
  const env: Record<string, string> = {};
561
- for (const key of envKeys) if (relayEnv(spawnArgs.env, key) !== undefined) writeChildEnv(env, { [key]: relayEnv(spawnArgs.env, key)! });
561
+ for (const key of envKeys) if (relayEnv(spawnArgs.env, key) !== undefined) writeChildEnv(env, { [key]: relayEnv(spawnArgs.env, key)! }, []);
562
562
 
563
563
  // Only gate the launch behind pipe-pane attachment when there's a log file to
564
- // protect (#1317) — no point serializing dev/interactive spawns that never
565
- // read AGENT_RELAY_LOG_FILE.
566
- const gateFifo = relayEnv(spawnArgs.env, "AGENT_RELAY_LOG_FILE") ? launcherGatePathForSession(sessionName) : undefined;
564
+ // protect — no point serializing dev/interactive spawns that never
565
+ // read HUMWICK_LOG_FILE.
566
+ const gateFifo = relayEnv(spawnArgs.env, "HUMWICK_LOG_FILE") ? launcherGatePathForSession(sessionName) : undefined;
567
567
 
568
568
  // tmux new-session has a hard command-length limit that includes every argv item,
569
569
  // including `-e KEY=value`. The script holds the command; its private FIFO