@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
@@ -27,9 +27,9 @@ import { OPENCODE_SOFT_STEER_POST_TIMEOUT_MS, OPENCODE_SOFT_STEER_RECONCILE_TIME
27
27
  export { openCodeContextThreshold, openCodeContextWindowTokens, openCodeModelDescriptor, openCodeModelRecord, openCodeQualifiedModel } from "./opencode-provider";
28
28
 
29
29
  /**
30
- * #2459 — the non-secret attachment generation for a web-terminal guest: a short one-way
30
+ * the non-secret attachment generation for a web-terminal guest: a short one-way
31
31
  * digest of the ACTIVE OpenCode session id. It changes exactly when the session rotates
32
- * (clearContext / the #2397 retry re-roll) and lets the orchestrator detect a stale guest
32
+ * (clearContext / the retry re-roll) and lets the orchestrator detect a stale guest
33
33
  * TUI — attached to a dead conversation — WITHOUT ever seeing the session id itself or any
34
34
  * credential (a digest of a 122-bit-random uuid is not reversible in practice). Rides the
35
35
  * attach spec, the runner's GET /terminal/generation, and the orchestrator's guest registry;
@@ -47,7 +47,7 @@ export interface OpenCodeServerEvent {
47
47
  type OpenCodeFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
48
48
 
49
49
  /**
50
- * #2428 — Bun extends its fetch init with a per-request `timeout` field that the standard
50
+ * Bun extends its fetch init with a per-request `timeout` field that the standard
51
51
  * RequestInit type does not declare (`number | false`; `false` disables Bun's native HTTP idle
52
52
  * watchdog for that one request). Neither @types/node's nor bun-types 1.3.14's ambient
53
53
  * RequestInit/BunFetchRequestInit declares the field, so it is declared here rather than reached
@@ -60,7 +60,7 @@ type OpenCodeFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<R
60
60
  export type OpenCodeRequestInit = RequestInit & { timeout?: number | false };
61
61
 
62
62
  /**
63
- * #2425 — whole-turn budget for `OpenCodeClient.sendMessage`'s synchronous POST (see its doc).
63
+ * whole-turn budget for `OpenCodeClient.sendMessage`'s synchronous POST (see its doc).
64
64
  * Env-tunable so an operator can trade a tighter bound for faster abandonment-detection on a
65
65
  * flaky network, or a longer one for a model/workload that legitimately runs longer than an hour.
66
66
  * The default is intentionally large rather than unbounded — `AbortSignal.timeout` requires a
@@ -136,14 +136,14 @@ export class OpenCodeClient {
136
136
  }
137
137
 
138
138
  /**
139
- * #2389 — this POST is the SYNCHRONOUS endpoint: it returns only once the assistant message is
139
+ * this POST is the SYNCHRONOUS endpoint: it returns only once the assistant message is
140
140
  * complete, so its status and duration describe the whole turn, not just the hand-off.
141
141
  *
142
142
  * It used to be entirely silent on success — no status, no timing, no access log — so when a
143
143
  * spawned agent idled forever there was no way to establish whether its first prompt had even
144
144
  * been accepted; that took reading the provider's own retained SQLite. Log it.
145
145
  *
146
- * #2425 — this call used to carry NO abort signal at all, yet a live, still-running turn was
146
+ * this call used to carry NO abort signal at all, yet a live, still-running turn was
147
147
  * observed being cut off client-side at roughly 294s regardless. Combined with the old PERMANENT
148
148
  * idle suppression, that stranded the agent's status at "idle" forever even though the server
149
149
  * kept working — see the `suppressProviderIdle` field doc on `OpenCodeAdapter`. Passing an
@@ -151,7 +151,7 @@ export class OpenCodeClient {
151
151
  * never the thing that trips it, and gives `send()` a deterministic abandonment point to recover
152
152
  * from if it ever does.
153
153
  *
154
- * #2428 — ROOT CAUSED (Bun repro): that ~294s cutoff is Bun's native HTTP idle watchdog — a
154
+ * ROOT CAUSED (Bun repro): that ~294s cutoff is Bun's native HTTP idle watchdog — a
155
155
  * fetch whose response sends no bytes for BUN_CONFIG_HTTP_IDLE_TIMEOUT (default 300s) dies
156
156
  * client-side with TimeoutError "The operation timed out" INDEPENDENTLY of the AbortSignal
157
157
  * above, which bounds only its own much longer duration. This endpoint is synchronous, so any
@@ -171,7 +171,7 @@ export class OpenCodeClient {
171
171
  headers: { "Content-Type": "application/json" },
172
172
  body: JSON.stringify({ model: openCodeTurnDescriptor(model, this.provider), parts: [{ type: "text", text }] }),
173
173
  signal: AbortSignal.timeout(timeoutMs),
174
- // #2428 — disable Bun's native HTTP idle watchdog for this whole-turn request only; the
174
+ // disable Bun's native HTTP idle watchdog for this whole-turn request only; the
175
175
  // signal above remains the sole deadline. See OpenCodeRequestInit for the verified semantics.
176
176
  timeout: false,
177
177
  });
@@ -185,7 +185,7 @@ export class OpenCodeClient {
185
185
  }
186
186
 
187
187
  /**
188
- * #2534 — bounded read of a session's persisted messages, used ONLY by the acceptance
188
+ * bounded read of a session's persisted messages, used ONLY by the acceptance
189
189
  * reconciliation when a context-carrying turn's completion await failed: a user message whose
190
190
  * persisted text equals the outbound prompt is positive proof the server ACCEPTED it, even
191
191
  * though the await never resolved. Kept on a short, explicit deadline — this runs on a failure
@@ -197,7 +197,7 @@ export class OpenCodeClient {
197
197
  }
198
198
 
199
199
  /**
200
- * #2539 Phase B — the soft-steer persist primitive: POST /session/:id/message with a CALLER
200
+ * Phase B — the soft-steer persist primitive: POST /session/:id/message with a CALLER
201
201
  * messageID + noReply:true. LIVE-PROVEN on pinned 1.18.21: persists the user row in single-digit
202
202
  * milliseconds WITHOUT joining/cancelling an in-flight run (does not touch the runner effect that
203
203
  * prompt_async would join), and the response body IS the persisted user message — so
@@ -227,7 +227,7 @@ export class OpenCodeClient {
227
227
  }
228
228
 
229
229
  /**
230
- * #2539 Phase B — the ambiguous-outcome reconcile: does the session's persisted message list
230
+ * Phase B — the ambiguous-outcome reconcile: does the session's persisted message list
231
231
  * contain EXACTLY this caller messageID? true/false are definitive (the GET succeeded and the
232
232
  * full session listing either has the row or does not); undefined means the listing itself was
233
233
  * unavailable — still ambiguous. Matching is by exact id, never by text (a resend must never
@@ -248,10 +248,10 @@ export class OpenCodeClient {
248
248
  }
249
249
 
250
250
  /**
251
- * #2440 — OpenCode's own native session-compaction endpoint (a prior effort, #2431, wrongly
251
+ * OpenCode's own native session-compaction endpoint (a prior effort, wrongly
252
252
  * concluded it did not exist and shipped clear-inject only). Like `sendMessage`, this may run
253
253
  * long server-side, so it gets the same Bun idle-watchdog bypass (`timeout: false`) plus an
254
- * explicit bounded `AbortSignal` — see `sendMessage`'s doc for the verified #2428 semantics.
254
+ * explicit bounded `AbortSignal` — see `sendMessage`'s doc for the verified semantics.
255
255
  * A session-not-found / compact-already-in-progress / server error all surface as the same
256
256
  * `request()`-thrown `HTTP <status>` error; the adapter's caller resets its busy hold on catch.
257
257
  */
@@ -278,8 +278,8 @@ export class OpenCodeClient {
278
278
  }
279
279
 
280
280
  /**
281
- * Retry/re-roll policy for a retryable turn-ending provider error (#2397). Direct measurement
282
- * (issue #2397 comment) showed these failures are BIMODAL per OpenCode session — a session either
281
+ * Retry/re-roll policy for a retryable turn-ending provider error. Direct measurement
282
+ * showed these failures are BIMODAL per OpenCode session — a session either
283
283
  * succeeds on every attempt or fails on every attempt for its whole life — so retrying harder
284
284
  * into the SAME session eventually accomplishes nothing. Bound the in-session retry tightly, then
285
285
  * re-roll to a fresh session (new session id, same server process) exactly once before giving up.
@@ -289,24 +289,24 @@ const OPENCODE_ERROR_RETRY_BASE_DELAY_MS = 1_000;
289
289
 
290
290
  /** Exponential backoff for retry attempt N (1-indexed): 1s, 2s, 4s at the defaults above. Driven
291
291
  * entirely by our own policy — OpenRouter sends neither `retry-after` nor `x-ratelimit-*` for
292
- * this error class (confirmed live, #2397), so there is nothing external to read. */
292
+ * this error class, so there is nothing external to read. */
293
293
  export function openCodeErrorRetryDelayMs(attempt: number): number {
294
294
  return OPENCODE_ERROR_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1);
295
295
  }
296
296
 
297
297
  /**
298
- * #2397 — a parsed assistant-message error off the pinned v1.18.21 wire.
298
+ * a parsed assistant-message error off the pinned v1.18.21 wire.
299
299
  *
300
300
  * `AssistantMessage.error` is a union (ProviderAuthError | UnknownError |
301
301
  * MessageOutputLengthError | MessageAbortedError | ApiError — see @opencode-ai/sdk's generated
302
302
  * types). Only `ApiError` carries a structured `statusCode`/`isRetryable`; OpenCode falls back to
303
- * `UnknownError` for anything it doesn't recognize. Verified live (#2397): a real OpenRouter 429
303
+ * `UnknownError` for anything it doesn't recognize. Verified live: a real OpenRouter 429
304
304
  * arrived as `UnknownError` with the raw provider response embedded as a JSON STRING in
305
305
  * `data.message` (`{"code":429,"message":"...","metadata":{"error_type":"rate_limit_exceeded"}}`),
306
306
  * not as a structured ApiError. So this parses defensively — peeling that nested JSON when
307
307
  * present — rather than trusting a shape that is usually absent for the errors that matter most.
308
308
  */
309
- /** #2619 — how one `send()` turn ended; see its doc. */
309
+ /** how one `send` turn ended; see its doc. */
310
310
  type OpenCodeSendOutcome = "ok" | "failed" | "held";
311
311
 
312
312
  export interface OpenCodeAssistantError {
@@ -349,7 +349,7 @@ export function parseOpenCodeAssistantError(raw: unknown): OpenCodeAssistantErro
349
349
  }
350
350
 
351
351
  /**
352
- * #2443 — a 404 from OpenCode's own session-message endpoint means OpenRouter has no route for
352
+ * a 404 from OpenCode's own session-message endpoint means OpenRouter has no route for
353
353
  * the requested provider/model at all (retired, mistyped, or never released) — generic across
354
354
  * ANY OpenRouter-routed model, never a specific one. Classifying it as the SDK's canonical
355
355
  * `model-unavailable` reason (already used by the Claude adapter for its own "model doesn't
@@ -361,7 +361,7 @@ export function openCodeTerminalFailureReason(error: Pick<OpenCodeAssistantError
361
361
  }
362
362
 
363
363
  /**
364
- * #2397 — the operator-visible providerState for "still trying" (retrying in-session, or
364
+ * the operator-visible providerState for "still trying" (retrying in-session, or
365
365
  * re-rolled to a fresh session and retrying), reusing `buildRateLimitProviderState`'s shape
366
366
  * (label/kind/source/errorType/timestamps) rather than a bespoke one. `reason` is overridden away
367
367
  * from `RATE_LIMIT_BLOCK_REASON` ("rate_limit") deliberately: that reason is special-cased in
@@ -372,7 +372,7 @@ export function openCodeTerminalFailureReason(error: Pick<OpenCodeAssistantError
372
372
  * ordinary busy-claim path (the same path `permission.asked` already uses below).
373
373
  */
374
374
  export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: boolean; message: string; errorType?: string }): ProviderState {
375
- // #2397 round 2 (F7) — only call it a rate limit when the parsed error actually said so.
375
+ // round 2 (F7) — only call it a rate limit when the parsed error actually said so.
376
376
  // `errorType` used to default to "rate_limit_exceeded" here, and the label was hardcoded to
377
377
  // "Rate-limited upstream" regardless of cause, so a 500/503 (isRetryable via the >=500 branch
378
378
  // in parseOpenCodeAssistantError, with no errorType at all) was misreported as a rate limit.
@@ -397,7 +397,7 @@ export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: b
397
397
  }
398
398
 
399
399
  /**
400
- * #2389 — what the synchronous `/message` POST hands back: the completed assistant message.
400
+ * what the synchronous `/message` POST hands back: the completed assistant message.
401
401
  *
402
402
  * This is ground truth for the incident and needs no inference at all. The stranded agent's turn
403
403
  * came back `finish=stop` with no text part, no tool part and zero tokens — an empty completion the
@@ -406,7 +406,7 @@ export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: b
406
406
  * Read defensively: OpenCode's wire is only pinned at v1.18.21, so an unrecognised shape yields
407
407
  * `undefined` counts and simply contributes no signal, leaving the cross-provider ledger to decide.
408
408
  *
409
- * #2397 — also carries the assistant message's `error`, when positively parseable. This is a
409
+ * also carries the assistant message's `error`, when positively parseable. This is a
410
410
  * STRONGER signal than `empty`: a turn can end with a non-empty `parts` count (the live incident's
411
411
  * completion carried one bare step-start part) and still be a turn-ending PROVIDER failure, so
412
412
  * `error` is checked ahead of (and independently of) the `empty` heuristic by every reader.
@@ -414,7 +414,7 @@ export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: b
414
414
  export interface OpenCodeCompletion {
415
415
  parts?: number;
416
416
  /**
417
- * #2397 round 3 (HIGH-1) — `parts` count MINUS bookkeeping-only parts (`step-start`,
417
+ * round 3 (HIGH-1) — `parts` count MINUS bookkeeping-only parts (`step-start`,
418
418
  * `step-finish`), which the server emits on every turn — including one that produced nothing
419
419
  * but a provider error. The live incident this issue was filed on captured exactly that shape:
420
420
  * one bare `step-start` part, zero tokens, a 429. `parts` alone is 1, which reads as "the model
@@ -425,8 +425,8 @@ export interface OpenCodeCompletion {
425
425
  finish?: string;
426
426
  error?: OpenCodeAssistantError;
427
427
  /**
428
- * #2413 — the terminal assistant message's TEXT, concatenated from its text parts. This
429
- * synchronous POST returns the completed assistant message (ground truth, #2389), so the
428
+ * the terminal assistant message's TEXT, concatenated from its text parts. This
429
+ * synchronous POST returns the completed assistant message, so the
430
430
  * turn-final response is read from it directly rather than reconstructed from a possibly-late
431
431
  * or lost SSE frame. Present only when the completed message carried assistant text; absent for
432
432
  * a tools-only or empty turn.
@@ -435,7 +435,7 @@ export interface OpenCodeCompletion {
435
435
  /** True only when the shape WAS understood and it says the model produced nothing. */
436
436
  empty: boolean;
437
437
  /**
438
- * #2605 — the terminal assistant message's REASONING as per-part steps (`{stepId, body}[]`,
438
+ * the terminal assistant message's REASONING as per-part steps (`{stepId, body}`,
439
439
  * wire order). Present only when the completed message carried non-empty reasoning; absent
440
440
  * for text/tools-only or empty turns. Per-part identity (not a joined string) is what lets
441
441
  * the turn-final emit upsert the live SSE rows instead of duplicating them — the server keys
@@ -444,13 +444,13 @@ export interface OpenCodeCompletion {
444
444
  */
445
445
  reasoningSteps?: OpenCodeReasoningStep[];
446
446
  /**
447
- * #2605 — the same reasoning as one joined string (the steps' bodies, `\n\n` join mirroring
447
+ * the same reasoning as one joined string (the steps' bodies, `\n\n` join mirroring
448
448
  * `text`). Kept for the completion log line only — the EMIT path reads `reasoningSteps`.
449
449
  */
450
450
  reasoning?: string;
451
451
  }
452
452
 
453
- /** #2397 round 3 (HIGH-1) — a bookkeeping marker the server emits around every turn regardless of
453
+ /** round 3 (HIGH-1) — a bookkeeping marker the server emits around every turn regardless of
454
454
  * outcome, never evidence the model produced anything. An unrecognised part shape is counted as
455
455
  * output (the conservative default): under-counting risks re-treating real work as empty and
456
456
  * re-executing it, which is the worse failure. */
@@ -471,7 +471,7 @@ function countTokens(value: unknown): number | undefined {
471
471
  if (typeof value === "number" && Number.isFinite(value)) return value;
472
472
  if (!isRecord(value)) return undefined;
473
473
  // `{ input, output, reasoning, cache: {...} }` on the pinned version, plus — confirmed against a
474
- // real captured v1.18.21 session (#2355) — a top-level `total` that is ALREADY the sum of every
474
+ // real captured v1.18.21 session — a top-level `total` that is ALREADY the sum of every
475
475
  // other leaf here (`input + output + reasoning + cache.read + cache.write`). When present it is
476
476
  // authoritative and must be returned directly: falling through to the leaf sum below as well
477
477
  // would double-count it (`total` + its own constituent parts).
@@ -511,7 +511,7 @@ export function openCodeCompletion(body: unknown): OpenCodeCompletion {
511
511
  ...(reasoning ? { reasoning } : {}),
512
512
  // Only claim emptiness when the shape was actually understood: a body we could not parse must
513
513
  // never be reported as an empty completion.
514
- // #2397 round 3 review (LOW-4r) — must agree with `completionProducedOutput`'s `outputParts`,
514
+ // round 3 review (LOW-4r) — must agree with `completionProducedOutput`'s `outputParts`,
515
515
  // not raw `parts`, or a bookkeeping-only body (step-start, tokens:0, no error) slips past BOTH
516
516
  // predicates in send(): not `producedOutput` (correctly), but also not `empty` — landing on
517
517
  // "if (!completion.empty) return true" with no turnActivityCb, no retry-hold clear, and no
@@ -530,7 +530,7 @@ function describeCompletion(completion: OpenCodeCompletion): string {
530
530
  return ` → ${bits.join(", ")}${completion.empty ? " — EMPTY COMPLETION" : ""}`;
531
531
  }
532
532
 
533
- /** Bound on the live per-session bookkeeping in `OpenCodeAdapter` (#2347): a headless agent can
533
+ /** Bound on the live per-session bookkeeping in `OpenCodeAdapter`: a headless agent can
534
534
  * run for days across many turns, so unbounded maps keyed by message/part id would leak. Both
535
535
  * `messageRoles` and `emittedTextParts` are pruned back to this size, oldest first, whenever a
536
536
  * write would exceed it. */
@@ -551,7 +551,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
551
551
  readonly reportsContextResets = true;
552
552
  readonly provider: string;
553
553
  readonly statusSourceId: string;
554
- // #2440 — unlocks `context.resume = "native"` capability advertisement (see
554
+ // unlocks `context.resume = "native"` capability advertisement (see
555
555
  // runtimeProviderContextCapabilities). `opts.instructions` is deliberately NOT threaded into the
556
556
  // `/compact` HTTP call itself (see `performCompact`'s doc) — the relay's own reactive
557
557
  // continuation inject already delivers it as a follow-up message either way, exactly like
@@ -560,7 +560,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
560
560
  // OpenCode's `/compact` endpoint is NOT instruction-steerable — `performCompact` never sends the
561
561
  // continuation on the wire at all, so the summarizer itself never sees it. Setting this flag true
562
562
  // is what makes `resume:"native"` selectable; the actual delivery guarantee comes entirely from
563
- // the reactive inject, not from this flag. Follow-up #2442 tracks a cleaner capability bit that
563
+ // the reactive inject, not from this flag. Tracks a cleaner capability bit that
564
564
  // distinguishes "native resume is offered" from "the compact call itself accepts steering text",
565
565
  // so a future reader doesn't read this as "the summary is instructable" when it is not.
566
566
  readonly compactSupportsInstructions = true;
@@ -568,53 +568,53 @@ export class OpenCodeAdapter implements ProviderAdapter {
568
568
  private sessionEventCb: (event: ProviderSessionEvent) => void = () => {};
569
569
  private turnActivityCb: () => void = () => {};
570
570
  // `message.updated` (role) always precedes that message's `message.part.updated` events on the
571
- // real v1.18.21 wire (verified live against OpenRouter meta/muse-spark-1.3-contributor, #2347) — spawn()
571
+ // real v1.18.21 wire (verified live against OpenRouter meta/muse-spark-1.3-contributor) — spawn
572
572
  // subscribes before any prompt is sent, so there is no race for the normal launch flow.
573
573
  private readonly messageRoles = new Map<string, string>();
574
574
  private readonly emittedTextParts = new Set<string>();
575
- // #2354 — stepId → highest lifecycle rank already emitted for that tool part, so a duplicate or
575
+ // stepId → highest lifecycle rank already emitted for that tool part, so a duplicate or
576
576
  // reordered `message.part.updated` (a late `running` after `completed`, a repeated frame) never
577
577
  // regresses or double-emits the step. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
578
578
  private readonly emittedToolSteps = new Map<string, number>();
579
- // #2437 — the ToolPart wire shape only carries `input` while `running`; a `completed` decode has
579
+ // the ToolPart wire shape only carries `input` while `running`; a `completed` decode has
580
580
  // none. Cache the redacted params captured at running, keyed by stepId, and carry them forward
581
581
  // onto the terminal event so the row's params don't vanish when the server upserts it in place
582
582
  // (upsertSessionStepInPlace replaces the whole payload with the incoming one, not a merge).
583
583
  private readonly toolStepParams = new Map<string, unknown>();
584
- // #2605 — reasoning part id → full body already emitted for that part, so a repeated or
584
+ // reasoning part id → full body already emitted for that part, so a repeated or
585
585
  // finalized `message.part.updated` frame for the same reasoning part emits the CURRENT full
586
586
  // text (the server upserts the trace row in place by stepId) instead of appending a duplicate
587
587
  // row. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
588
588
  private readonly emittedReasoningParts = new Map<string, string>();
589
- // #2413 review (HIGH-1) — a genuinely PER-SEND turn id, minted at the top of each `send()` and
589
+ // review (HIGH-1) — a genuinely PER-SEND turn id, minted at the top of each `send` and
590
590
  // used for the busy edge, narration, tool steps, and the turn-final response alike. Was
591
591
  // `process.meta.sessionId`, which PERSISTS across sends: two turns in one session then shared a
592
592
  // turnId, so lineage dedup (promoteLineageCapturedResponse/flushWorkFinalReports) forwarded the
593
593
  // FIRST turn's body and dropped the SECOND — the real final report never reached the coordinator.
594
594
  private sendTurnId: string | undefined;
595
- // #2397 round 2 (F3/F4) — live-confirmed: the real server emits its OWN busy/idle
595
+ // round 2 (F3/F4) — live-confirmed: the real server emits its OWN busy/idle
596
596
  // session.status pair around EVERY attempt, including failed ones (two `idle` events for one
597
597
  // errored turn). Left alone, each failed attempt's idle looks like a real provider-turn close
598
- // to runner-core's #2389 seam: it clears `currentTurnId` and judges that blip as an empty turn,
598
+ // to runner-core's seam: it clears `currentTurnId` and judges that blip as an empty turn,
599
599
  // then the next attempt's busy mints a brand-new turn id — up to
600
600
  // `OPENCODE_ERROR_RETRY_MAX_ATTEMPTS * 2` extra empty-turn closes the operator sees, and a
601
601
  // premature nudge/escalation mid-retry. Raised for the WHOLE `send()` call (see the loop entry),
602
602
  // this swallows every transient idle for the call's own turn so the whole retry sequence stays
603
603
  // ONE turn from the runner's view.
604
- // #2397 round 5 review (MED-1) — this is now a STICKY suppression, not cleared at close time:
604
+ // round 5 review (MED-1) — this is now a STICKY suppression, not cleared at close time:
605
605
  // `closeSuppressedTurn` used to lower it as it fired, so a HEALTHY turn's own trailing real SSE
606
606
  // idle — if processed after the POST already resolved — sailed through unsuppressed and closed
607
607
  // the same turn a SECOND time (duplicate reInjectPendingObligation, duplicate
608
608
  // completeObservedTaskClaims/Task-done). The invariant that actually holds: an idle belonging to
609
609
  // a turn `send()` already closed must stay swallowed; only an idle for a turn that STARTS AFTER
610
610
  // that close may pass.
611
- // #2397 round 6 review (MED-1r) — round 5 had `handleEvent`'s BUSY door lower this flag whenever
611
+ // round 6 review (MED-1r) — round 5 had `handleEvent`'s BUSY door lower this flag whenever
612
612
  // `turnInFlight` was false, on the assumption that such a busy edge could only be a genuinely new
613
613
  // turn. That is a timing assumption, not an invariant: `subscribe()` dispatches a whole SSE chunk
614
614
  // synchronously in one loop, and the SSE/POST sockets have no cross-ordering guarantee, so a late
615
615
  // own-turn busy+idle pair batched together AFTER the POST resolves cleared suppression via the
616
616
  // busy half and then let the idle half sail straight through — double-closing the turn.
617
- // #2397 round 7 review (MED-1/MED-2/LOW-1) — round 6 only made the reopen one-shot (the FIRST
617
+ // round 7 review (MED-1/MED-2/LOW-1) — round 6 only made the reopen one-shot (the FIRST
618
618
  // post-close idle lowered this flag again), which still let a SECOND trailing idle in the same
619
619
  // window (two late idles; a 429-retry where both attempts' own idles land after send() returns;
620
620
  // a foreign child session's busy+idle, since `handleEvent` never filters `properties.sessionID`)
@@ -623,19 +623,19 @@ export class OpenCodeAdapter implements ProviderAdapter {
623
623
  // real turn-boundary transition for a turn `send()` actually SAW END is already emitted
624
624
  // EXPLICITLY by `send()` (its own `busy` at each attempt's start) and by
625
625
  // `closeSuppressedTurn`/`failTurn` (their own `idle`/`error`) — never by this SSE echo.
626
- // #2425 — that reasoning has exactly one hole: it assumes `send()` always learns how its own
626
+ // that reasoning has exactly one hole: it assumes `send` always learns how its own
627
627
  // turn ended. `sendMessage`'s POST can instead THROW with the server-side turn's outcome
628
628
  // unknown (a client-side timeout on a turn that keeps running upstream) — see `turnAbandoned`
629
629
  // below. In that one case ONLY, staying permanent forever would swallow every subsequent real
630
630
  // busy/idle for the rest of this adapter instance's life, freezing status at whatever
631
631
  // `send()`'s own `finally` guessed. Both SSE doors below therefore also check `turnAbandoned`
632
- // — the per-session filter (also added for #2425) is what makes that safe: a foreign session's
632
+ // — the per-session filter is what makes that safe: a foreign session's
633
633
  // busy/idle can never reach here regardless of `turnAbandoned`, so lifting suppression for OUR
634
634
  // OWN session's real events reopens no hazard round 7 was guarding against.
635
635
  private suppressProviderIdle = false;
636
- // #2425 — true once THIS call's own `finally` closed the turn WITHOUT any deterministic
636
+ // true once THIS call's own `finally` closed the turn WITHOUT any deterministic
637
637
  // success/failure branch having run first (i.e. `sendMessage` or something else in the retry
638
- // loop threw — a client-side timeout being the motivating case, #2425's incident). The server-
638
+ // loop threw — a client-side timeout being the motivating case, the incident). The server-
639
639
  // side outcome is genuinely unknown in that case, unlike a normal completion or `failTurn`
640
640
  // (both of which set `turnClosed` themselves before the `finally` runs, so `turnAbandoned` stays
641
641
  // false for them — see the `finally` block). While true, the two SSE doors above stop enforcing
@@ -645,35 +645,35 @@ export class OpenCodeAdapter implements ProviderAdapter {
645
645
  // boundary, its own explicit busy/idle is authoritative again, and any stray leftover event from
646
646
  // a truly-abandoned previous turn must stay suppressed rather than corrupt the new turn's state.
647
647
  private turnAbandoned = false;
648
- // #2619 — a session re-roll already spent in the current provider-hold episode; cleared by the
648
+ // a session re-roll already spent in the current provider-hold episode; cleared by the
649
649
  // first turn that goes through (send() → "ok"). Bounds a long wall to ONE history-dropping re-roll.
650
650
  private rerolledThisHoldEpisode = false;
651
- // #2775 — a wall OpenCode's own retry hit during this send(); the session is aborted and send() holds.
651
+ // a wall OpenCode's own retry hit during this send; the session is aborted and send holds.
652
652
  private serverRetryWall?: OpenCodeServerRetry;
653
- // #2397 round 5 review (MED-1/LOW-2) — true from the moment `send()` is actually about to run an
653
+ // round 5 review (MED-1/LOW-2) — true from the moment `send` is actually about to run an
654
654
  // attempt (past the readiness guard, so a "session not ready" throw never sets it) through the
655
655
  // call's `finally`.
656
- // #2397 round 7 review — no longer read by either SSE door (both now key only on the permanent
656
+ // round 7 review — no longer read by either SSE door (both now key only on the permanent
657
657
  // `suppressProviderIdle` above). Still captured by `send()`'s `finally` (round 6, MED-2r) to
658
658
  // decide whether THIS call ever actually started an attempt, so a "session not ready" throw
659
659
  // never triggers a close for a turn that never began.
660
660
  private turnInFlight = false;
661
- // #2397 round 5 review (MED-1) — true once THIS call's own close has already fired (via
661
+ // round 5 review (MED-1) — true once THIS call's own close has already fired (via
662
662
  // `closeSuppressedTurn` or `failTurn`), reset at the top of every `send()` call. Distinct from
663
663
  // `suppressProviderIdle` (which deliberately stays raised after firing, per above): this is the
664
664
  // idempotency guard that stops the SAME call's multiple close sites — an explicit branch call
665
665
  // plus the `finally` fallback — from emitting a second idle for one call.
666
- // #2425 — also doubles as the double-close guard for the `turnAbandoned` escape hatch: once
666
+ // also doubles as the double-close guard for the `turnAbandoned` escape hatch: once
667
667
  // abandonment force-closes a turn, this stays true until a genuine same-session busy re-arms it
668
668
  // (see the busy door), so a stray trailing idle for the SAME abandoned turn finds it already
669
669
  // closed and is a no-op rather than a second `idle` dispatch.
670
670
  private turnClosed = false;
671
- // #2431 round 1 (HIGH) — FIFO serializer making `clearContext()` safe against an IN-FLIGHT turn.
671
+ // round 1 (HIGH) — FIFO serializer making `clearContext` safe against an IN-FLIGHT turn.
672
672
  // `agent.clearContext` is dispatched fire-and-forget (dispatchCommand holds no provider-input
673
673
  // ticket for it — PROVIDER_INPUT_COMMAND_TYPES is inject-only) and OpenCode's `/message` POST is
674
- // SYNCHRONOUS (#2389), so the model calling relay_compact_and_resume from within its own turn
674
+ // SYNCHRONOUS, so the model calling humwick_compact_and_resume from within its own turn
675
675
  // means the command genuinely runs WHILE send() is still awaiting that POST. send()'s retry loop
676
- // re-reads process.meta.sessionId every iteration and cannot tell its own #2397 reroll from an
676
+ // re-reads process.meta.sessionId every iteration and cannot tell its own reroll from an
677
677
  // external swap, so an unserialized clear redirected the live turn's retries/rerolls — and their
678
678
  // reportContextUsage writes — straight into the freshly cleared session. Every writer of that
679
679
  // per-call state therefore goes through this queue: both send() call sites (deliverInitialPrompt/
@@ -686,11 +686,11 @@ export class OpenCodeAdapter implements ProviderAdapter {
686
686
  // command early, because "clear anyway on timeout" would reintroduce exactly this bug.
687
687
  private turnQueue: Promise<unknown> = Promise.resolve();
688
688
 
689
- // ─── #2539 Phase B — non-destructive soft-steer lane (default-OFF kill switch) ───
689
+ // ─── Phase B — non-destructive soft-steer lane (default-OFF kill switch) ───
690
690
  // Durable-until-consumed ledger for positively-persisted soft batches (persistence != delivery).
691
691
  private readonly softSteer = new OpenCodeSoftSteerLedger();
692
692
  // The soft lane's OWN one-in-flight FIFO (fast loopback POSTs) — deliberately NOT turnQueue:
693
- // serializing soft steers behind whole turns is the 84-minute #2539 delay this lane exists to
693
+ // serializing soft steers behind whole turns is the 84-minute delay this lane exists to
694
694
  // remove. Only the POST+reconcile run here; the wake turn (rare, fallback) goes through
695
695
  // turnQueue like any normal turn so it owns a real busy/idle pair.
696
696
  private softSteerQueue: Promise<unknown> = Promise.resolve();
@@ -700,7 +700,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
700
700
  private softSteerProcess: ManagedProcess | undefined;
701
701
 
702
702
  /**
703
- * #2544 — the operator-facing capability bit. DEFAULT-ON since the review-gated flip: the
703
+ * the operator-facing capability bit. DEFAULT-ON since the review-gated flip: the
704
704
  * deployed-fleet canary soak (one opencode/zai agent, 2026-09-11) proved the end-to-end path
705
705
  * through A's seam — fold-in, durable-until-consumed settlement, no dup/loss/hot-loop, no run
706
706
  * disruption. The per-offer policy reads `ProviderConfig.activeTurnMessageDelivery` via
@@ -710,8 +710,8 @@ export class OpenCodeAdapter implements ProviderAdapter {
710
710
  */
711
711
  readonly supportsMidTurnSteer = true;
712
712
 
713
- // #2459 — event-driven turn identity for TERMINAL-origin turns. OpenCode is headless with no
714
- // server-initiated turns (the #2397 round-7 invariant), so a session.status:busy arriving for
713
+ // event-driven turn identity for TERMINAL-origin turns. OpenCode is headless with no
714
+ // server-initiated turns, so a session.status:busy arriving for
715
715
  // the active session OUTSIDE send() can only be a prompt typed into an attached web-terminal
716
716
  // TUI. send() is not running then, so its sticky `suppressProviderIdle` would otherwise swallow
717
717
  // the whole turn, and the events would fall back to the STALE `sendTurnId` of the last headless
@@ -721,7 +721,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
721
721
  // Assistant text accumulated (finalized text parts, active session) while the event turn is
722
722
  // open, so its close can emit the turn-final response exactly like send()'s POST path does.
723
723
  private eventTurnText: string[] = [];
724
- // #2605 — reasoning observed this event turn, keyed by part id to the LATEST body seen. The
724
+ // reasoning observed this event turn, keyed by part id to the LATEST body seen. The
725
725
  // terminal-origin path has no completed-message POST to read back from, so the close must
726
726
  // reconcile from what the live door saw — but keyed per part (not a body pool): a growth
727
727
  // frame for an already-emitted part only needs the newest snapshot, and an identical body in
@@ -729,17 +729,17 @@ export class OpenCodeAdapter implements ProviderAdapter {
729
729
  // Bounded: part ids are server-unique per turn and the map is reset every turn, so it holds
730
730
  // at most one entry per reasoning part of the CURRENT turn — no prefix scan, no growth.
731
731
  private eventTurnReasoning = new Map<string, string>();
732
- // #2459 round 1 (review D) — the EVENT-SOURCE discriminator replacing the round-0 time
732
+ // round 1 (review D) — the EVENT-SOURCE discriminator replacing the round-0 time
733
733
  // window. A terminal-origin turn is always PRECEDED by its prompt: the attached TUI's
734
734
  // POST creates the user message on the active session before/while the turn's busy edge
735
735
  // flows, so the message.updated(role=user) door arms this flag. The busy door admits a
736
- // terminal turn only when armed (or when no headless turn ever suppressed, or #2425
736
+ // terminal turn only when armed (or when no headless turn ever suppressed, or
737
737
  // abandonment). A HEADLESS echo — the buffered busy+idle pair of a turn send() just
738
738
  // closed — has NO such fresh user message by construction, so it stays suppressed with NO
739
739
  // time bound: a delayed echo of any age is indistinguishable from a stale one and both are
740
- // dropped, restoring #2397 round-7 permanence positively instead of heuristically.
740
+ // dropped, restoring round-7 permanence positively instead of heuristically.
741
741
  private pendingTerminalPrompt = false;
742
- // #2459 round 2 (review D) — the terminal user prompt BUFFER. The [user message.updated →
742
+ // round 2 (review D) — the terminal user prompt BUFFER. The [user message.updated →
743
743
  // user part → busy] wire ordering means the prompt can arrive BEFORE the busy that mints
744
744
  // the terminal turn id; buffering keeps the prompt from publishing a raw session id as its
745
745
  // turnId and from splitting off from its trace/final. SINGLE SLOT (the bound): a newer
@@ -748,13 +748,13 @@ export class OpenCodeAdapter implements ProviderAdapter {
748
748
  // send() takeover, and the clear/re-roll swap — the fallback id is ALWAYS the session
749
749
  // pseudo-turn or the minted id, never a stale headless sendTurnId.
750
750
  private bufferedTerminalPrompt: { body: string; occurredAt: number } | undefined;
751
- // #2459 round 1 (review D) — permanent marker that at least one headless turn ran on this
751
+ // round 1 (review D) — permanent marker that at least one headless turn ran on this
752
752
  // process. Once true, the busy door admits a terminal-origin turn ONLY through the armed
753
753
  // `pendingTerminalPrompt` (fresh first-seen user message), with NO time bound: a headless
754
754
  // busy echo of any age stays suppressed. Fresh adapters (no headless turn yet) admit freely,
755
755
  // preserving pure-terminal sessions whose busy arrives before any user-message echo.
756
756
  private headlessTurnSeen = false;
757
- // #2459 round 1 (review D) — user-message ids already observed on this process (bounded like
757
+ // round 1 (review D) — user-message ids already observed on this process (bounded like
758
758
  // the other event-state maps). A REPEAT user message.updated is a buffered duplicate of a
759
759
  // turn that already ran (send()'s own POST echo delivered late) and must never re-arm the
760
760
  // terminal gate; only a FIRST-SEEN user message is evidence of a freshly typed TUI prompt.
@@ -762,11 +762,11 @@ export class OpenCodeAdapter implements ProviderAdapter {
762
762
 
763
763
  private readonly sleepImpl: (ms: number) => Promise<void>;
764
764
 
765
- // #2484 — the token-usage spine transport, wired by runner-core (Claude's #2482/#2491 seam).
765
+ // the token-usage spine transport, wired by runner-core.
766
766
  private readonly apiBudgets = new WeakMap<ManagedProcess, OpenCodeApiBudget>();
767
767
 
768
768
  private tokenUsageReporter?: ProviderTokenUsageReporter;
769
- // #2484 round 1 review (F2/F3) — collapses the message.updated door's streaming echoes to one
769
+ // round 1 review (F2/F3) — collapses the message.updated door's streaming echoes to one
770
770
  // spine post per message; flushed early at real turn boundaries (see flushAll() call sites).
771
771
  private readonly tokenUsagePoster = new OpenCodeTokenUsagePoster(() => this.tokenUsageReporter);
772
772
 
@@ -796,11 +796,11 @@ export class OpenCodeAdapter implements ProviderAdapter {
796
796
  this.tokenUsageReporter = reporter;
797
797
  }
798
798
 
799
- // #2484 round 2 review (MED-1) — the runner's ONE awaited, time-boxed teardown seam
799
+ // round 2 review (MED-1) — the runner's ONE awaited, time-boxed teardown seam
800
800
  // (RunnerCore.settleTerminalTokenUsage, wired at stop() / restartUnexpectedProviderExit /
801
801
  // runPreSessionDestroy) previously no-op'd for OpenCode because it bails on a falsy
802
802
  // transcriptPath — a Claude-shaped precondition OpenCode never has (it has no transcript file
803
- // to tail; #2484's capture is event-sourced from the message.updated SSE door). Owning that
803
+ // to tail; the capture is event-sourced from the message.updated SSE door). Owning that
804
804
  // bail here — ignore transcriptPath, just settle the poster — means the crash-exit and
805
805
  // pre-session-destroy paths (which never call adapter.shutdown()) now reach the same debounced
806
806
  // report shutdown() already flushes, instead of losing the session's final token counts.
@@ -818,7 +818,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
818
818
  openCodeApiKey(config, launch.qualified);
819
819
  const args = this.buildSpawnArgs(config, config.providerConfig as ProviderConfig, launch);
820
820
  const apiBudget = launch.backendID === "openai" ? new OpenCodeApiBudget(config) : undefined;
821
- await apiBudget?.start(relayEnv(config.env, "AGENT_RELAY_PROVIDER_SESSION_ID") ?? crypto.randomUUID());
821
+ await apiBudget?.start(relayEnv(config.env, "HUMWICK_PROVIDER_SESSION_ID") ?? crypto.randomUUID());
822
822
  const server = await spawnOpenCodeServer(args, apiBudget);
823
823
  const password = args.env.OPENCODE_SERVER_PASSWORD!;
824
824
  const client = new OpenCodeClient(args.env.OPENCODE_SERVER_URL!, password, fetch, this.provider);
@@ -827,23 +827,23 @@ export class OpenCodeAdapter implements ProviderAdapter {
827
827
  const sessionTitle = config.label ?? config.agentId;
828
828
  const sessionId = await client.createSession(sessionTitle);
829
829
  const events = new AbortController();
830
- // sessionTitle is also read by a #2397 session re-roll (a fresh session created mid-flight
830
+ // sessionTitle is also read by a session re-roll (a fresh session created mid-flight
831
831
  // after repeated retryable errors); sessionId is MUTATED IN PLACE there, so every other
832
832
  // reader of process.meta.sessionId (send/interrupt/shutdown) picks the new session up too.
833
- // #2459 — the launch metadata terminalAttachSpec() needs is retained here as well:
833
+ // the launch metadata terminalAttachSpec needs is retained here as well:
834
834
  // resolved executable, loopback server URL, agent cwd and the server password. These live
835
835
  // ONLY in this runner-local memory (and the password ONLY re-emerges as the guest process
836
836
  // env inside the attach spec); they are never persisted, logged, or returned anywhere else.
837
- // #2599 — the payload record (when present) pins meta.model to the server-computed
837
+ // the payload record (when present) pins meta.model to the server-computed
838
838
  // qualified name, so later reportContextUsage calls keep payload precedence instead of
839
839
  // re-deriving from a possibly-ambiguous alias. Manifest path unchanged when absent.
840
- // #2659 — meta.model is the normalized launch model; turns split it verbatim, never re-resolve.
840
+ // meta.model is the normalized launch model; turns split it verbatim, never re-resolve.
841
841
  const process: ManagedProcess = { pid: server.pid, process: server, meta: { client, sessionId, events, model: launch.qualified, launchModel: launch.qualified, sessionTitle, config, relayContextSent: false, launchContextSent: false, attachCommand: args.command, cwd: args.cwd, serverUrl: args.env.OPENCODE_SERVER_URL!, serverPassword: password, stateDir: args.env.XDG_STATE_HOME } };
842
842
  if (apiBudget) this.apiBudgets.set(process, apiBudget);
843
- // #2443 — both used to be a bare `void promise.catch(...)`: a synchronous throw from
843
+ // both used to be a bare `void promise.catch(...)`: a synchronous throw from
844
844
  // `this.statusCb`/`handleEvent` inside the fulfillment or catch handler produced a SECOND,
845
845
  // truly unhandled rejection nothing consumed (Bun exits(1) on that; nothing respawns the
846
- // runner — #1618). `fireAndForget` guarantees the rejection — from either the initial call
846
+ // runner —). `fireAndForget` guarantees the rejection — from either the initial call
847
847
  // or the handler itself — is always logged, never escapes.
848
848
  void fireAndForget(`opencode SSE subscribe (session ${sessionId})`, () => client.subscribe(events.signal, (event) => this.handleEvent(event, process)), (message) => logger.error("opencode", message));
849
849
  void fireAndForget(`opencode server exit status callback (session ${sessionId})`, async () => {
@@ -861,19 +861,19 @@ export class OpenCodeAdapter implements ProviderAdapter {
861
861
  }
862
862
  }
863
863
 
864
- // #2659 — spawn() passes the registry-normalized launch model; direct callers resolve statically.
864
+ // spawn passes the registry-normalized launch model; direct callers resolve statically.
865
865
  buildSpawnArgs(config: RunnerSpawnConfig, providerConfig: ProviderConfig, launch = openCodeLaunchModel(config.model, this.provider, openCodeModelRecord(config.modelRecord, this.provider))): SpawnArgs {
866
866
  return buildOpenCodeSpawnArgs(config, providerConfig, this.provider, launch);
867
867
  }
868
868
 
869
869
  /**
870
- * #2459 — the web-terminal attach spec for an interactive `opencode attach` guest TUI,
870
+ * the web-terminal attach spec for an interactive `opencode attach` guest TUI,
871
871
  * reusing the codex guest-terminal path downstream (runner /terminal/attach-spec →
872
872
  * orchestrator guest tmux → Relay's authenticated terminal proxy → dashboard xterm).
873
873
  *
874
874
  * Fail-closed BEFORE readiness/session: every input is read from the launch metadata
875
875
  * retained at spawn() and a missing piece throws rather than degrading. The command is
876
- * exactly the pinned-CLI seam the #2459 feasibility verified against v1.18.21:
876
+ * exactly the pinned-CLI seam the feasibility verified against v1.18.21:
877
877
  * `opencode attach <loopback server URL> --session <active session id> --dir <agent cwd>
878
878
  * --pure`. `--dir` MUST equal the server/session cwd — OpenCode sessions are
879
879
  * project-directory scoped and attach fails (or silently diverges) when the client and
@@ -916,7 +916,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
916
916
  }
917
917
 
918
918
  /**
919
- * #2459 — the CURRENT attachment generation (derived from the active session id), read by
919
+ * the CURRENT attachment generation (derived from the active session id), read by
920
920
  * the runner control server's GET /terminal/generation so the orchestrator can detect a
921
921
  * session rotation without learning the session id or any credential. undefined when the
922
922
  * session is not ready (nothing to check).
@@ -927,7 +927,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
927
927
  }
928
928
 
929
929
  /**
930
- * #2431 round 1 (HIGH) — run `operation` as the exclusive holder of the turn queue (see the
930
+ * round 1 (HIGH) — run `operation` as the exclusive holder of the turn queue (see the
931
931
  * `turnQueue` field doc for why this exists). FIFO: an operation enqueued while a turn is
932
932
  * mid-flight starts only after that turn's promise settles, success or failure.
933
933
  */
@@ -938,23 +938,23 @@ export class OpenCodeAdapter implements ProviderAdapter {
938
938
  }
939
939
 
940
940
  async deliverInitialPrompt(process: ManagedProcess, prompt: string, options?: PromptOfferOptions): Promise<void | "held"> {
941
- // #2397 round 3 review (LOW-5r) — `send()`'s boolean is intentionally NOT surfaced as a throw
941
+ // round 3 review (LOW-5r) — `send`'s boolean is intentionally NOT surfaced as a throw
942
942
  // here: this method's `Promise<void>` contract is shared by every adapter, and a throw would
943
943
  // route into attemptInitialPromptDelivery's (runner-core.ts) generic retry-and-eventually-
944
944
  // give-up path, re-sending the SAME prompt on top of the retry+reroll budget `send()` already
945
945
  // exhausted. Instead, runner-core reads `this.terminalFailure` (set by failTurn's own `error`
946
946
  // status, cleared by the `busy` edge this very call started with) right after this resolves, so
947
947
  // it can log the terminal outcome correctly without inventing a second retry loop.
948
- // #2619 — a refusal under a provider hold is surfaced ("held"): the runner parks the prompt.
948
+ // a refusal under a provider hold is surfaced ("held"): the runner parks the prompt.
949
949
  if (await this.enqueueTurn(() => this.send(process, prompt, options?.inputObserver)) === "held") return "held";
950
950
  }
951
951
 
952
952
  async deliver(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<DeliverResult> {
953
- // #2539 Phase B — the paste-instant soft-steer routing (Phase A's single-call-path pattern,
953
+ // Phase B — the paste-instant soft-steer routing (Phase A's single-call-path pattern,
954
954
  // codex.ts:435): a mid-turn offer with steerable content goes through the adapter's
955
955
  // deliverMidTurn (noReply persist, settlement deferred through ctx); boundary-only classes
956
956
  // and the lane-off/idle cases keep the turn-end path below. Policy resolves per offer from
957
- // the spawn config; this adapter's default is "steer" (#2544 flip), "defer" the kill switch.
957
+ // the spawn config; this adapter's default is "steer", "defer" the kill switch.
958
958
  if (messages.length > 0 && this.softSteerPolicyFor(process) === "steer" && ctx?.isRecipientMidTurn?.() === true) {
959
959
  // r3 HIGH — steer ONLY the steerable partition, never the whole batch: the omitted ids
960
960
  // (claimables and synthetic memory/resume injections) stay in this
@@ -967,21 +967,21 @@ export class OpenCodeAdapter implements ProviderAdapter {
967
967
  const { steerable } = partitionMidTurnDelivery(messages);
968
968
  if (steerable.length > 0) return this.deliverMidTurn(process, steerable, ctx);
969
969
  }
970
- // #2397 round 3 (MED-3) — only a message `send()` actually got through (no terminal failure)
970
+ // round 3 (MED-3) — only a message `send` actually got through (no terminal failure)
971
971
  // is acknowledged. runner-core marks every acknowledged id read and re-queues everything else
972
972
  // (runner-core.ts ~969-995); returning ALL ids unconditionally — the old behavior — marked a
973
973
  // message that provoked 8 failed POSTs and a terminal failure as delivered+read though the
974
- // model never processed it: recorded delivered, silently lost. #2397 round 3 (MED-6) — once a
974
+ // model never processed it: recorded delivered, silently lost. Round 3 (MED-6) — once a
975
975
  // message fails terminally the session is now `error`/offline, so stop rather than burn the
976
976
  // same ~16-minute worst-case retry budget on every remaining queued message in this batch.
977
- // #2397 round 3 review (MED-3r) — `terminalFailureIds` tells runner-core WHY this id was not
977
+ // round 3 review (MED-3r) — `terminalFailureIds` tells runner-core WHY this id was not
978
978
  // acknowledged, so its re-drain paces with bounded exponential backoff instead of hammering an
979
979
  // upstream that is already rate-limiting on the very next tick.
980
980
  const acknowledgedIds: number[] = [];
981
981
  const terminalFailureIds: number[] = [];
982
982
  const providerHeldIds: number[] = [];
983
983
  for (const message of messages) {
984
- let disarmOpener: (() => void) | undefined; // #2840 — each send is its own turn, opened by this message
984
+ let disarmOpener: (() => void) | undefined; // each send is its own turn, opened by this message
985
985
  try {
986
986
  const outcome = await this.enqueueTurn(() => { disarmOpener = ctx?.openTurnFor?.([message]); return this.send(process, providerMessageText([message]), ctx?.inputObserver); });
987
987
  if (outcome !== "ok") disarmOpener?.();
@@ -989,7 +989,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
989
989
  terminalFailureIds.push(message.id);
990
990
  break;
991
991
  }
992
- // #2619 — refused under a provider hold: neither delivered nor failed. Stop the batch and
992
+ // refused under a provider hold: neither delivered nor failed. Stop the batch and
993
993
  // hand the refused id AND the untried tail to the runner to park — they would only hit
994
994
  // the same wall; the runner redelivers them once a provider turn goes through again.
995
995
  if (outcome === "held") {
@@ -997,7 +997,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
997
997
  break;
998
998
  }
999
999
  } catch (error) {
1000
- // #2534 review (LOW-3) — symmetric with runner-core's attemptInitialPromptDelivery: a
1000
+ // review (LOW-3) — symmetric with runner-core's attemptInitialPromptDelivery: a
1001
1001
  // PromptAcceptedError means the provider ACCEPTED this queued message's POST and the
1002
1002
  // completion await failed/aborted on a still-running turn. Propagating it reached
1003
1003
  // runner-core's drain, which requeued the whole batch and double-POSTed into that live
@@ -1012,7 +1012,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1012
1012
  }
1013
1013
 
1014
1014
  /**
1015
- * #2539 Phase B — the soft-steer op (see the adapter-interface doc): persist the batch into
1015
+ * Phase B — the soft-steer op (see the adapter-interface doc): persist the batch into
1016
1016
  * the in-flight turn WITHOUT touching any send() turn state. Non-negotiable invariants:
1017
1017
  * - send()/its instance fields stay non-reentrant: this path must not read or write
1018
1018
  * sendTurnId/turnInFlight/suppressProviderIdle/turnClosed/retry/reroll and must never emit a
@@ -1030,7 +1030,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1030
1030
  * - At-least-once with explicit ambiguity: one POST, then (only on ambiguity) ONE bounded
1031
1031
  * exact-id reconcile; a positive persistence is NEVER re-POSTed.
1032
1032
  *
1033
- * #2539 Phase A reconciliation — the result speaks the shared DeliverResult vocabulary:
1033
+ * Phase A reconciliation — the result speaks the shared DeliverResult vocabulary:
1034
1034
  * persisted/ambiguous ids return as `awaitingConsumptionIds` (their settlement completes later
1035
1035
  * through ctx.settleMidTurnConsumed / ctx.reofferMidTurnIds — persistence is not delivery);
1036
1036
  * everything NOT accepted (lane off, boundary-only, cap overflow, pre-POST fencing loss) is
@@ -1115,7 +1115,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1115
1115
  // Force-tracked even at the ledger cap: the cap gates new POSITIVE persists (backpressure to
1116
1116
  // the normal queue), never the RESOLVABILITY of an already-attempted POST — an untracked
1117
1117
  // unknown batch could never be resolved by any ledger exit while this contract reports it as
1118
- // held (silent orphan under exactly the long-turn + one-timeout scenario #2539 exists for).
1118
+ // held (silent orphan under exactly the long-turn + one-timeout scenario exists for).
1119
1119
  this.softSteer.noteBatch(messageID, includedIds, sessionId, { force: true, inputObserver: ctx?.inputObserver });
1120
1120
  // Same swap-inside-the-POST-window fence as the persisted path: a batch noted onto a dead
1121
1121
  // session can never be resolved there (the same-session consumption door and the
@@ -1131,7 +1131,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1131
1131
  // The spawn config the adapter was launched with (meta.config is the RunnerSpawnConfig handed
1132
1132
  // to spawn()); the steer policy reads its providerConfig — never a provider-name branch.
1133
1133
  // Same shape as codex's spawnProviderConfig (kept per-adapter, matching the landed precedent).
1134
- // #2544: the absent-config default is "steer" (default-on); an explicit "defer" is the
1134
+ // the absent-config default is "steer" (default-on); an explicit "defer" is the
1135
1135
  // per-agent kill switch, resolved through the shared seam like codex's "steer" default.
1136
1136
  private softSteerPolicyFor(process: ManagedProcess): "steer" | "defer" {
1137
1137
  const config = (process.meta?.config as { providerConfig?: ProviderConfig } | undefined)?.providerConfig;
@@ -1159,7 +1159,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1159
1159
  }
1160
1160
 
1161
1161
  /**
1162
- * #2539 Phase B — the bounded idle-wake fallback: the provider just went idle with
1162
+ * Phase B — the bounded idle-wake fallback: the provider just went idle with
1163
1163
  * positively-persisted-but-unconsumed soft batches (mid-run reload missed them — the live
1164
1164
  * at-stop strand, or an unknown-outcome batch). Arm EXACTLY ONE content-free wake turn through
1165
1165
  * turnQueue (a NORMAL send() turn: real busy/idle pair, full retry semantics — no second turn
@@ -1198,16 +1198,16 @@ export class OpenCodeAdapter implements ProviderAdapter {
1198
1198
  }
1199
1199
 
1200
1200
  /**
1201
- * #2431 — self-resume clear-inject support: mint a fresh, history-less session and make it the
1202
- * active one. This REUSES the proven #2397 re-roll seam (the same `POST /session` via
1201
+ * self-resume clear-inject support: mint a fresh, history-less session and make it the
1202
+ * active one. This REUSES the proven re-roll seam (the same `POST /session` via
1203
1203
  * `createSession`, which explicitly drops conversation history, and the same in-place
1204
1204
  * `process.meta.sessionId` swap so every reader — send/interrupt/shutdown and the SSE doors'
1205
1205
  * foreign-session filter — picks the new id up with no separate bookkeeping); it does not invent
1206
1206
  * a second session-minting mechanism.
1207
1207
  *
1208
- * Ordering/composition with the turn state machine (#2397/#2425/#2427/#2428) is ENFORCED, not
1208
+ * Ordering/composition with the turn state machine is ENFORCED, not
1209
1209
  * assumed (round 1 HIGH): the whole swap runs inside `enqueueTurn`, so a clear that arrives while
1210
- * a turn is mid-flight — which DOES happen, since the model calls relay_compact_and_resume from
1210
+ * a turn is mid-flight — which DOES happen, since the model calls humwick_compact_and_resume from
1211
1211
  * within its own synchronous `/message` turn and dispatchCommand gives agent.clearContext no
1212
1212
  * provider-input ticket — waits for that turn to settle and completes it wholly on the session
1213
1213
  * it started with before minting anything. The reactive continuation inject is separately
@@ -1216,9 +1216,9 @@ export class OpenCodeAdapter implements ProviderAdapter {
1216
1216
  * closed turn left `turnClosed` set and `suppressProviderIdle` sticky per round-5 MED-1; those
1217
1217
  * fields are deliberately NOT touched — resetting them could double-close an already-closed turn,
1218
1218
  * and any trailing SSE event belonging to the PRE-clear turn carries the OLD sessionID, which the
1219
- * #2425 foreign-session filter now drops against the NEW active id. Same reasoning leaves
1219
+ * foreign-session filter now drops against the NEW active id. Same reasoning leaves
1220
1220
  * `messageRoles`/`emittedTextParts`/`emittedToolSteps` alone: ids are server-global on this one
1221
- * process and pruned to OPENCODE_EVENT_STATE_LIMIT regardless (the #2397 re-roll leaves them
1221
+ * process and pruned to OPENCODE_EVENT_STATE_LIMIT regardless (the re-roll leaves them
1222
1222
  * too). The SSE subscription is per-server, not per-session, so the new session stays fully
1223
1223
  * observable through the existing wiring.
1224
1224
  *
@@ -1237,12 +1237,12 @@ export class OpenCodeAdapter implements ProviderAdapter {
1237
1237
  const client = process.meta?.client as OpenCodeClient | undefined;
1238
1238
  const previousSessionId = stringValue(process.meta?.sessionId);
1239
1239
  if (!client || !previousSessionId) throw new Error("OpenCode session is not ready");
1240
- // #2459 — the session swap is also an attachment-generation rotation: any open event-driven
1240
+ // the session swap is also an attachment-generation rotation: any open event-driven
1241
1241
  // terminal turn belongs to the OLD session (its matching idle carries the old sessionID and
1242
1242
  // would be dropped by the foreign-session filter, stranding runner-core busy). Close it on
1243
1243
  // the old session's truth before the swap; the orchestrator's generation check kills the
1244
1244
  // stale guest TUI on its next input/stream contact.
1245
- // #2459 round 2 (review D) — flush a buffered terminal prompt under the OLD session
1245
+ // round 2 (review D) — flush a buffered terminal prompt under the OLD session
1246
1246
  // pseudo-turn before the swap (the operator typed there; the buffer must not leak into
1247
1247
  // the new session), then close any open terminal turn as in send() takeover.
1248
1248
  this.flushBufferedPrompt(previousSessionId || undefined);
@@ -1250,7 +1250,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1250
1250
  this.statusCb({ status: "busy", reason: "provider-turn", timeline: { status: "clearing-context", timestamp: Date.now() } });
1251
1251
  let newSessionId: string;
1252
1252
  try {
1253
- newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
1253
+ newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "humwick");
1254
1254
  } catch (error) {
1255
1255
  // The old session is still intact and active — restore idle so the failed command doesn't
1256
1256
  // strand a provider-turn busy hold (same recovery shape as Codex's clearContext).
@@ -1260,7 +1260,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1260
1260
  const clearedAt = Date.now();
1261
1261
  if (process.meta) {
1262
1262
  process.meta.sessionId = newSessionId;
1263
- // #2539 Phase B — soft-steer fencing: batches accepted-but-unconsumed on the replaced
1263
+ // Phase B — soft-steer fencing: batches accepted-but-unconsumed on the replaced
1264
1264
  // session can never be consumed on the fresh one; their relay ids go BACK to the runner's
1265
1265
  // normal queue (the settlement callback fires inside sessionReplaced — never reposted into
1266
1266
  // the cleared session).
@@ -1278,12 +1278,12 @@ export class OpenCodeAdapter implements ProviderAdapter {
1278
1278
  }
1279
1279
 
1280
1280
  /**
1281
- * #2440 — native self-resume compaction via OpenCode's own `POST /session/{id}/compact`
1282
- * (mistakenly believed unavailable when #2431 shipped clear-inject only). Reuses the SAME
1281
+ * native self-resume compaction via OpenCode's own `POST /session/{id}/compact`
1282
+ * (mistakenly believed unavailable when shipped clear-inject only). Reuses the SAME
1283
1283
  * turnQueue FIFO serializer clearContext() established: `agent.compact` is dispatched
1284
1284
  * fire-and-forget with no provider-input ticket (like `agent.clearContext`), and OpenCode's
1285
1285
  * `/message` POST is synchronous, so a compact triggered from inside the model's own turn
1286
- * (relay_compact_and_resume) genuinely races an in-flight send() without it. See the
1286
+ * (humwick_compact_and_resume) genuinely races an in-flight send() without it. See the
1287
1287
  * `turnQueue` field doc for the full race shape this closes off.
1288
1288
  */
1289
1289
  async compact(process: ManagedProcess, opts?: { instructions?: string }): Promise<Record<string, unknown>> {
@@ -1299,7 +1299,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1299
1299
  * (no `couplesClearInjectContinuation`, same as its clear-inject path today), so the relay's own
1300
1300
  * reactive continuation inject (`enqueueContinuationInjectionAfterSelfResume`, src/self-resume.ts)
1301
1301
  * delivers the SAME continuation as a follow-up `agent.injectContext` once this command succeeds
1302
- * — the identical path clear-inject already relies on for OpenCode (see the #2431 test "the first
1302
+ * — the identical path clear-inject already relies on for OpenCode (see the test "the first
1303
1303
  * post-clear delivery lands in the NEW session ... through the same deliver()/send() path").
1304
1304
  */
1305
1305
  private async performCompact(process: ManagedProcess, _opts?: { instructions?: string }): Promise<Record<string, unknown>> {
@@ -1318,7 +1318,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1318
1318
  const compactedAt = Date.now();
1319
1319
  const currentContext = process.meta?.context as ContextState | undefined;
1320
1320
  if (process.meta) {
1321
- // #2440 round 2 — stamp unconditionally, mirroring swapSessionForClear's shape: a compact
1321
+ // round 2 — stamp unconditionally, mirroring swapSessionForClear's shape: a compact
1322
1322
  // that lands before any context state was ever reported (e.g. immediately after spawn) must
1323
1323
  // still record the compaction rather than silently no-oping, exactly like clearContext's
1324
1324
  // always-fresh stamp below. Falls back to clearContext's own baseline defaults (utilization
@@ -1338,13 +1338,13 @@ export class OpenCodeAdapter implements ProviderAdapter {
1338
1338
  }
1339
1339
 
1340
1340
  async shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<ShutdownOutcome> {
1341
- // #2539 Phase B — anything accepted-but-unconsumed goes back to the runner's normal queue via
1341
+ // Phase B — anything accepted-but-unconsumed goes back to the runner's normal queue via
1342
1342
  // the settlement callback (inside reofferAll) before teardown; the ledger itself never drops
1343
1343
  // an id silently — a failed callback re-inserts the entries for the next exit.
1344
1344
  const softSteerLeftovers = await this.softSteer.reofferAll();
1345
1345
  if (softSteerLeftovers.length > 0) logger.info("opencode", `soft-steer: shutdown reoffering ${softSteerLeftovers.length} relay id(s)`);
1346
1346
  this.softSteerProcess = undefined;
1347
- // #2484 round 1 review (F2/F3) — session end: force any still-pending debounced token-usage
1347
+ // round 1 review (F2/F3) — session end: force any still-pending debounced token-usage
1348
1348
  // report out now, rather than losing it to a timer that never gets to fire. Round 2 (MED-1)
1349
1349
  // — awaited, not fire-and-forget: this covers the controlled-teardown paths that call
1350
1350
  // adapter.shutdown() directly (restartProvider, shutdownProvider) without going through
@@ -1360,7 +1360,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1360
1360
  /**
1361
1361
  * Exported behavior through the adapter so fixture contracts can pin the v1.18.21 event shapes.
1362
1362
  *
1363
- * Reproduced live against a real server (#2347): the wire shapes here were captured directly
1363
+ * Reproduced live against a real server: the wire shapes here were captured directly
1364
1364
  * off `/event`, not inferred from docs. Two things the previous implementation got wrong:
1365
1365
  *
1366
1366
  * 1. `session.status` carries a nested `{ status: { type: "busy" | "idle" } }`, not a bare
@@ -1376,12 +1376,12 @@ export class OpenCodeAdapter implements ProviderAdapter {
1376
1376
  handleEvent(event: OpenCodeServerEvent, process?: ManagedProcess): void {
1377
1377
  const properties = event.properties ?? {};
1378
1378
  if (event.type === "session.status") {
1379
- // #2425 — a foreign-session event must never affect this adapter's turn/idle state, full
1379
+ // a foreign-session event must never affect this adapter's turn/idle state, full
1380
1380
  // stop, regardless of suppression state. This is what makes it safe for the two doors below
1381
1381
  // to stop enforcing PERMANENT suppression once `turnAbandoned`: an unfiltered foreign child
1382
1382
  // session's busy/idle was exactly the hazard round 7 made suppression permanent to guard
1383
1383
  // against, and this filter closes it directly instead.
1384
- // #2459 round 1 (review C) — FAIL CLOSED: whenever the active session id is known (a live
1384
+ // round 1 (review C) — FAIL CLOSED: whenever the active session id is known (a live
1385
1385
  // spawn always carries one), an event WITHOUT a sessionID is rejected too — a session-
1386
1386
  // less status cannot be positively attributed to the active conversation, and acting on
1387
1387
  // it could open/close a turn for a dead session. Test-only call sites that pass no
@@ -1397,26 +1397,26 @@ export class OpenCodeAdapter implements ProviderAdapter {
1397
1397
  }
1398
1398
  if (status === "busy") {
1399
1399
  // send() owns the turn boundary while a headless delivery is in flight — its own
1400
- // explicit busy (with its minted id) is authoritative, exactly as before #2459. Only
1400
+ // explicit busy (with its minted id) is authoritative, exactly as before. Only
1401
1401
  // busy events OUTSIDE send() can open a terminal-origin turn below.
1402
1402
  if (this.turnInFlight) return;
1403
1403
  // A terminal turn is already open — its matching idle owns the close; a duplicate
1404
1404
  // busy must not re-mint.
1405
1405
  if (this.eventTurnId) return;
1406
- // #2425 — re-arm the close guard: this real busy is evidence the turn (from
1406
+ // re-arm the close guard: this real busy is evidence the turn (from
1407
1407
  // `send()`'s perspective, already force-closed to idle by the abandonment path) is
1408
1408
  // actually still running, so the eventual matching idle must be allowed to close it
1409
1409
  // again.
1410
1410
  if (this.turnAbandoned) this.turnClosed = false;
1411
- // #2459 round 1 (review D) — #2397 round-7 echo guard, restored to PERMANENT and made
1411
+ // round 1 (review D) — round-7 echo guard, restored to PERMANENT and made
1412
1412
  // POSITIVE (no time heuristic): once any headless turn has run on this process, a busy
1413
1413
  // is admitted ONLY when a fresh terminal user prompt armed `pendingTerminalPrompt` (the
1414
1414
  // TUI's POST creates the user message before the turn's busy flows). The buffered
1415
1415
  // own-turn echo has no fresh user message by construction — at ANY delay — so it stays
1416
- // swallowed. (#2425 abandonment keeps this door open — those server events are the only
1416
+ // swallowed. (abandonment keeps this door open — those server events are the only
1417
1417
  // remaining proof of the turn's real state.)
1418
1418
  if (!this.turnAbandoned && this.headlessTurnSeen && !this.pendingTerminalPrompt) return;
1419
- // #2459 round 1 (review D) — a genuinely new, terminal-origin turn: mint its provider turn id and hand it
1419
+ // round 1 (review D) — a genuinely new, terminal-origin turn: mint its provider turn id and hand it
1420
1420
  // to runner-core (which keys currentTurnId off this busy), then associate every user/
1421
1421
  // assistant/tool message id of the turn with it (see activeTurnId()) until the matching
1422
1422
  // idle closes it. Un-stick the idle suppression so this turn's own idle can pass.
@@ -1425,7 +1425,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1425
1425
  this.eventTurnText = [];
1426
1426
  this.eventTurnReasoning = new Map();
1427
1427
  this.suppressProviderIdle = false;
1428
- // #2459 round 2 (review D) — a prompt buffered by the [user message → user part →
1428
+ // round 2 (review D) — a prompt buffered by the [user message → user part →
1429
1429
  // busy] ordering is emitted NOW under the freshly minted id, so prompt → trace →
1430
1430
  // final-response all share one opencode-turn-* id.
1431
1431
  this.flushBufferedPrompt(this.eventTurnId);
@@ -1434,16 +1434,16 @@ export class OpenCodeAdapter implements ProviderAdapter {
1434
1434
  if (status === "idle") {
1435
1435
  // send() closes its own turn deterministically (closeSuppressedTurn / failTurn / the
1436
1436
  // finally) — an SSE idle while a headless delivery is in flight stays swallowed, as
1437
- // since #2397 round 2.
1437
+ // since round 2.
1438
1438
  if (this.turnInFlight) return;
1439
- // #2459 round 2 (review D) — bounded fallback: an active-session idle with a prompt
1439
+ // round 2 (review D) — bounded fallback: an active-session idle with a prompt
1440
1440
  // still buffered means the turn ended without us seeing its busy (or it was itself an
1441
1441
  // echo). Flush under the session pseudo-turn so the operator's input is never stranded
1442
1442
  // in the buffer; RunnerCore's injection dedup guards the chat-echo case.
1443
1443
  if (this.bufferedTerminalPrompt && !this.eventTurnId) {
1444
1444
  this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
1445
1445
  }
1446
- // #2459 — the matching close of an event-driven (terminal) turn: emit the turn-final
1446
+ // the matching close of an event-driven (terminal) turn: emit the turn-final
1447
1447
  // response accumulated from the turn's live assistant text parts (mirroring send()'s
1448
1448
  // emitTurnFinalResponse → close ordering) and then the idle edge that closes
1449
1449
  // runner-core's turn.
@@ -1451,17 +1451,17 @@ export class OpenCodeAdapter implements ProviderAdapter {
1451
1451
  this.closeEventTurn();
1452
1452
  return;
1453
1453
  }
1454
- // #2397 round 2 (F4) — a transient idle from a failed attempt, still mid-retry-loop.
1455
- // #2397 round 7 review (MED-2/LOW-1) — round 6's one-shot reopen (lower the flag on the
1454
+ // round 2 (F4) — a transient idle from a failed attempt, still mid-retry-loop.
1455
+ // round 7 review (MED-2/LOW-1) — round 6's one-shot reopen (lower the flag on the
1456
1456
  // FIRST post-close idle, keyed on `turnInFlight`) let a SECOND idle in the same trailing-
1457
1457
  // echo window pass through and double-close. There is no legitimate turn boundary this SSE
1458
1458
  // door needs to report that `send()`/`closeSuppressedTurn`/`failTurn` don't already emit
1459
1459
  // explicitly for a DETERMINISTIC outcome (see the field comment) — so there is no reopen on
1460
1460
  // that path: suppression stays permanent for any turn `send()` itself closed knowingly.
1461
- // #2459 — EXCEPT the terminal-origin path above: an idle outside send() with no event
1461
+ // EXCEPT the terminal-origin path above: an idle outside send with no event
1462
1462
  // turn open is still an echo/stray (the busy that minted a turn would have set one), so
1463
1463
  // the permanent suppression holds exactly as before for chat turns.
1464
- // #2425 — the one exception is the same `turnAbandoned` escape hatch as the busy door
1464
+ // the one exception is the same `turnAbandoned` escape hatch as the busy door
1465
1465
  // above; `turnClosed` (not `suppressProviderIdle`) is what still prevents a double-close
1466
1466
  // here — either the abandonment path's own forced idle already closed it (this real idle
1467
1467
  // is then a no-op confirmation), or a real busy re-armed it above and this is the genuine
@@ -1489,15 +1489,15 @@ export class OpenCodeAdapter implements ProviderAdapter {
1489
1489
  this.messageRoles.set(id, role);
1490
1490
  pruneToLimit(this.messageRoles, OPENCODE_EVENT_STATE_LIMIT);
1491
1491
  }
1492
- // #2427 — the SSE echo of a finalized assistant message carries the SAME per-message
1493
- // `tokens` totals the synchronous POST resolves with (send()'s #2355 report), but on a long
1492
+ // the SSE echo of a finalized assistant message carries the SAME per-message
1493
+ // `tokens` totals the synchronous POST resolves with (send's report), but on a long
1494
1494
  // turn that POST can die client-side or stay in-flight forever, so usage reported ONLY from
1495
- // it never lands (#2427's blank context-window). Report from this door instead: it fires
1495
+ // it never lands. Report from this door instead: it fires
1496
1496
  // DURING the turn regardless of POST survival, mirroring how Codex consumes its
1497
1497
  // `thread/tokenUsage/updated` push. Idempotent with the POST path — last write wins and both
1498
1498
  // doors feed the same openCodeContextState no-signal rules (0/unparsable → no-op), so neither
1499
- // can regress a good reading — and same-session-filtered like #2425's session.status door.
1500
- // #2459 round 1 (review C) — FAIL CLOSED: with the active session id known, a usage echo
1499
+ // can regress a good reading — and same-session-filtered like the session.status door.
1500
+ // round 1 (review C) — FAIL CLOSED: with the active session id known, a usage echo
1501
1501
  // WITHOUT a sessionID is rejected too (an unattributable reading must never overwrite the
1502
1502
  // active session's context state). Test-only process-less call sites keep the lenient path.
1503
1503
  const sessionID = stringValue(properties.sessionID);
@@ -1508,9 +1508,9 @@ export class OpenCodeAdapter implements ProviderAdapter {
1508
1508
  const model = stringValue(process.meta?.model);
1509
1509
  this.reportContextUsage(process, model, tokens);
1510
1510
  }
1511
- // #2484 — same door, reused for the spine report: model/createdAt prefer the message's
1511
+ // same door, reused for the spine report: model/createdAt prefer the message's
1512
1512
  // own info (round 1 review F5/F6) over the session's configured model / receipt time.
1513
- // #3351 — outside the context guard so a finished message without counts reports unknown.
1513
+ // outside the context guard so a finished message without counts reports unknown.
1514
1514
  if (process && role === "assistant" && id) captureOpenCodeApiUsage(this.apiBudgets.get(process), info, sessionID ?? activeSessionId, stringValue(process.meta?.model), id);
1515
1515
  if (process && role === "assistant" && id && !sessionUnattributable) {
1516
1516
  scheduleOpenCodeTokenUsage(this.tokenUsagePoster, {
@@ -1523,7 +1523,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1523
1523
  finished: openCodeMessageFinished(info),
1524
1524
  });
1525
1525
  }
1526
- // #2459 round 1 (review D) — ARM the terminal-turn gate: a FIRST-SEEN user message on the
1526
+ // round 1 (review D) — ARM the terminal-turn gate: a FIRST-SEEN user message on the
1527
1527
  // ACTIVE session observed outside send() is the event-source proof that a human typed into
1528
1528
  // the attached TUI. The next active-session busy is then admitted as terminal-origin even
1529
1529
  // after a headless close (whose own buffered echo carries no fresh user message — its user
@@ -1531,7 +1531,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1531
1531
  // from arming). Fail-closed session-filtered like the usage read above, never armed while
1532
1532
  // send() is in flight (the chat delivery's own user echo must not open a terminal turn
1533
1533
  // behind send()'s back) nor while a terminal turn is already open.
1534
- // #2539 Phase B — soft-steer consumption door: an assistant message whose info.parentID
1534
+ // Phase B — soft-steer consumption door: an assistant message whose info.parentID
1535
1535
  // equals a soft batch's user row id is positive proof the active loop CONSUMED that batch
1536
1536
  // (the loop reloads full messages each step and re-parents subsequent assistant steps to
1537
1537
  // the newest user row — live-verified on pinned 1.18.21). FIFO settle: the matched batch
@@ -1550,19 +1550,19 @@ export class OpenCodeAdapter implements ProviderAdapter {
1550
1550
  const firstSeen = !this.seenUserMessageIds.has(id);
1551
1551
  this.seenUserMessageIds.add(id);
1552
1552
  pruneToLimit(this.seenUserMessageIds, OPENCODE_EVENT_STATE_LIMIT);
1553
- // #2539 Phase B — relay's OWN soft-steer user rows never arm the terminal-origin gate:
1553
+ // Phase B — relay's OWN soft-steer user rows never arm the terminal-origin gate:
1554
1554
  // they are first-seen user messages OUTSIDE send() (the whole point of the lane), and
1555
1555
  // arming here would misclassify the wake turn (or any user's next turn) as terminal.
1556
1556
  const terminalOrigin = firstSeen && !this.turnInFlight && !this.eventTurnId && !sessionUnattributable && !this.softSteer.isTrackedUserId(id);
1557
1557
  if (terminalOrigin) this.pendingTerminalPrompt = true;
1558
- if (terminalOrigin) adoptOpenCodeTerminalModel(process?.meta, info); // #2659 — keep a model picked in the TUI
1558
+ if (terminalOrigin) adoptOpenCodeTerminalModel(process?.meta, info); // keep a model picked in the TUI
1559
1559
  }
1560
1560
  return;
1561
1561
  }
1562
1562
  if (event.type === "message.part.updated") {
1563
1563
  const part = isRecord(properties.part) ? properties.part : undefined;
1564
1564
  if (!part) return;
1565
- // #2534 — acceptance door: a persisted user text part whose text equals the armed
1565
+ // acceptance door: a persisted user text part whose text equals the armed
1566
1566
  // context-carrying outbound prompt is positive proof OpenCode ACCEPTED it. Consuming here
1567
1567
  // marks the first-turn markers MID-TURN — seconds after dispatch — so a first turn that
1568
1568
  // later runs past the whole-turn deadline can never leave them false (the await-then-mark
@@ -1570,33 +1570,33 @@ export class OpenCodeAdapter implements ProviderAdapter {
1570
1570
  // text identity are fail-closed inside consumeOpenCodeContextAcceptance: a stale-session
1571
1571
  // echo or an operator-typed prompt never marks.
1572
1572
  consumeOpenCodeContextAcceptance(process, stringValue(properties.sessionID), stringValue(part.text));
1573
- // #2389 — ANY part on an assistant message is proof the model produced something, including
1573
+ // ANY part on an assistant message is proof the model produced something, including
1574
1574
  // the tool parts this adapter does not mirror (their internal shape is not pinned, so they are
1575
1575
  // reported as bare activity rather than published as a half-understood trace step). Without
1576
1576
  // this a tools-only OpenCode turn emits no session events at all and reads as empty.
1577
- // #2397 round 3 review (HIGH-1r) — EXCEPT a bare step-start/step-finish bookkeeping part,
1577
+ // round 3 review (HIGH-1r) — EXCEPT a bare step-start/step-finish bookkeeping part,
1578
1578
  // which the server emits on every turn regardless of outcome. `isOutputPart` is the same
1579
1579
  // predicate `completionProducedOutput` uses on the POST-body door (HIGH-1); this is the SSE
1580
1580
  // door, and both must agree or the issue's own incident (a step-start-only turn) still
1581
- // discharges #2389's escalation through whichever door fires first. `noteOutput()` is called
1581
+ // discharges the escalation through whichever door fires first. `noteOutput` is called
1582
1582
  // with no event here (onTurnActivity takes no args), so MED-5's `diagnostic` filter cannot
1583
1583
  // see this path at all — it must be excluded at the source instead.
1584
1584
  if (isOutputPart(part) && this.messageRoles.get(stringValue(part.messageID) ?? "") === "assistant") this.turnActivityCb();
1585
- // #2459 round 2 (review D) — no-busy-seen fallback, bounded: when trace activity
1585
+ // round 2 (review D) — no-busy-seen fallback, bounded: when trace activity
1586
1586
  // (tool/narration/reasoning) arrives while a terminal prompt is still buffered and no
1587
1587
  // terminal turn ever opened, flush the prompt FIRST under the same session pseudo-turn
1588
1588
  // the trace will use, keeping prompt→trace grouped instead of stranding the buffer.
1589
1589
  if (this.bufferedTerminalPrompt && !this.eventTurnId && !this.turnInFlight) {
1590
1590
  this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
1591
1591
  }
1592
- // #2354 — surface tool calls in chat/timeline the way Codex does: decode the ToolPart and
1592
+ // surface tool calls in chat/timeline the way Codex does: decode the ToolPart and
1593
1593
  // emit a running/completed/failed/cancelled session step (upserted in place by stepId). This
1594
1594
  // must run before the text-only early-return below, which used to drop every non-text part.
1595
1595
  if (stringValue(part.type) === "tool") {
1596
1596
  this.emitToolStep(part, process);
1597
1597
  return;
1598
1598
  }
1599
- // #2605 — surface the model's thinking as a `reasoning` trace row the way Codex/Claude do:
1599
+ // surface the model's thinking as a `reasoning` trace row the way Codex/Claude do:
1600
1600
  // decode the ReasoningPart and emit it (upserted in place by stepId = stable part id). Runs
1601
1601
  // before the text-only early-return below, which is also what used to drop these parts.
1602
1602
  if (stringValue(part.type) === "reasoning") {
@@ -1609,11 +1609,11 @@ export class OpenCodeAdapter implements ProviderAdapter {
1609
1609
  const text = stringValue(part.text);
1610
1610
  const finalized = isRecord(part.time) && part.time.end !== undefined;
1611
1611
  if (!partId || !messageId || !text || !finalized) return;
1612
- // #2459 — a finalized role:user text part on the ACTIVE server session is a prompt typed
1612
+ // a finalized role:user text part on the ACTIVE server session is a prompt typed
1613
1613
  // into an attached web-terminal TUI: mirror it into chat as a terminal-origin prompt.
1614
1614
  // RunnerCore's recordInjectedPrompt already consumes the echo of every Relay-injected
1615
1615
  // prompt (chat deliveries), so terminal prompts appear once while dashboard-injected
1616
- // prompts do not duplicate. #2459 round 1 (review C) — FAIL CLOSED: with the active
1616
+ // prompts do not duplicate. Round 1 (review C) — FAIL CLOSED: with the active
1617
1617
  // session id known, a part WITHOUT a sessionID is rejected too — after a clearContext()
1618
1618
  // or retry re-roll an unattributable/old-session prompt belongs (at best) to a dead
1619
1619
  // conversation, and the generation boundary kills that stale TUI anyway.
@@ -1624,7 +1624,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1624
1624
  if (this.emittedTextParts.has(partId)) return;
1625
1625
  this.emittedTextParts.add(partId);
1626
1626
  pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
1627
- // #2459 round 2 (review D) — turn-id ordering. While send() is in flight, emit
1627
+ // round 2 (review D) — turn-id ordering. While send is in flight, emit
1628
1628
  // immediately under send()'s id (RunnerCore dedups the chat-injection echo). While a
1629
1629
  // terminal turn is open, emit under its minted id. Otherwise — the [user message →
1630
1630
  // user part → busy] wire ordering, where the TUI's POST creates the user message
@@ -1651,7 +1651,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1651
1651
  if (this.emittedTextParts.has(partId)) return;
1652
1652
  this.emittedTextParts.add(partId);
1653
1653
  pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
1654
- // #2459 — while an event-driven terminal turn is open, its finalized assistant text also
1654
+ // while an event-driven terminal turn is open, its finalized assistant text also
1655
1655
  // accumulates toward the turn-final response emitted by closeEventTurn().
1656
1656
  if (this.eventTurnId) this.eventTurnText.push(text.trim());
1657
1657
  const turnId = this.activeTurnId() ?? stringValue(process?.meta?.sessionId);
@@ -1660,7 +1660,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1660
1660
  }
1661
1661
 
1662
1662
  /**
1663
- * #2605 — decode one ReasoningPart and emit its trace row, with in-place upsert semantics: a
1663
+ * decode one ReasoningPart and emit its trace row, with in-place upsert semantics: a
1664
1664
  * reasoning part GROWS as the model thinks (SSE emits the part repeatedly, each frame carrying
1665
1665
  * the FULL text so far), so a frame for an already-emitted part id re-emits the current full
1666
1666
  * body — the server upserts it into the same row by stepId. Only assistant-message reasoning
@@ -1682,7 +1682,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1682
1682
  if (prev === step.body) return;
1683
1683
  this.emittedReasoningParts.set(step.stepId, step.body);
1684
1684
  pruneToLimit(this.emittedReasoningParts, OPENCODE_EVENT_STATE_LIMIT);
1685
- // #2605 — record the latest snapshot per part for the event-turn close below. Unconditional
1685
+ // record the latest snapshot per part for the event-turn close below. Unconditional
1686
1686
  // (not just on emit): the close's reconcile must see the newest body even for frames that
1687
1687
  // were live-emitted, so it can compare per-part and emit nothing already traced.
1688
1688
  if (this.eventTurnId) this.eventTurnReasoning.set(step.stepId, step.body);
@@ -1691,14 +1691,14 @@ export class OpenCodeAdapter implements ProviderAdapter {
1691
1691
  }
1692
1692
 
1693
1693
  /**
1694
- * #2354 — decode one ToolPart and emit its session step, with dedup + terminal-lock: a
1694
+ * decode one ToolPart and emit its session step, with dedup + terminal-lock: a
1695
1695
  * duplicate frame or a reordered update (a late `running` after `completed`) is dropped rather
1696
1696
  * than regressing or double-appending the row. Only assistant-message tool parts are surfaced.
1697
1697
  * `stepId` = the stable part id, so the server upserts running→completed IN PLACE (one row),
1698
1698
  * exactly like Codex's app-server item-id upsert.
1699
1699
  */
1700
1700
  private emitToolStep(part: Record<string, unknown>, process?: ManagedProcess): void {
1701
- // #2354 review (LOW) — fail CLOSED: a tool part lacking a messageID (so its role can't be
1701
+ // review (LOW) — fail CLOSED: a tool part lacking a messageID (so its role can't be
1702
1702
  // confirmed assistant) is dropped, not surfaced.
1703
1703
  const messageId = stringValue(part.messageID);
1704
1704
  if (!messageId || this.messageRoles.get(messageId) !== "assistant") return;
@@ -1712,18 +1712,18 @@ export class OpenCodeAdapter implements ProviderAdapter {
1712
1712
  if (step.params !== undefined) this.toolStepParams.set(step.stepId, step.params);
1713
1713
  const params = step.params !== undefined ? step.params : this.toolStepParams.get(step.stepId);
1714
1714
  pruneToLimit(this.toolStepParams, OPENCODE_EVENT_STATE_LIMIT);
1715
- // #2459 — tool steps attach to the live turn id (send()'s while a headless delivery runs,
1715
+ // tool steps attach to the live turn id (send's while a headless delivery runs,
1716
1716
  // the event-driven terminal turn otherwise), never to a stale prior headless turn id.
1717
1717
  this.sessionEventCb(openCodeToolSessionEvent({ ...step, params }, this.activeTurnId() ?? stringValue(process?.meta?.sessionId)));
1718
1718
  }
1719
1719
 
1720
1720
  /**
1721
- * #2413 — mark the turn-final assistant text as a `response` (not `narration`), so the runner's
1721
+ * mark the turn-final assistant text as a `response` (not `narration`), so the runner's
1722
1722
  * Final Response capture identifies it, auto-forwards an OpenCode child's report to its
1723
1723
  * coordinator, and the chat styles it apart from reasoning — parity with Claude/Codex, which
1724
1724
  * OpenCode lacked entirely (everything read as thinking, nothing auto-forwarded).
1725
1725
  *
1726
- * #2605 — also emits the turn-final `reasoning` rows from the SAME completed assistant message
1726
+ * also emits the turn-final `reasoning` rows from the SAME completed assistant message
1727
1727
  * (`completion.reasoningSteps`, via `openCodeTurnFinalEvents` in opencode-tool-events.ts), so
1728
1728
  * the model's thinking survives in the trace even when the SSE reasoning frames were never
1729
1729
  * observed (a POST-only reader, a dropped subscription, a turn whose frames raced the close):
@@ -1734,7 +1734,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1734
1734
  * emitted-body record the SSE door shares, scoped to part ids — never a global body pool.
1735
1735
  *
1736
1736
  * The text is read from the synchronous POST's completed assistant message (`completion.text`,
1737
- * ground truth per #2389) rather than reconstructed from SSE. `confidence:"definitive"` because
1737
+ * ground truth) rather than reconstructed from SSE. `confidence:"definitive"` because
1738
1738
  * the POST returning IS the provider-native turn-end edge; `final:true` is what makes it a
1739
1739
  * turn-final capture downstream. The same text already streamed live as `narration` (per text
1740
1740
  * part); the dashboard's per-turn narration/response dedup (keyed on the shared `turnId`)
@@ -1747,7 +1747,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1747
1747
  for (const event of openCodeTurnFinalEvents(completion, turnId, emittedBodies)) this.emitReasoningFinal(event);
1748
1748
  }
1749
1749
 
1750
- /** #2605 — ledger write for a turn-final reasoning row, shared by both turn-close seams. */
1750
+ /** ledger write for a turn-final reasoning row, shared by both turn-close seams. */
1751
1751
  private emitReasoningFinal(event: ProviderSessionEvent): void {
1752
1752
  if (event.stepId) {
1753
1753
  this.emittedReasoningParts.set(event.stepId, event.body);
@@ -1757,9 +1757,9 @@ export class OpenCodeAdapter implements ProviderAdapter {
1757
1757
  }
1758
1758
 
1759
1759
  /**
1760
- * #2397 — turn-ending PROVIDER errors (e.g. an OpenRouter 429 on a throttled preview model)
1760
+ * turn-ending PROVIDER errors (e.g. an OpenRouter 429 on a throttled preview model)
1761
1761
  * are read off THIS synchronous POST's response body (`completion.error`, via
1762
- * `openCodeCompletion`), not off a separate SSE listener. #2389 already established that this
1762
+ * `openCodeCompletion`), not off a separate SSE listener. Already established that this
1763
1763
  * response is ground truth and deterministic (no dependency on an SSE frame's timing); reusing
1764
1764
  * that one seam here — instead of also inspecting `message.updated`'s `info.error` — keeps
1765
1765
  * OpenCode's turn-outcome detection in exactly one place, as the standing one-seam rule in this
@@ -1772,13 +1772,13 @@ export class OpenCodeAdapter implements ProviderAdapter {
1772
1772
  */
1773
1773
  /**
1774
1774
  * @returns `"ok"` when the turn ended in output (or a positively-parsed empty completion that
1775
- * is not itself a failure — #2389's normal empty-turn path), `"failed"` only when `failTurn` was
1776
- * called — i.e. the message provoked a terminal, unrecoverable failure — and `"held"` (#2619)
1775
+ * is not itself a failure — the normal empty-turn path), `"failed"` only when `failTurn` was
1776
+ * called — i.e. the message provoked a terminal, unrecoverable failure — and `"held"`
1777
1777
  * when the provider refused the turn and it ended in the rate-limit hold: not delivered, not
1778
- * failed. #2397 round 3 (MED-3): the caller uses this to decide whether the message may be
1778
+ * failed. Round 3 (MED-3): the caller uses this to decide whether the message may be
1779
1779
  * marked delivered/read.
1780
1780
  *
1781
- * #2397 round 5 review (INFO) — this method is non-reentrant BY CONSTRUCTION, and the
1781
+ * round 5 review (INFO) — this method is non-reentrant BY CONSTRUCTION, and the
1782
1782
  * `suppressProviderIdle`/`turnInFlight`/`turnClosed`/`turnAbandoned` instance fields it owns
1783
1783
  * assume that: every `adapter.deliver`/`deliverInitialPrompt` call site in runner-core runs inside
1784
1784
  * `providerInput.run`, which serializes them, so a second `send()` call never starts before the
@@ -1787,44 +1787,44 @@ export class OpenCodeAdapter implements ProviderAdapter {
1787
1787
  */
1788
1788
  private async send(process: ManagedProcess, text: string, inputObserver?: ProviderInputObserver): Promise<OpenCodeSendOutcome> {
1789
1789
  await this.apiBudgets.get(process)?.prepare();
1790
- // #2539 Phase B — retain the live process for the soft-steer wake hook (see its doc).
1790
+ // Phase B — retain the live process for the soft-steer wake hook (see its doc).
1791
1791
  this.softSteerProcess = process;
1792
1792
  let attempt = 0;
1793
1793
  let rerolled = false;
1794
1794
  let lastFailureDetail: string | undefined;
1795
1795
  let lastFailureErrorType: string | undefined;
1796
- // #2397 round 4 review (F1/F2) — round 3 only raised this flag on a failed, retryable attempt,
1796
+ // round 4 review (F1/F2) — round 3 only raised this flag on a failed, retryable attempt,
1797
1797
  // so it stayed false while ATTEMPT 1's own POST was in flight. On a fast (e.g. loopback) server
1798
1798
  // the FAILED-attempt idle SSE frame routinely beats that attempt's own POST response — the
1799
1799
  // issue's own captured incident shape, a 429 on the very first attempt — so an unsuppressed
1800
- // attempt-1 idle still closed the turn early and, via runner-core's #2389 seam, marked the
1800
+ // attempt-1 idle still closed the turn early and, via runner-core's seam, marked the
1801
1801
  // linked Task done while the retry loop was still running. Raised for the WHOLE call (below,
1802
1802
  // past the readiness guard — round 5 LOW-2) so there is no "attempt 1 is unguarded" window.
1803
1803
  // Every return path must therefore close the turn itself — `closeSuppressedTurn` does that,
1804
1804
  // idempotently per call (`turnClosed`) — see the `finally`.
1805
- // #2459 — close a still-open event-driven terminal turn BEFORE the reset below: a chat
1805
+ // close a still-open event-driven terminal turn BEFORE the reset below: a chat
1806
1806
  // delivery taking over the boundary must end the terminal turn first (its own SSE idle, if
1807
1807
  // it even arrived, would be swallowed under this call's suppression), and closeEventTurn
1808
1808
  // sets `turnClosed` — resetting it after the close keeps THIS call's deterministic close
1809
1809
  // live. Order: terminal idle clears the terminal turn id, then this call's busy mints its own.
1810
1810
  if (this.eventTurnId) this.closeEventTurn();
1811
- // #2459 round 1 (review D) — a chat delivery also consumes any armed terminal prompt: a
1811
+ // round 1 (review D) — a chat delivery also consumes any armed terminal prompt: a
1812
1812
  // stale arm surviving this call would wrongly admit this call's own buffered busy echo
1813
1813
  // after the close. (This call's POST user echo is recorded in seenUserMessageIds live, so
1814
1814
  // its late duplicate can never re-arm — see the message.updated door.)
1815
1815
  this.pendingTerminalPrompt = false;
1816
- // #2459 round 2 (review D) — a buffered terminal prompt flushed under the session
1816
+ // round 2 (review D) — a buffered terminal prompt flushed under the session
1817
1817
  // pseudo-turn BEFORE the delivery takes over: the operator typed first, and the buffer
1818
1818
  // must never leak across the headless turn boundary. Fallback id is the session
1819
1819
  // pseudo-turn — never this call's (or any prior headless) sendTurnId.
1820
1820
  this.flushBufferedPrompt(stringValue(process.meta?.sessionId) || undefined);
1821
1821
  this.turnClosed = false;
1822
- // #2425 — reset at the top of every call: a stray leftover event from a truly-abandoned
1822
+ // reset at the top of every call: a stray leftover event from a truly-abandoned
1823
1823
  // PREVIOUS turn must not be allowed to affect THIS new call's turn boundary (see the field
1824
1824
  // comment).
1825
1825
  this.turnAbandoned = false;
1826
1826
  this.serverRetryWall = undefined;
1827
- // #2413 review (HIGH-1) — one fresh turn id per send() (stable across this call's retries/
1827
+ // review (HIGH-1) — one fresh turn id per send (stable across this call's retries/
1828
1828
  // rerolls, which are ONE logical turn), so every send is distinct and the busy edge below hands
1829
1829
  // runner-core this exact id as its currentTurnId.
1830
1830
  this.sendTurnId = `opencode-turn-${crypto.randomUUID()}`;
@@ -1834,27 +1834,27 @@ export class OpenCodeAdapter implements ProviderAdapter {
1834
1834
  const sessionId = stringValue(process.meta?.sessionId);
1835
1835
  const model = stringValue(process.meta?.model);
1836
1836
  if (!client || !sessionId || !model) throw new Error("OpenCode session is not ready with a hub-resolved model");
1837
- // #2397 round 5 review (LOW-2) — raised only once we're actually about to run an attempt
1837
+ // round 5 review (LOW-2) — raised only once we're actually about to run an attempt
1838
1838
  // (past the guard above), so a "session not ready" throw never emits a close for a turn
1839
1839
  // that never started (never emitted its own busy edge below).
1840
1840
  this.turnInFlight = true;
1841
1841
  this.suppressProviderIdle = true;
1842
- // #2459 round 1 (review D) — a headless turn now exists on this process: from here on,
1842
+ // round 1 (review D) — a headless turn now exists on this process: from here on,
1843
1843
  // terminal-origin busies are admitted only through the armed fresh-prompt gate.
1844
1844
  this.headlessTurnSeen = true;
1845
1845
  const providerState = attempt > 0 || rerolled
1846
1846
  ? openCodeUpstreamRetryState({ attempt, rerolled, message: lastFailureDetail ?? "retrying", errorType: lastFailureErrorType })
1847
1847
  : undefined;
1848
1848
  this.statusCb({ status: "busy", reason: "provider-turn", ...(this.sendTurnId ? { id: this.sendTurnId } : {}), ...(providerState ? { providerState } : {}) });
1849
- // #2389 — this endpoint is synchronous: it returns the COMPLETED assistant message. That
1849
+ // this endpoint is synchronous: it returns the COMPLETED assistant message. That
1850
1850
  // body is ground truth about whether the model produced anything, so it feeds the same
1851
1851
  // evidence funnel the cross-provider ledger reads, rather than being discarded as it was
1852
1852
  // during the incident. Only a positively-parsed non-empty completion counts; an
1853
1853
  // unrecognised body says nothing.
1854
- // #2418/#2532 — compose once outside this retry loop, but the durable first-turn markers
1854
+ // compose once outside this retry loop, but the durable first-turn markers
1855
1855
  // are consumed by PROOF OF ACCEPTANCE, not by this await's return: `sendOpenCodeRelayTurn`
1856
1856
  // arms the SSE acceptance door before dispatch and reconciles against persisted session
1857
- // messages if the await itself fails, so an accepted-but-interrupted turn (e.g. the #2425
1857
+ // messages if the await itself fails, so an accepted-but-interrupted turn (e.g. the
1858
1858
  // whole-turn abort) still retains the consumed flags instead of re-prepending the briefing
1859
1859
  // on every queued message. A readiness guard or rejected transport request has not reached
1860
1860
  // the provider, so RunnerCore's retry must still include the briefing. Retryable resolved
@@ -1869,7 +1869,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1869
1869
  throw error;
1870
1870
  }
1871
1871
  if (this.holdOnServerRetryWall()) return "held";
1872
- // #2355 — every completion (success, a produced-output error, or a still-retrying error)
1872
+ // every completion (success, a produced-output error, or a still-retrying error)
1873
1873
  // carries this turn's fresh token usage; report it to Relay's normal context telemetry path
1874
1874
  // the same way Codex's `codexTokenUsageContext` does, rather than only on the happy path.
1875
1875
  this.reportContextUsage(process, model, completion.tokens);
@@ -1879,14 +1879,14 @@ export class OpenCodeAdapter implements ProviderAdapter {
1879
1879
  if (completion.error && completion.error.name !== "MessageAbortedError") {
1880
1880
  const error = completion.error;
1881
1881
  const detail = `${error.name}${error.statusCode !== undefined ? ` ${error.statusCode}` : ""}: ${error.message}`;
1882
- // #2397 round 2 (F1/F2) — `completion.error` used to be checked independently of any
1882
+ // round 2 (F1/F2) — `completion.error` used to be checked independently of any
1883
1883
  // output the turn already produced. A turn that ran tool steps (a commit, say) and THEN
1884
1884
  // hit a retryable error was resent into a brand-new session with no memory of that work
1885
1885
  // — re-executing it. And a non-retryable error on a turn that DID produce a reply (e.g.
1886
1886
  // MessageOutputLengthError after a long, otherwise-successful answer) took a healthy
1887
1887
  // agent offline. Retrying or failing must only ever happen when the turn produced
1888
1888
  // NOTHING; with output present, report the error and let the turn end normally.
1889
- // #2397 round 3 (HIGH-1) — `completionProducedOutput` excludes bare step-start/finish
1889
+ // round 3 (HIGH-1) — `completionProducedOutput` excludes bare step-start/finish
1890
1890
  // bookkeeping parts. The issue's own captured incident (one bare step-start part, zero
1891
1891
  // tokens, a 429) used to satisfy the old parts>0 check and take THIS branch — no retry,
1892
1892
  // no error status, reported as a clean success. It must fall through to the retry path.
@@ -1897,10 +1897,10 @@ export class OpenCodeAdapter implements ProviderAdapter {
1897
1897
  this.emitTurnFinalResponse(process, completion);
1898
1898
  this.closeSuppressedTurn();
1899
1899
  this.turnActivityCb();
1900
- this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
1900
+ this.rerolledThisHoldEpisode = false; // the provider took a turn: the hold episode is over
1901
1901
  return "ok";
1902
1902
  }
1903
- // #2619 — an explicit account usage/quota wall: in-session retries and a re-roll cannot
1903
+ // an explicit account usage/quota wall: in-session retries and a re-roll cannot
1904
1904
  // clear it (the re-roll would only drop the conversation). Hold now on the same session;
1905
1905
  // the relay resumes us. Checked BEFORE the non-retryable exit: a provider may send a
1906
1906
  // quota wall as a non-retryable 403 ("Quota exceeded; limit will reset tomorrow").
@@ -1908,7 +1908,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1908
1908
  this.holdTurnOnProviderLimit(error, detail);
1909
1909
  return "held";
1910
1910
  }
1911
- // #2619 — throttle wording is retryable whatever the provider flagged (zai sends a
1911
+ // throttle wording is retryable whatever the provider flagged (zai sends a
1912
1912
  // non-retryable 403 "Rate limit exceeded"): retry, then hold like any throttle. A real
1913
1913
  // auth/model refusal still fails here.
1914
1914
  if (!error.isRetryable && !isProviderThrottle(error)) {
@@ -1920,10 +1920,10 @@ export class OpenCodeAdapter implements ProviderAdapter {
1920
1920
  const delayMs = openCodeErrorRetryDelayMs(attempt);
1921
1921
  lastFailureDetail = detail;
1922
1922
  lastFailureErrorType = error.errorType;
1923
- // #2397 round 3 (MED-5) — `diagnostic: true`: this narrates the ADAPTER's own retry
1923
+ // round 3 (MED-5) — `diagnostic: true`: this narrates the ADAPTER's own retry
1924
1924
  // mechanics, not anything the model produced. Left uncredited it would let
1925
1925
  // emptyTurns.noteOutput() (runner-core's onSessionEvent wiring) read a retry storm
1926
- // that ultimately fails as forward progress, cancelling #2389's input-armed escalation
1926
+ // that ultimately fails as forward progress, cancelling the input-armed escalation
1927
1927
  // that is the only thing left to wake the spawn parent once failTurn takes the agent
1928
1928
  // offline below.
1929
1929
  this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed (${detail}); retrying in ${Math.round(delayMs / 1000)}s (attempt ${attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…` });
@@ -1932,27 +1932,27 @@ export class OpenCodeAdapter implements ProviderAdapter {
1932
1932
  await this.sleepImpl(delayMs);
1933
1933
  continue;
1934
1934
  }
1935
- // #2619 — a provider overload (5xx) is provider-wide, not a broken session: a re-roll
1935
+ // a provider overload (5xx) is provider-wide, not a broken session: a re-roll
1936
1936
  // would only drop the history before the same wall. Hold on the SAME session.
1937
1937
  if (isProviderOverload(error)) {
1938
1938
  this.holdTurnOnProviderLimit(error, `${detail} (after ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} in-session retries)`);
1939
1939
  return "held";
1940
1940
  }
1941
- // Retry exhausted for the CURRENT session. Bimodal-per-session evidence (#2397 issue
1941
+ // Retry exhausted for the CURRENT session. Bimodal-per-session evidence (issue
1942
1942
  // comment) says an ambiguous 429 can be a per-session failure — re-roll to a fresh one
1943
- // once. #2619 — at most ONCE PER HOLD EPISODE (until a turn goes through again), so a
1943
+ // once. At most ONCE PER HOLD EPISODE (until a turn goes through again), so a
1944
1944
  // long wall re-rolls once and then holds: history loss stays bounded to one attempt.
1945
1945
  if (!rerolled && !this.rerolledThisHoldEpisode) {
1946
1946
  this.rerolledThisHoldEpisode = true;
1947
- // #2397 round 2 (F5) — say plainly that this drops the conversation: re-rolling
1947
+ // round 2 (F5) — say plainly that this drops the conversation: re-rolling
1948
1948
  // mutates process.meta.sessionId to a brand-new, history-less session, which for a
1949
1949
  // long-lived agent throws away everything said so far.
1950
1950
  this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} times in a row in this session (${detail}); re-rolling to a new OpenCode session (this drops the current conversation history) and retrying…` });
1951
1951
  let newSessionId: string;
1952
1952
  try {
1953
- newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
1953
+ newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "humwick");
1954
1954
  } catch (createError) {
1955
- // #2619 — the original session is still intact; a failed re-roll during a
1955
+ // the original session is still intact; a failed re-roll during a
1956
1956
  // provider outage is no reason to end the agent. Hold like the exhausted path.
1957
1957
  this.holdTurnOnProviderLimit(error, `${detail} (session re-roll failed: ${errMessage(createError)})`);
1958
1958
  return "held";
@@ -1961,7 +1961,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1961
1961
  // process.meta.sessionId, so mutating it in place re-routes the whole adapter to the
1962
1962
  // new session — no separate "which session is active" bookkeeping needed.
1963
1963
  if (process.meta) process.meta.sessionId = newSessionId;
1964
- // #2539 Phase B — soft-steer fencing on re-roll: same contract as the clear swap —
1964
+ // Phase B — soft-steer fencing on re-roll: same contract as the clear swap —
1965
1965
  // accepted-but-unconsumed batches on the dead session reoffer to the normal queue
1966
1966
  // (the settlement callback fires inside sessionReplaced).
1967
1967
  const softSteerOrphans = await this.softSteer.sessionReplaced(newSessionId);
@@ -1975,7 +1975,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
1975
1975
  // suppressProviderIdle is already raised for the whole call — see entry.
1976
1976
  continue;
1977
1977
  }
1978
- // #2619 — retries and the re-roll are exhausted on a RETRYABLE error: the provider is
1978
+ // retries and the re-roll are exhausted on a RETRYABLE error: the provider is
1979
1979
  // down or throttling, not the agent. This used to failTurn → terminal `error` → runner
1980
1980
  // exit, killing every zai session ~10 min into a quota wall. Hold and let the relay's
1981
1981
  // resume sweep back off and wake the agent, as it does for Claude.
@@ -1987,24 +1987,24 @@ export class OpenCodeAdapter implements ProviderAdapter {
1987
1987
  this.emitTurnFinalResponse(process, completion);
1988
1988
  this.closeSuppressedTurn();
1989
1989
  this.turnActivityCb();
1990
- this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
1990
+ this.rerolledThisHoldEpisode = false; // the provider took a turn: the hold episode is over
1991
1991
  return "ok";
1992
1992
  }
1993
1993
  // Either a non-empty completion with no positively-parsable output (an unrecognised body —
1994
- // says nothing either way), or a POSITIVELY parsed empty completion (#2389's normal
1994
+ // says nothing either way), or a POSITIVELY parsed empty completion (the normal
1995
1995
  // empty-turn path, closed deterministically here rather than depending on a possibly late
1996
1996
  // or lost SSE idle frame). Either way this POST returning is itself proof the turn is over
1997
1997
  // — the endpoint is synchronous — so let the shared `finally` below perform the close
1998
- // instead of duplicating it at two near-identical return sites. #2397 round 4 review (F3):
1998
+ // instead of duplicating it at two near-identical return sites. Round 4 review (F3):
1999
1999
  // that exact duplication was unadjudicated — deleting either explicit call left every
2000
2000
  // opencode test green, since `finally` already produces the identical close with no
2001
2001
  // observable ordering difference from here (nothing else runs between this `return` and
2002
2002
  // the `finally`).
2003
- this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
2003
+ this.rerolledThisHoldEpisode = false; // the provider took a turn: the hold episode is over
2004
2004
  return "ok";
2005
2005
  }
2006
2006
  } finally {
2007
- // #2397 round 4/5 review (F2) — the single deterministic close for this whole call. Some
2007
+ // round 4/5 review (F2) — the single deterministic close for this whole call. Some
2008
2008
  // return paths above call `closeSuppressedTurn()` explicitly first (when something else in
2009
2009
  // that branch — `turnActivityCb`, most notably — must run AFTER the close, so ordering
2010
2010
  // matters); the rest fall straight through to here, since nothing runs between their
@@ -2012,7 +2012,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2012
2012
  // (see F3 above). `failTurn`'s own unconditional close also marks `turnClosed`, so by the
2013
2013
  // time ANY return path reaches here this is a no-op — EXCEPT when NONE of those ran: a throw
2014
2014
  // from `client.sendMessage` (transport reset, socket drop) or the "session not ready" guard.
2015
- // #2397 round 6 review (MED-2r) — that guard throw must NOT fall through to
2015
+ // round 6 review (MED-2r) — that guard throw must NOT fall through to
2016
2016
  // `closeSuppressedTurn()`. The old comment here claimed `suppressProviderIdle` "was also
2017
2017
  // never raised this call, so closeSuppressedTurn is a no-op there too" — true only on the
2018
2018
  // FIRST call. `suppressProviderIdle` is STICKY (round 5, MED-1): on any later call, if the
@@ -2029,9 +2029,9 @@ export class OpenCodeAdapter implements ProviderAdapter {
2029
2029
  // Closing here (when `turnStartedThisCall` is true — a genuine mid-attempt throw) trades a
2030
2030
  // turn the server might still be running for a turn that reliably ends; the adapter has no
2031
2031
  // other signal on a throw, and `failTurn` already makes that same trade unconditionally today.
2032
- // #2425 — `turnClosed` still being false at this point means NEITHER a success branch nor
2032
+ // `turnClosed` still being false at this point means NEITHER a success branch nor
2033
2033
  // `failTurn` ran before we got here — the only way in is an uncaught throw (e.g. `sendMessage`
2034
- // aborting on the #2425 turn-timeout, or any other transport failure) whose actual server-side
2034
+ // aborting on the turn-timeout, or any other transport failure) whose actual server-side
2035
2035
  // outcome is unknown. Mark the turn abandoned before force-closing it, so a genuine same-
2036
2036
  // session busy/idle that arrives later can still self-heal the status instead of being
2037
2037
  // swallowed forever by the otherwise-permanent `suppressProviderIdle` (see its field doc).
@@ -2043,20 +2043,20 @@ export class OpenCodeAdapter implements ProviderAdapter {
2043
2043
  }
2044
2044
 
2045
2045
  /**
2046
- * #2355 — the runner's `latestProcessContext()` (runner-core.ts) reads `process.meta.context`
2046
+ * the runner's `latestProcessContext` (runner-core.ts) reads `process.meta.context`
2047
2047
  * directly (the same fallback path Codex's `meta.context` sets from `thread/tokenUsage/updated`),
2048
2048
  * feeding Relay's normal telemetry (dashboard/API/timeline) and the context-pressure warning /
2049
2049
  * self-compaction advisory. A no-op whenever the turn carried no parsable token count or the
2050
2050
  * model's window is unknown — silence here, not a fabricated 0/0 reading.
2051
2051
  */
2052
2052
  private reportContextUsage(process: ManagedProcess, model: string | undefined, tokensUsed: number | undefined): void {
2053
- // #2599 — precedence payload-record > legacy #2595 window > manifest. The record's qualified
2053
+ // precedence payload-record > legacy window > manifest. The record's qualified
2054
2054
  // name is ALSO threaded through: meta.model is re-pinned to the server-computed qualified name
2055
2055
  // when the record is present, so later reportContextUsage calls (which read meta.model) keep
2056
2056
  // resolving against the payload instead of re-deriving from a possibly-ambiguous alias.
2057
2057
  const config = process.meta?.config as RunnerSpawnConfig | undefined;
2058
2058
  const record = openCodeModelRecord(config?.modelRecord, this.provider);
2059
- // #2659 — the configured window belongs to the LAUNCH model, not a terminal-switched one.
2059
+ // the configured window belongs to the LAUNCH model, not a terminal-switched one.
2060
2060
  const configWindow = openCodeOnLaunchModel(process.meta, model) ? record?.contextWindowTokens ?? config?.contextWindowTokens : undefined;
2061
2061
  const windowTokens = typeof configWindow === "number" && Number.isFinite(configWindow) && configWindow > 0
2062
2062
  ? configWindow
@@ -2066,7 +2066,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2066
2066
  }
2067
2067
 
2068
2068
  /**
2069
- * #2397 round 2 (F6) — once a retry (or the post-reroll retry) actually produces output, the
2069
+ * round 2 (F6) — once a retry (or the post-reroll retry) actually produces output, the
2070
2070
  * operator-visible "retrying…"/blocked hold must not linger on screen until whatever SSE idle
2071
2071
  * happens to arrive next — which `suppressProviderIdle` above can delay well past this turn.
2072
2072
  * Clear it explicitly and immediately; a no-op when no retry hold was ever shown.
@@ -2081,7 +2081,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2081
2081
  }
2082
2082
 
2083
2083
  /**
2084
- * #2397 round 4/5 review (F1/F2/MED-1) — `suppressProviderIdle` is raised for the WHOLE `send()`
2084
+ * round 4/5 review (F1/F2/MED-1) — `suppressProviderIdle` is raised for the WHOLE `send`
2085
2085
  * call, from just past the readiness guard (see the loop entry), so every path that ends the
2086
2086
  * turn — success, either failure, or the `finally`'s throw fallback — must close it
2087
2087
  * deterministically itself; the synchronous POST returning (or the throw itself) is ground truth
@@ -2095,20 +2095,20 @@ export class OpenCodeAdapter implements ProviderAdapter {
2095
2095
  private closeSuppressedTurn(hold?: ProviderState): void {
2096
2096
  if (this.turnClosed) return;
2097
2097
  this.turnClosed = true;
2098
- // #2484 round 1 review (F2/F3) — turn close: flush the debounced token-usage report now
2098
+ // round 1 review (F2/F3) — turn close: flush the debounced token-usage report now
2099
2099
  // rather than wait out its timer. Not awaited — a live turn boundary, not teardown; flush()
2100
2100
  // already routes failures to reporter.reportFailure internally, so the `.catch(() => {})`
2101
- // here only satisfies the unguarded-async ratchet (#1665) — it never actually fires.
2101
+ // here only satisfies the unguarded-async ratchet — it never actually fires.
2102
2102
  void this.tokenUsagePoster.flushAll().catch(() => {});
2103
2103
  this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"], ...(hold ? { providerState: hold } : {}) });
2104
- // #2539 Phase B — idle edge with unconsumed soft batches: arm the one bounded wake. Never
2104
+ // Phase B — idle edge with unconsumed soft batches: arm the one bounded wake. Never
2105
2105
  // into a provider-limit hold: the wake would only hit the same wall; the relay's resume turn
2106
2106
  // ends in a normal idle that arms it then.
2107
2107
  if (!hold) this.maybeArmSoftSteerWake();
2108
2108
  }
2109
2109
 
2110
2110
  /**
2111
- * #2619 — end the turn in the shared rate-limit hold instead of a terminal failure: the turn
2111
+ * end the turn in the shared rate-limit hold instead of a terminal failure: the turn
2112
2112
  * closes (idle + provider-turn) carrying the `blocked/rate_limit` providerState, which
2113
2113
  * runner-core keeps as its rateLimitHold until the relay's resume turn goes busy. The session
2114
2114
  * (and its history, including the refused user message) stays alive.
@@ -2120,7 +2120,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2120
2120
  }
2121
2121
 
2122
2122
  /**
2123
- * #2775 — OpenCode's own retry of an in-flight send(): a transient one is shown as "retrying"; a wall
2123
+ * OpenCode's own retry of an in-flight send: a transient one is shown as "retrying"; a wall
2124
2124
  * (usage/free-tier limit, far-off `next`, or an endless throttle) aborts the session so the POST
2125
2125
  * returns and send() ends the turn in the shared rate-limit hold instead of waiting silently.
2126
2126
  */
@@ -2146,11 +2146,11 @@ export class OpenCodeAdapter implements ProviderAdapter {
2146
2146
  }
2147
2147
 
2148
2148
  /**
2149
- * #2459 — close the event-driven (terminal-origin) turn on its matching SSE idle: emit the
2149
+ * close the event-driven (terminal-origin) turn on its matching SSE idle: emit the
2150
2150
  * turn-final response accumulated from the turn's finalized assistant text parts (the same
2151
2151
  * narration→final-response shape send()'s POST path produces, and the same turnId so the
2152
2152
  * dashboard's per-turn narration/response dedup applies), then the idle edge that closes
2153
- * runner-core's turn. Completes the #2425 abandonment self-heal (the server's own truth for
2153
+ * runner-core's turn. Completes the abandonment self-heal (the server's own truth for
2154
2154
  * the abandoned turn has now arrived). Deliberately does NOT re-raise the sticky idle
2155
2155
  * suppression or stamp the echo window: an event turn's busy+idle arrive LIVE — they DRIVE
2156
2156
  * this state machine; there is no send()-POST await whose buffered chunk could replay them —
@@ -2160,7 +2160,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2160
2160
  private closeEventTurn(): void {
2161
2161
  const turnId = this.eventTurnId;
2162
2162
  const text = this.eventTurnText.join("\n\n").trim();
2163
- // #2605 — reconcile the turn's reasoning parts against what the live door already emitted,
2163
+ // reconcile the turn's reasoning parts against what the live door already emitted,
2164
2164
  // per part id: a part whose latest snapshot EQUALS its last-emitted body needs no row (it is
2165
2165
  // already traced); a part that GREW after its last live emit — or was never live-emitted
2166
2166
  // (frames racing the close, a part observed only at close time) — gets one row under its OWN
@@ -2180,8 +2180,8 @@ export class OpenCodeAdapter implements ProviderAdapter {
2180
2180
  this.eventTurnReasoning = new Map();
2181
2181
  // Stray idles with no open event turn stay swallowed via the existing `turnClosed` guard.
2182
2182
  this.turnClosed = true;
2183
- // #2425 self-heal completion: this idle close was the server's own truth for the abandoned
2184
- // turn. Round 1 (review D) — re-latch the sticky suppression it had bypassed (the #2425
2183
+ // self-heal completion: this idle close was the server's own truth for the abandoned
2184
+ // turn. Round 1 (review D) — re-latch the sticky suppression it had bypassed (the
2185
2185
  // escape hatch exists only while the abandonment is unresolved), so post-heal strays stay
2186
2186
  // swallowed under the same permanent suppression as every other send()-closed turn.
2187
2187
  if (this.turnAbandoned) {
@@ -2192,17 +2192,17 @@ export class OpenCodeAdapter implements ProviderAdapter {
2192
2192
  for (const step of unseen) this.emitReasoningFinal(openCodeReasoningSessionEvent(step, turnId, false));
2193
2193
  for (const event of openCodeTurnFinalEvents({ text }, turnId)) this.sessionEventCb(event);
2194
2194
  }
2195
- // #2484 round 1 review (F2/F3) — same flush as closeSuppressedTurn's turn close; the
2195
+ // round 1 review (F2/F3) — same flush as closeSuppressedTurn's turn close; the
2196
2196
  // `.catch(() => {})` is the same ratchet-only no-op (flush() already self-catches).
2197
2197
  void this.tokenUsagePoster.flushAll().catch(() => {});
2198
2198
  this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
2199
- // #2539 Phase B — a terminal-origin turn's close is an idle edge too: unconsumed soft
2199
+ // Phase B — a terminal-origin turn's close is an idle edge too: unconsumed soft
2200
2200
  // batches get the same single bounded wake.
2201
2201
  this.maybeArmSoftSteerWake();
2202
2202
  }
2203
2203
 
2204
2204
  /**
2205
- * #2459 round 2 (review D) — emit a buffered terminal prompt under `turnId` and clear the
2205
+ * round 2 (review D) — emit a buffered terminal prompt under `turnId` and clear the
2206
2206
  * slot. `turnId` is the freshly minted terminal turn id (primary path — busy admission) or
2207
2207
  * the session pseudo-turn (bounded no-busy fallbacks); it is NEVER a stale headless
2208
2208
  * sendTurnId, which only exists while send() is in flight and is never passed here.
@@ -2215,7 +2215,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2215
2215
  }
2216
2216
 
2217
2217
  /**
2218
- * #2459 — the turn id live session events should attach to: send()'s per-call id while a
2218
+ * the turn id live session events should attach to: send's per-call id while a
2219
2219
  * headless delivery is in flight, otherwise the event-driven terminal turn minted on the
2220
2220
  * server's own busy edge. Deliberately NO fallback to a stale `sendTurnId` outside send() —
2221
2221
  * that inheritance is exactly the contamination this replaces; callers fall back to the
@@ -2226,10 +2226,10 @@ export class OpenCodeAdapter implements ProviderAdapter {
2226
2226
  }
2227
2227
 
2228
2228
  private failTurn(detail: string, terminalFailureReason: string): void {
2229
- // #2397 round 3 (MED-5) — diagnostic:true; see the retry-loop narration above for why.
2229
+ // round 3 (MED-5) — diagnostic:true; see the retry-loop narration above for why.
2230
2230
  this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed: ${detail}` });
2231
- // #2397 round 2 (F3) — an "error" status alone is never treated as a turn boundary by
2232
- // #2389's seam in runner-core (only `idle && provider-turn` clears `currentTurnId` and notes
2231
+ // round 2 (F3) — an "error" status alone is never treated as a turn boundary by
2232
+ // seam in runner-core (only `idle && provider-turn` clears `currentTurnId` and notes
2233
2233
  // the turn ended); without this, the next `busy` reuses the stale turn id and the empty-turn
2234
2234
  // detector is blinded for the rest of the agent's life. Close the turn through that same seam
2235
2235
  // first — exactly as the parsed-empty-completion path above already does — before marking the
@@ -2237,7 +2237,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2237
2237
  // raised), so mark `turnClosed` here too — otherwise `send()`'s `finally` fallback would see it
2238
2238
  // still unset and emit a second, out-of-order idle after the error status below.
2239
2239
  this.turnClosed = true;
2240
- // #2484 round 1 review (F2/F3) — same flush as the other turn-close sites: a failed turn can
2240
+ // round 1 review (F2/F3) — same flush as the other turn-close sites: a failed turn can
2241
2241
  // still carry real (partial) usage worth persisting; the `.catch(() => {})` is the same
2242
2242
  // ratchet-only no-op as the other two sites.
2243
2243
  void this.tokenUsagePoster.flushAll().catch(() => {});
@@ -2246,7 +2246,7 @@ export class OpenCodeAdapter implements ProviderAdapter {
2246
2246
  status: "error",
2247
2247
  reason: "provider-turn",
2248
2248
  clear: ["provider-turn"],
2249
- // #2443 — mirrors the Claude adapter's own "model-unavailable" stop-failure shape
2249
+ // mirrors the Claude adapter's own "model-unavailable" stop-failure shape
2250
2250
  // (providerState + an embedded timeline event) instead of leaving this error silent beyond
2251
2251
  // the mirror log: a terminal failure used to reach runner-core with no timeline event and no
2252
2252
  // classification, so an exit driven by this status surfaced nothing to the operator beyond