@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
@@ -26,7 +26,7 @@ import { codexWebToolsConfigArgs, launchWebTools } from "../web-tools";
26
26
  import { codexMcpOutputRedactor } from "./codex-tool-output";
27
27
 
28
28
  /** Relay context prepended to a Codex agent's first turn: the standard relay
29
- * blurb plus, when running in an isolated workspace, the deps caveat (#159). */
29
+ * blurb plus, when running in an isolated workspace, the deps caveat. */
30
30
  function codexRelayContextBlock(process: ManagedProcess): string {
31
31
  const config = process.meta?.config as RunnerSpawnConfig | undefined;
32
32
  return [resolveRelayContext({
@@ -70,13 +70,13 @@ type PendingCodexCompact = {
70
70
 
71
71
  const CODEX_COMPACT_TIMEOUT_MS = 10 * 60 * 1000;
72
72
 
73
- // #2539 Phase A — bounded memory of clientUserMessageIds POSITIVELY accepted by the app-server
73
+ // Phase A — bounded memory of clientUserMessageIds POSITIVELY accepted by the app-server
74
74
  // (observed via the userMessage item echo). Insertion-ordered, oldest evicted; the reconcile
75
75
  // path only needs recent turns' ids.
76
76
  const CODEX_STEER_ACCEPTED_CLIENT_CAP = 128;
77
77
 
78
78
  /**
79
- * #2539 Phase A — classify a rejected `turn/steer` RPC so the fallback matches the refusal.
79
+ * Phase A — classify a rejected `turn/steer` RPC so the fallback matches the refusal.
80
80
  * The app-server refuses steer against a turn that cannot take additive input (review/compact)
81
81
  * and against a turn id that is no longer active (ended under us / expected-id mismatch); both
82
82
  * are expected flow control, distinct from transport faults (socket, 30s timeout — an AMBIGUOUS
@@ -110,9 +110,9 @@ export class CodexAdapter implements ProviderAdapter {
110
110
  readonly provider = "codex";
111
111
  readonly reportsContextResets = true;
112
112
  readonly supportsMidTurnSteer = true;
113
- // #2569 — clearContext starts a FRESH thread and compact rewrites the current one, both via an
113
+ // clearContext starts a FRESH thread and compact rewrites the current one, both via an
114
114
  // awaited app-server handshake. That await confirms the new generation is ready but does NOT
115
- // settle the predecessor turn (the model calls relay_compact_and_resume from within its own live
115
+ // settle the predecessor turn (the model calls humwick_compact_and_resume from within its own live
116
116
  // turn). Without a fence, that turn keeps writing the worktree on the old thread while the
117
117
  // successor generation writes on the new one. The runner therefore interrupts + settles the
118
118
  // predecessor turn before compact/clearContext (interrupt-then-settle, via the primitives below).
@@ -127,42 +127,42 @@ export class CodexAdapter implements ProviderAdapter {
127
127
  private activeTurnId?: string;
128
128
  private lastCompactionKey?: string;
129
129
  private turnFailure?: ReturnType<typeof codexTurnFailure>;
130
- // #1339: the turn id we last logged a delivery-deferral for, so the once-per-turn "held
130
+ // the turn id we last logged a delivery-deferral for, so the once-per-turn "held
131
131
  // behind active turn" notice isn't repeated on every ~1s runner retry during that turn.
132
132
  private deferredForTurnId?: string;
133
- // #2539 Phase A — the turn id whose steer the app-server REJECTED as not steerable
133
+ // Phase A — the turn id whose steer the app-server REJECTED as not steerable
134
134
  // (review/compact). Once set for a turn id, mid-turn offers silently defer until the turn id
135
135
  // changes, instead of re-attempting the RPC on every ~1s drain only to be refused again.
136
136
  private steerDeniedForTurnId?: string;
137
- // #2539 Phase A — clientUserMessageIds POSITIVELY accepted by the app-server (the userMessage
137
+ // Phase A — clientUserMessageIds POSITIVELY accepted by the app-server (the userMessage
138
138
  // item echoes the steered input's clientId). The reconcile path reads this to settle an
139
139
  // accepted-but-response-lost steer instead of re-steering (which could duplicate the input).
140
140
  private readonly acceptedSteerClientIds = new Set<string>();
141
- // #653: monotonic id for a turn we synthesize from live item activity when a real
141
+ // monotonic id for a turn we synthesize from live item activity when a real
142
142
  // turn/started never reached us — so a working Codex agent isn't reported `idle`.
143
143
  private syntheticTurnSeq = 0;
144
- // #2543: monotonic clientUserMessageId sequence for prompt injections steered into a live
144
+ // monotonic clientUserMessageId sequence for prompt injections steered into a live
145
145
  // turn — per-attempt unique (the app-server echoes it back; no reconcile reads it).
146
146
  private injectionSteerSeq = 0;
147
147
  private tokenUsageReporter?: ProviderTokenUsageReporter;
148
148
  private readonly agentMessageCapture = new CodexAgentMessageCapture();
149
149
  private readonly itemTextBuffers = new Map<string, string>();
150
150
  private readonly itemTextBufferTypes = new Map<string, string>();
151
- // #183/#184: the normalized session-event log for the current process lifetime, fed
151
+ // the normalized session-event log for the current process lifetime, fed
152
152
  // from the same completed-item stream that drives the chat mirror. The runner slices
153
153
  // this per-segment (since the last compact/clear/restart) via its own cursor, so we
154
154
  // accumulate and never clear mid-session; spawn() resets it for a fresh process. Soft
155
155
  // cap below keeps a runaway session from growing memory unbounded.
156
156
  private sessionEvents: SessionEvent[] = [];
157
157
  private static readonly SESSION_EVENTS_CAP = 50_000;
158
- // #3411 — the session cwd a fileChange's relative paths resolve against.
158
+ // the session cwd a fileChange's relative paths resolve against.
159
159
  private sessionCwd?: string;
160
160
 
161
161
  onStatusChange(cb: (status: ProviderStatusUpdate) => void): void {
162
162
  this.statusCb = cb;
163
163
  }
164
164
 
165
- // §4.1 contract verdict (#746). Codex reference (#724): a Codex provider-turn can wedge
165
+ // §4.1 contract verdict. Codex reference: a Codex provider-turn can wedge
166
166
  // while a long-running subprocess child (a `bun test` / build gate) is still alive and
167
167
  // making real progress. That subprocess is tracked as a non-`provider-turn` work-kind, so
168
168
  // `hasLiveWork` stays true and the turn is never mis-read as stuck; app-server item events
@@ -178,7 +178,7 @@ export class CodexAdapter implements ProviderAdapter {
178
178
 
179
179
  private resetProcessState(): void {
180
180
  this.resetThreadState();
181
- this.sessionEvents = []; // fresh process -> fresh segment cursor (#184)
181
+ this.sessionEvents = []; // fresh process -> fresh segment cursor
182
182
  }
183
183
 
184
184
  private resetThreadState(): void {
@@ -208,9 +208,9 @@ export class CodexAdapter implements ProviderAdapter {
208
208
  const client = process.meta?.client as CodexAppClient | undefined;
209
209
  if (!client?.isConnected()) return "unknown";
210
210
  const threadId = typeof process.meta?.threadId === "string" ? process.meta.threadId : "";
211
- // #651: when no local activeTurnId, don't short-circuit to idle — query the app-server.
211
+ // when no local activeTurnId, don't short-circuit to idle — query the app-server.
212
212
  // turn/started can arrive after processing begins (or be missed entirely); a stale-idle
213
- // probe would make a busy agent appear idle to the coordinator. Same fallback as #653's
213
+ // probe would make a busy agent appear idle to the coordinator. Same fallback as the
214
214
  // ensureMainTurnActive path, but for the pull (probe) direction, not the push (event) one.
215
215
  if (!this.activeTurnId) {
216
216
  if (!threadId) return "idle";
@@ -341,7 +341,7 @@ export class CodexAdapter implements ProviderAdapter {
341
341
  return { threadId };
342
342
  }
343
343
 
344
- /** #3398 — every compaction of the main thread is a context reset, whether or not compact() still waits for it. */
344
+ /** every compaction of the main thread is a context reset, whether or not compact still waits for it. */
345
345
  private onThreadCompacted(process: ManagedProcess, threadId: string, turnId: string | undefined, compactTrigger: "manual" | "auto"): void {
346
346
  if (threadId !== process.meta?.threadId) return;
347
347
  // thread/compacted and the compaction item report the same compaction.
@@ -401,15 +401,15 @@ export class CodexAdapter implements ProviderAdapter {
401
401
  }
402
402
 
403
403
  async deliverInitialPrompt(process: ManagedProcess, prompt: string, options?: PromptOfferOptions): Promise<void> {
404
- const text = sanitizeLaunchText(prompt, "initial prompt").trim(); // options.readyTimeoutMs (#329) is a Claude-TUI concern; ignored here
404
+ const text = sanitizeLaunchText(prompt, "initial prompt").trim(); // options.readyTimeoutMs is a Claude-TUI concern; ignored here
405
405
  if (!text) return;
406
406
  const threadId = await ensureCodexThread(process);
407
- // #1228: only a genuine first turn gets the initial-prompt label — a mid-session
407
+ // only a genuine first turn gets the initial-prompt label — a mid-session
408
408
  // dashboard continue/injection (isInitial: false) is delivered as a plain turn.
409
409
  const isInitial = options?.isInitial !== false;
410
- // #2543 — the last provider-input path without an active-turn guard: a non-initial injection
410
+ // the last provider-input path without an active-turn guard: a non-initial injection
411
411
  // (dashboard continue/hard-redirect, AskUserQuestion answer, empty-turn nudge) offered while
412
- // a turn is ACTIVE must never issue turn/start (#1339: the app-server silently drops it).
412
+ // a turn is ACTIVE must never issue turn/start (the app-server silently drops it).
413
413
  // Fold it into the live turn via the same additive turn/steer channel deliverMidTurn uses; a
414
414
  // refusal defers by throwing the shared turn-in-progress signal, which the runner's injection
415
415
  // paths translate into a bounded wait-for-turn-clear + re-offer. A genuine initial prompt
@@ -420,10 +420,10 @@ export class CodexAdapter implements ProviderAdapter {
420
420
  }
421
421
  let input = [
422
422
  codexLaunchContext(process),
423
- ...(isInitial ? ["[agent-relay initial prompt from dashboard]"] : []),
423
+ ...(isInitial ? ["[humwick initial prompt from dashboard]"] : []),
424
424
  text,
425
425
  ].filter(Boolean).join("\n\n");
426
- // #2542 — transactional first-delivery context (#2539 design C applied to this start path):
426
+ // transactional first-delivery context (design C applied to this start path):
427
427
  // stage the relay-context block but commit relayContextSent ONLY after turn/start is
428
428
  // positively accepted, so a failed first turn/start (ready-signal retry, spawn retry) still
429
429
  // carries the context instead of silently losing it.
@@ -438,7 +438,7 @@ export class CodexAdapter implements ProviderAdapter {
438
438
  const started = await client.turnStart(threadId, input);
439
439
  options?.inputObserver?.consumed();
440
440
  if (relayContextPending) process.meta = { ...(process.meta ?? {}), relayContextSent: true };
441
- // #1565 F1 — claim the turn from the RESPONSE, the same hardening deliver() has: without it,
441
+ // F1 — claim the turn from the RESPONSE, the same hardening deliver has: without it,
442
442
  // a second input in the notification lag read activeTurnId as undefined and issued a
443
443
  // concurrent turn/start the app-server silently dropped. A later turn/started overwrites
444
444
  // this id; the completion/interrupt/probeActivity paths clear it, so it cannot wedge.
@@ -446,8 +446,8 @@ export class CodexAdapter implements ProviderAdapter {
446
446
  }
447
447
 
448
448
  /**
449
- * #2543 — fold a non-initial prompt injection into the ACTIVE turn via the additive
450
- * `turn/steer` channel (the #2539 Phase A channel, shared with deliverMidTurn). The relay
449
+ * fold a non-initial prompt injection into the ACTIVE turn via the additive
450
+ * `turn/steer` channel (the Phase A channel, shared with deliverMidTurn). The relay
451
451
  * context is not re-prepended: an active turn can only exist if its own accepted start already
452
452
  * carried (and committed) the first-delivery context. Refusals DEFER — the shared
453
453
  * turn-in-progress signal makes the runner's injection paths wait for the turn to clear and
@@ -485,16 +485,16 @@ export class CodexAdapter implements ProviderAdapter {
485
485
 
486
486
  async deliver(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<void | number[]> {
487
487
  const threadId = await ensureCodexThread(process);
488
- // #2539 Phase A — a mid-turn offer goes through the adapter-owned steer contract: the
488
+ // Phase A — a mid-turn offer goes through the adapter-owned steer contract: the
489
489
  // steerable subset folds into the in-flight turn (soft-steer is the codex default), while
490
490
  // boundary-only classes stay un-acked for the runner to requeue toward turn-end. An idle
491
491
  // offer takes the WHOLE ordered batch as one fresh turn/start — NEVER turn/start while a
492
- // turn is active (#1339: the app-server would drop it).
492
+ // turn is active (the app-server would drop it).
493
493
  if (this.activeTurnId !== undefined) {
494
494
  return this.deliverMidTurn(process, messages, ctx);
495
495
  }
496
496
  const client = process.meta?.client as CodexAppClient;
497
- // #2542 — transactional formatting state on the IDLE turn/start path (#2539 design C, the
497
+ // transactional formatting state on the IDLE turn/start path (design C, the
498
498
  // same fix deliverMidTurn already has): stage the first-delivery
499
499
  // relay context, and commit BOTH only after turn/start is positively accepted — a failed
500
500
  // start must not consume the first-delivery context, so the runner's requeue re-offers WITH
@@ -510,7 +510,7 @@ export class CodexAdapter implements ProviderAdapter {
510
510
  const started = await client.turnStart(threadId, text);
511
511
  ctx?.inputObserver?.consumed();
512
512
  if (relayContextPending) process.meta = { ...(process.meta ?? {}), relayContextSent: true };
513
- // #1565 F1 — claim the turn from the RESPONSE, not from the later `turn/started` notification.
513
+ // F1 — claim the turn from the RESPONSE, not from the later `turn/started` notification.
514
514
  // The guard above is only as good as the state it reads: with the turn id arriving a few ms
515
515
  // later over the event stream, a second delivery in that window passed the guard and issued a
516
516
  // concurrent turn/start on the same thread, which the app-server silently drops — while its
@@ -522,7 +522,7 @@ export class CodexAdapter implements ProviderAdapter {
522
522
  }
523
523
 
524
524
  /**
525
- * #2539 Phase A — the codex implementation of the shared mid-turn steer contract (see
525
+ * Phase A — the codex implementation of the shared mid-turn steer contract (see
526
526
  * {@link ProviderAdapter.deliverMidTurn}). Steerable messages fold into the in-flight turn via
527
527
  * the ADDITIVE app-server `turn/steer`: the running tool/response step finishes untouched, and
528
528
  * the injected input is drained before the next model request — never an abort, never a
@@ -577,7 +577,7 @@ export class CodexAdapter implements ProviderAdapter {
577
577
  logger.info("codex", `steer for Codex turn ${steerTurnId} was already accepted (clientId ${clientUserMessageId}); settling ${eligible.length} message${eligible.length === 1 ? "" : "s"} without re-steering`);
578
578
  return eligible.map((message) => message.id);
579
579
  }
580
- // Transactional formatting state (#2539 design C): prepare the Relay context, send,
580
+ // Transactional formatting state: prepare the Relay context, send,
581
581
  // and commit it ONLY on positive acceptance.
582
582
  let steerText = [codexLaunchContext(process), providerMessageText(eligible)].filter(Boolean).join("\n\n");
583
583
  let relayContextPending = false;
@@ -670,14 +670,14 @@ export class CodexAdapter implements ProviderAdapter {
670
670
  }
671
671
 
672
672
  buildSpawnArgs(config: RunnerSpawnConfig, providerConfig: ProviderConfig): SpawnArgs {
673
- // #2719 — the fallback listen port is allocated, never derived: `controlPort + 1000`
673
+ // the fallback listen port is allocated, never derived: `controlPort + 1000`
674
674
  // overflowed 65535 on a high ephemeral control port (macOS), which the app-server does not
675
675
  // report as a bad port — it binds elsewhere and the client then polls a dead URL.
676
676
  const appServerUrl = String(config.appServerUrl || process.env.CODEX_APP_SERVER_URL || `ws://127.0.0.1:${allocateLoopbackPort()}`);
677
677
  // Create + bootstrap the isolated CODEX_HOME (no-op for host base) so the assembler's
678
678
  // materialize step has somewhere to write the provisioned skills.
679
679
  prepareCodexProfileHome(config);
680
- // #557 — ONE assembler owns the provisioning/vanilla surface (skills.config, relay +
680
+ // ONE assembler owns the provisioning/vanilla surface (skills.config, relay +
681
681
  // provisioned -c mcp_servers.*, project_doc_max_bytes); profile-projection describes the
682
682
  // SAME assembled result, so report == launch. The adapter only adds core launch config the
683
683
  // assembler does not own: app-server wiring, model/approval/tool-output, managed flags, --listen.
@@ -704,11 +704,11 @@ export class CodexAdapter implements ProviderAdapter {
704
704
  cwd: config.cwd,
705
705
  env: writeChildEnv({}, {
706
706
  ...config.env,
707
- // #557 — the assembler owns the isolated config-home env (CODEX_HOME).
707
+ // the assembler owns the isolated config-home env (CODEX_HOME).
708
708
  ...assembled.env,
709
709
  CODEX_APP_SERVER_URL: appServerUrl,
710
- AGENT_RELAY_PROVIDER: "codex",
711
- }),
710
+ HUMWICK_PROVIDER: "codex",
711
+ }, []),
712
712
  });
713
713
  }
714
714
 
@@ -716,7 +716,7 @@ export class CodexAdapter implements ProviderAdapter {
716
716
  if (event.type === "server-request" && process) {
717
717
  const approval = codexApprovalFromServerRequest(event.message);
718
718
  if (approval) {
719
- // #435/#723 ordering invariant: mirror every reasoning/narrative step of the turn
719
+ // ordering invariant: mirror every reasoning/narrative step of the turn
720
720
  // BEFORE the approval form surfaces — the same guarantee Claude makes by pre-flushing
721
721
  // the transcript at its hook seam, here enforced from the Codex app-server mechanism.
722
722
  // approval.view.reasoningSettled === true asserts this flush has run.
@@ -730,7 +730,7 @@ export class CodexAdapter implements ProviderAdapter {
730
730
  state: "blocked",
731
731
  reason: "permissionRequest",
732
732
  label: approval.view.title,
733
- recommendedAction: "Review the permission request in Agent Relay.",
733
+ recommendedAction: "Review the permission request in Humwick.",
734
734
  source: "codex",
735
735
  pendingApproval: approval.view,
736
736
  updatedAt: Date.now(),
@@ -744,12 +744,12 @@ export class CodexAdapter implements ProviderAdapter {
744
744
  // managed agent has no human to answer it; the old -32601 reject makes codex resolve
745
745
  // the elicitation as `Decline`, so the MCP tool call dies with "user rejected MCP tool
746
746
  // call" — stranding read-only recon/reviewer workers with no egress to report back
747
- // over the bus (#395). The agent is already bounded by its scoped relay token + the
747
+ // over the bus. The agent is already bounded by its scoped relay token + the
748
748
  // codex sandbox, so auto-accept (spec-compliant `{ action: "accept" }`, not codex's
749
749
  // legacy `{ decision: "approved" }`) so the agent's reporting/recon MCP tools run.
750
750
  const client = process.meta?.client as CodexAppClient | undefined;
751
751
  const serverName = isRecord(event.message.params) ? stringValue(event.message.params.serverName) : undefined;
752
- logger.info("codex", `auto-accepting headless MCP elicitation from ${serverName ?? "unknown server"} so the agent's MCP tools can run (#395)`);
752
+ logger.info("codex", `auto-accepting headless MCP elicitation from ${serverName ?? "unknown server"} so the agent's MCP tools can run`);
753
753
  client?.respondToServerRequest(event.message.id, codexHeadlessElicitationResponse());
754
754
  } else {
755
755
  const client = process.meta?.client as CodexAppClient | undefined;
@@ -844,7 +844,7 @@ export class CodexAdapter implements ProviderAdapter {
844
844
  // Turn one completed Codex thread item into a session-mirror event. agentMessage text is
845
845
  // mirrored as narration as it lands; the captured turn-final response repeats only the
846
846
  // TRAILING agentMessage run — the segment after the last tool/file-change/reasoning
847
- // boundary (design #1079 §3.2) — never earlier mid-work narration, which was already
847
+ // boundary — never earlier mid-work narration, which was already
848
848
  // mirrored live. The rest (user prompt echo, reasoning, tool steps) is surfaced as it lands.
849
849
  private handleCodexItem(item: Record<string, unknown> | undefined): void {
850
850
  if (!item) return;
@@ -856,11 +856,11 @@ export class CodexAdapter implements ProviderAdapter {
856
856
  // buffered reasoning first so it lands in transcript order, ahead of this tool/message.
857
857
  if (type !== "reasoning") this.flushBufferedReasoning();
858
858
  if (type === "agentMessage") {
859
- this.agentMessageCapture.completeItem(itemId, stringValue(item.text), (body) => this.emitAgentNarration(body), { phase: item.phase });
859
+ this.agentMessageCapture.completeItem(itemId, typeof item.text === "string" ? item.text : undefined, (body) => this.emitAgentNarration(body), { phase: item.phase });
860
860
  return;
861
861
  }
862
862
  if (type === "userMessage") {
863
- // #2539 Phase A — the app-server echoes a steered input's clientUserMessageId as the
863
+ // Phase A — the app-server echoes a steered input's clientUserMessageId as the
864
864
  // item's clientId. Observing it is the POSITIVE acceptance evidence that reconciles an
865
865
  // accepted-but-response-lost steer: the next offer of the same composition settles by
866
866
  // correlation instead of re-steering.
@@ -891,7 +891,7 @@ export class CodexAdapter implements ProviderAdapter {
891
891
  if (codexItemFailed(item)) this.recordInsightEvent({ type: "tool_error" });
892
892
  // stepId = the app-server item id: the server upserts the running step emitted on
893
893
  // item/started into this terminal state IN PLACE, so the tool persists as ONE row.
894
- // #2354 — a FAILED item was previously hardcoded to status:"completed", so a tool that
894
+ // a FAILED item was previously hardcoded to status:"completed", so a tool that
895
895
  // errored rendered identical to one that succeeded. `codexItemFailed` is the same
896
896
  // failed-outcome predicate used for the insight proxy above; map it through to the shared
897
897
  // `failed` status so every provider agrees on the contract.
@@ -923,7 +923,7 @@ export class CodexAdapter implements ProviderAdapter {
923
923
  }
924
924
  }
925
925
 
926
- // #183/#184: append to the session-event log with a soft cap. On overflow we drop the
926
+ // append to the session-event log with a soft cap. On overflow we drop the
927
927
  // oldest half; the runner detects the resulting length shrink and resets its segment
928
928
  // cursor (worst case: one slightly-truncated datapoint on a pathologically long session).
929
929
  private recordInsightEvent(event: SessionEvent): void {
@@ -933,14 +933,14 @@ export class CodexAdapter implements ProviderAdapter {
933
933
  }
934
934
  }
935
935
 
936
- // Whole-session event stream for the Insights context-ratio signal (#184). Codex learns
936
+ // Whole-session event stream for the Insights context-ratio signal. Codex learns
937
937
  // about activity through app-server item events, not a transcript, so we replay the log
938
938
  // accumulated since this process started. The runner owns per-segment slicing.
939
939
  async collectSessionEvents(): Promise<SessionEvent[] | null> {
940
940
  return [...this.sessionEvents];
941
941
  }
942
942
 
943
- // #653: Codex busy-state normally flips on turn/started, but the app-server can stream
943
+ // Codex busy-state normally flips on turn/started, but the app-server can stream
944
944
  // item activity (which already drives the dashboard chat mirror) before — or without —
945
945
  // a turn/started reaching us, leaving a visibly-working agent reported `idle` (a reap
946
946
  // hazard). Derive busy from that SAME live activity: any in-flight item event with no
@@ -997,12 +997,12 @@ export class CodexAdapter implements ProviderAdapter {
997
997
  if (method.includes("outputDelta") || method.includes("output_delta") || method.includes("progress")) return;
998
998
  }
999
999
 
1000
- // A turn-final capture is emitted ONLY for a genuine `completed` turn (design #1079 §3.2):
1000
+ // A turn-final capture is emitted ONLY for a genuine `completed` turn:
1001
1001
  // failed/interrupted turns must never produce a turn-final. Every "response"-type
1002
1002
  // ProviderSessionEvent is unconditionally dual-stamped turnFinal/final downstream
1003
1003
  // (publishCapturedResponse's provider-agnostic stamp), so the only way to keep a
1004
1004
  // failed/interrupted partial OUT of D's work-final ring is to never emit it as a
1005
- // "response" event at all (#1083 Finding 1). The partial text was already mirrored live
1005
+ // "response" event at all. The partial text was already mirrored live
1006
1006
  // as narration by the `emit` callback inside agentMessageCapture.finish(), so it's still
1007
1007
  // visible in the dashboard.
1008
1008
  private flushTurnResponse(endedBy: MessageTurnEndReason): void {
@@ -1086,10 +1086,10 @@ function codexDeltaText(params: Record<string, unknown> | undefined): string {
1086
1086
  const delta = params?.delta;
1087
1087
  if (typeof delta === "string") return delta;
1088
1088
  if (isRecord(delta)) {
1089
- const text = stringValue(delta.text) ?? stringValue(delta.value) ?? stringValue(delta.content);
1090
- if (text) return text;
1089
+ for (const key of ["text", "value", "content"]) if (typeof delta[key] === "string") return delta[key];
1091
1090
  }
1092
- return stringValue(params?.text) ?? stringValue(params?.chunk) ?? stringValue(params?.content) ?? "";
1091
+ for (const key of ["text", "chunk", "content"]) if (typeof params?.[key] === "string") return params[key];
1092
+ return "";
1093
1093
  }
1094
1094
 
1095
1095
  function codexItemTypeFromMethod(method: string): string | undefined {
@@ -1098,12 +1098,12 @@ function codexItemTypeFromMethod(method: string): string | undefined {
1098
1098
  }
1099
1099
 
1100
1100
  // Codex forwards MCP elicitation (incl. its per-tool-call approval gate) to the app-server
1101
- // client under this server-request method (#395). See handleCodexEvent for why a headless
1101
+ // client under this server-request method. See handleCodexEvent for why a headless
1102
1102
  // agent must answer it instead of rejecting it.
1103
1103
  export const CODEX_MCP_ELICITATION_METHOD = "mcpServer/elicitation/request";
1104
1104
 
1105
1105
  // A managed agent with no attended TUI (the spawn intent, `config.headless`) has no human to
1106
- // resolve an elicitation. Such agents are the #395 case; interactive (TUI) sessions leave the
1106
+ // resolve an elicitation. Such agents are the case; interactive (TUI) sessions leave the
1107
1107
  // elicitation to the human in the attached terminal.
1108
1108
  function isCodexHeadless(process: ManagedProcess): boolean {
1109
1109
  return (process.meta?.config as RunnerSpawnConfig | undefined)?.headless === true;
@@ -1151,7 +1151,7 @@ export function codexReasoningText(item: Record<string, unknown>): string {
1151
1151
  }
1152
1152
 
1153
1153
  /** Build a compact { label, body } activity summary for a Codex tool item. */
1154
- // Canonical tool name for the #184 gathering/action classifier. Codex item types map to
1154
+ // Canonical tool name for the gathering/action classifier. Codex item types map to
1155
1155
  // names the shared classifier already understands: commandExecution executes (→ Bash, an
1156
1156
  // action), fileChange mutates (→ Edit), webSearch gathers (→ WebSearch, in the set), and
1157
1157
  // MCP/dynamic calls carry their real tool name so name-shape classification applies.
@@ -1168,7 +1168,7 @@ export function codexInsightToolName(type: string | undefined, item: Record<stri
1168
1168
  }
1169
1169
  }
1170
1170
 
1171
- // #1498: Codex app-server item types → the provider-independent category taxonomy. Codex has
1171
+ // Codex app-server item types → the provider-independent category taxonomy. Codex has
1172
1172
  // no MCP-tool-search or native read/ask-user-question item, so it simply never emits those
1173
1173
  // categories; webSearch and any other/future item type fall to "generic" rather than being
1174
1174
  // force-fit — the fallback exists precisely so nothing is hidden from the activity trace.
@@ -1196,7 +1196,7 @@ export function codexItemFailed(item: Record<string, unknown>): boolean {
1196
1196
 
1197
1197
  export function codexToolSummary(type: string | undefined, item: Record<string, unknown>, cwd?: string): { label: string; body: string; params?: unknown; paths?: string[] } | null {
1198
1198
  // One-line + redact + clip is the shared `tool-summary.ts` primitive (Claude/OpenCode parity);
1199
- // only Codex's per-item-type field mapping lives here. `params` (#2437) reuses the same raw
1199
+ // only Codex's per-item-type field mapping lives here. `params` reuses the same raw
1200
1200
  // field each branch already extracted for its one-line body, redacted+capped separately for
1201
1201
  // the trace's expandable detail — the one-line label stays exactly as it was.
1202
1202
  const oneLine = oneLineText, clip = clipToolSummary, params = redactAndCapParams;
@@ -1207,7 +1207,7 @@ export function codexToolSummary(type: string | undefined, item: Record<string,
1207
1207
  if (type === "fileChange") {
1208
1208
  const changes = Array.isArray(item.changes) ? item.changes.filter(isRecord) : [];
1209
1209
  const files = changes.map((c) => stringValue(c.path) ?? stringValue(c.file) ?? "").filter(Boolean);
1210
- const paths = absoluteToolPaths(files, cwd); // #3411 — from the raw changes, never the capped params
1210
+ const paths = absoluteToolPaths(files, cwd); // from the raw changes, never the capped params
1211
1211
  return { label: "Edit", body: clip(files.length ? files.join(", ") : "file changes"), params: params(item.changes), ...(paths ? { paths } : {}) };
1212
1212
  }
1213
1213
  if (type === "mcpToolCall" || type === "dynamicToolCall") {
@@ -1248,7 +1248,7 @@ function codexApprovalMethod(method: string): boolean {
1248
1248
 
1249
1249
  // Build the normalized InteractivePrompt for a Codex app-server approval request.
1250
1250
  // reasoningSettled is stamped `true` because handleCodexEvent flushes buffered reasoning +
1251
- // any open narration segment BEFORE this view is emitted as pendingApproval (#435/#723). No
1251
+ // any open narration segment BEFORE this view is emitted as pendingApproval. No
1252
1252
  // `provider` tag — the server reads the typed contract, not provider identity.
1253
1253
  function codexApprovalView(id: string, method: string, params: Record<string, unknown>): InteractivePrompt {
1254
1254
  const command = typeof params.command === "string" ? params.command
@@ -1326,7 +1326,7 @@ async function ensureCodexThread(process: ManagedProcess): Promise<string> {
1326
1326
  export function codexDeliveryNotice(messages: Message[], threadId: string): string {
1327
1327
  const ids = messages.map((message) => `#${message.id}`).join(", ");
1328
1328
  const senders = [...new Set(messages.map((message) => message.from))].join(", ");
1329
- return `[agent-relay] received ${messages.length} relay message${messages.length === 1 ? "" : "s"} ${ids} from ${senders}; injecting into Codex thread ${threadId}`;
1329
+ return `[humwick] received ${messages.length} relay message${messages.length === 1 ? "" : "s"} ${ids} from ${senders}; injecting into Codex thread ${threadId}`;
1330
1330
  }
1331
1331
 
1332
1332
  export function codexProviderStateFromThreadStatus(status: unknown, params?: Record<string, unknown>, now = Date.now()): ProviderState | undefined {
@@ -1413,7 +1413,7 @@ function activeFlags(value: unknown): string[] {
1413
1413
  return value.activeFlags.filter((flag): flag is string => typeof flag === "string" && flag.length > 0);
1414
1414
  }
1415
1415
 
1416
- // #557 — bundledCodexSkillDirs + bundledSkillConfigArgs now live in ../launch-assembly
1416
+ // bundledCodexSkillDirs + bundledSkillConfigArgs now live in ../launch-assembly
1417
1417
  // (the single launch-arg home, shared with the assembler); re-exported so existing
1418
1418
  // codex.ts consumers + tests keep their import path. tomlString likewise from ../relay-mcp.
1419
1419
  export { bundledCodexSkillDirs, bundledSkillConfigArgs };
@@ -1426,7 +1426,7 @@ function codexRelayContextEnabled(process: ManagedProcess): boolean {
1426
1426
  return resolveRelayContext({ slug: "relay.context.codex", config }).available;
1427
1427
  }
1428
1428
 
1429
- // Exported for integration coverage (#462): Codex has no `--append-system-prompt`
1429
+ // Exported for integration coverage: Codex has no `--append-system-prompt`
1430
1430
  // flag — the composed systemPromptAppend rides in as the launch-context message
1431
1431
  // prepended to the first turn. This is the Codex twin of Claude's append arg.
1432
1432
  export function codexLaunchContext(process: ManagedProcess): string | undefined {
@@ -1435,7 +1435,7 @@ export function codexLaunchContext(process: ManagedProcess): string | undefined
1435
1435
  if (!text || process.meta?.systemPromptAppendSent) return undefined;
1436
1436
  process.meta = { ...(process.meta ?? {}), systemPromptAppendSent: true };
1437
1437
  return [
1438
- "[agent-relay launch context]",
1438
+ "[humwick launch context]",
1439
1439
  "The following context was attached when this provider session was launched. Treat it as background context, not as a user request and not as your own prior actions.",
1440
1440
  text,
1441
1441
  ].join("\n");
@@ -21,7 +21,7 @@ export class OpenCodeApiBudget {
21
21
  private heartbeat?: ReturnType<typeof setInterval>;
22
22
 
23
23
  constructor(config: RunnerSpawnConfig) {
24
- this.ledger = new ApiBudgetAccounting("openai-api", (action, body) => this.ledger.transport(config, action, { ...body, ...(action === "reserve" && relayEnv(config.env, "AGENT_RELAY_SPAWN_REQUEST_ID") ? { spawnRequestId: relayEnv(config.env, "AGENT_RELAY_SPAWN_REQUEST_ID") } : {}) }));
24
+ this.ledger = new ApiBudgetAccounting("openai-api", (action, body) => this.ledger.transport(config, action, { ...body, ...(action === "reserve" && relayEnv(config.env, "HUMWICK_SPAWN_REQUEST_ID") ? { spawnRequestId: relayEnv(config.env, "HUMWICK_SPAWN_REQUEST_ID") } : {}) }));
25
25
  }
26
26
 
27
27
  async start(sessionId: string): Promise<void> {
@@ -1,10 +1,10 @@
1
1
  import type { ContextState } from "@humwick/sdk";
2
2
  import { providerContextState } from "../transcript-context";
3
3
 
4
- // #2355 — OpenCode's `/session/:id/message` response (and its `message.updated` SSE echo) carries
4
+ // OpenCode's `/session/:id/message` response (and its `message.updated` SSE echo) carries
5
5
  // a per-assistant-message `tokens` object once the message finalizes: `{ input, output, reasoning,
6
6
  // cache: { read, write} }`, confirmed live against a real captured v1.18.21 session's SQLite
7
- // `message` table (issue #2355 investigation) — and, when present, a `total` leaf that is already
7
+ // `message` table — and, when present, a `total` leaf that is already
8
8
  // the sum of all of those (`input + output + reasoning + cache.read + cache.write`). Unlike Codex's
9
9
  // `thread/tokenUsage/updated` (which also carries `modelContextWindow`), OpenCode's wire never
10
10
  // reports the model's context-window size anywhere — so the window has to come from the static,
@@ -17,9 +17,9 @@ import { providerContextState } from "../transcript-context";
17
17
  // confirms turn-to-turn. `confidence: "estimated"` (not "reported") makes that partial provenance
18
18
  // visible downstream instead of presenting the whole reading as fully API-confirmed.
19
19
  //
20
- // #2355 review (MED-1) — `tokensUsed === 0` is treated as NO SIGNAL, same as `undefined`, not as a
20
+ // review (MED-1) — `tokensUsed === 0` is treated as NO SIGNAL, same as `undefined`, not as a
21
21
  // genuine zero-usage reading. `send()` calls this on EVERY completion, including a positively-parsed
22
- // EMPTY completion (`openCodeCompletion`'s `empty: true` path — #2389's deterministic-close path,
22
+ // EMPTY completion (`openCodeCompletion`'s `empty: true` path — the deterministic-close path,
23
23
  // which reports `tokens: 0`). A finalized assistant message that actually engaged the model always
24
24
  // has `input > 0` (the prompt itself is never zero tokens), so a `total` of exactly 0 only ever
25
25
  // means "no usable token reading for this turn" — never "the context is now empty". Returning a real
@@ -46,7 +46,7 @@ export interface OpenCodeRelayContextInput {
46
46
  }
47
47
 
48
48
  /**
49
- * #2534 — proof-of-acceptance watcher for one context-carrying outbound prompt. Armed at
49
+ * proof-of-acceptance watcher for one context-carrying outbound prompt. Armed at
50
50
  * dispatch and consumed by the SSE `message.part.updated` door (or the bounded persisted-message
51
51
  * reconciliation) the moment OpenCode positively persists the prompt as a user message —
52
52
  * BEFORE the synchronous turn-completion await resolves, which on a long turn may only do so
@@ -58,7 +58,7 @@ export interface OpenCodeContextAcceptance {
58
58
  readonly emitsRelayContext: boolean;
59
59
  readonly emitsLaunchContext: boolean;
60
60
  /**
61
- * #2534 review round 2 (MED-2) — epoch ms when THIS arm was dispatched. Every arm of send()'s
61
+ * review round 2 (MED-2) — epoch ms when THIS arm was dispatched. Every arm of send's
62
62
  * retry/re-roll loop dispatches the SAME immutable text into the SAME session, so the
63
63
  * reconciliation's persisted match needs a per-arm discriminator beyond text equality: a
64
64
  * persisted user message only proves THIS arm if it was created at or after this arm's
@@ -66,7 +66,7 @@ export interface OpenCodeContextAcceptance {
66
66
  */
67
67
  readonly dispatchedAt: number;
68
68
  /**
69
- * #2534 review (MED-2) — accepted verdict for THIS dispatch arm, set only by this arm's own
69
+ * review (MED-2) — accepted verdict for THIS dispatch arm, set only by this arm's own
70
70
  * door or reconciliation. The durable first-turn markers are process-scoped and the prepared
71
71
  * context input is immutable across send()'s retry/re-roll loop, so a later arm that fails
72
72
  * PRE-acceptance must never inherit an earlier arm's acceptance: keying the verdict to the
@@ -82,15 +82,11 @@ function openCodeLaunchContext(process: ManagedProcess): string | undefined {
82
82
  const config = process.meta?.config as RunnerSpawnConfig | undefined;
83
83
  const text = config?.systemPromptAppend?.trim();
84
84
  if (!text || process.meta?.launchContextSent) return undefined;
85
- return [
86
- "[agent-relay launch context]",
87
- "The following context was attached when this provider session was launched. Treat it as background context, not as a user request and not as your own prior actions.",
88
- text,
89
- ].join("\n");
85
+ return text;
90
86
  }
91
87
 
92
88
  /**
93
- * Prepare exactly one OpenCode turn with Relay context before its retry loop begins.
89
+ * Prepare one OpenCode turn and its native-instruction acceptance markers before retries.
94
90
  *
95
91
  * This deliberately does not mutate `relayContextSent`: a missing client/session, or a client
96
92
  * that throws before accepting a request, has not emitted the block and must leave the next
@@ -102,7 +98,9 @@ export function prepareOpenCodeRelayContext(process: ManagedProcess, text: strin
102
98
  const emitsRelayContext = context.available && !process.meta?.relayContextSent;
103
99
  const launchContext = openCodeLaunchContext(process);
104
100
  return {
105
- text: [emitsRelayContext ? context.text : undefined, launchContext, text].filter(Boolean).join("\n\n"),
101
+ // Context is loaded through opencode.json instructions at system level. Keep first-turn
102
+ // acceptance bookkeeping, but never duplicate those instructions in user messages.
103
+ text,
106
104
  emitsRelayContext,
107
105
  emitsLaunchContext: launchContext !== undefined,
108
106
  };
@@ -126,7 +124,7 @@ export async function sendOpenCodeRelayMessage<T>(client: { sendMessage(sessionI
126
124
  }
127
125
 
128
126
  /**
129
- * #2534 — arm the acceptance watcher just before a context-carrying POST is dispatched. A
127
+ * arm the acceptance watcher just before a context-carrying POST is dispatched. A
130
128
  * prompt that emits no first-turn context has nothing to protect and never arms one, so
131
129
  * ordinary mid-session deliveries stay watcher-free.
132
130
  */
@@ -147,7 +145,7 @@ export function clearOpenCodeContextAcceptance(process: ManagedProcess): void {
147
145
  }
148
146
 
149
147
  /**
150
- * #2534 — consume the watcher from an observed persisted user text part. Identity is matched to
148
+ * consume the watcher from an observed persisted user text part. Identity is matched to
151
149
  * THIS launch incarnation on three axes, all of which must hold:
152
150
  * 1. the echo's session id equals the session the prompt was dispatched to, AND
153
151
  * 2. that session is still the process's ACTIVE session (a stale-session echo from a superseded
@@ -174,13 +172,13 @@ export function consumeOpenCodeContextAcceptance(process: ManagedProcess | undef
174
172
  }
175
173
 
176
174
  /**
177
- * #2534 — does the `GET /session/:id/message` body positively contain a persisted USER message
175
+ * does the `GET /session/:id/message` body positively contain a persisted USER message
178
176
  * whose text part equals `text` exactly AND that was created at or after `dispatchedAt`? The
179
177
  * outbound launch-carrying prompt (role/memory/gate briefing prepended to the assignment) is its
180
178
  * own fingerprint: a same-session prompt typed by the operator can never collide with it, so
181
179
  * exact equality is the identity match. But equality alone is not per-arm: every arm of send()'s
182
180
  * retry loop dispatches the SAME text into the SAME session, so an earlier arm's persisted
183
- * message would match a later arm's pre-acceptance failure (#2534 review round 2, MED-2) — the
181
+ * message would match a later arm's pre-acceptance failure (review round 2, MED-2) — the
184
182
  * real envelope's `info.time.created` (epoch ms, verified live against v1.18.21) is the
185
183
  * discriminator: only messages created at or after THIS arm's dispatch can prove THIS arm.
186
184
  * The response shape is parsed defensively — an unparsable body, or a match candidate with no
@@ -208,18 +206,18 @@ function sessionPersistsUserText(body: unknown, text: string, dispatchedAt: numb
208
206
  }
209
207
 
210
208
  /**
211
- * #2534 — best-effort acceptance proof for a failed completion await. The SSE door has usually
209
+ * best-effort acceptance proof for a failed completion await. The SSE door has usually
212
210
  * already marked by now (proof 1); if not, read the session's persisted messages once, on the
213
211
  * client's own short deadline, and consume the markers on an exact persisted-text match. Any
214
212
  * reconciliation failure is swallowed: an unreachable server cannot confirm acceptance, so the
215
213
  * markers stay false and the next attempt retries the launch text (fail-closed).
216
214
  *
217
- * #2534 review (MED-2) — the accepted short-circuit reads THIS dispatch arm's consumed flag,
215
+ * review (MED-2) — the accepted short-circuit reads THIS dispatch arm's consumed flag,
218
216
  * never the durable first-turn markers: the markers are process-scoped and survive across the
219
217
  * send() retry/re-roll loop's arms, so keying to them would make any later arm that fails
220
218
  * PRE-acceptance inherit an earlier arm's acceptance and report the assignment delivered.
221
219
  *
222
- * #2534 review round 2 (MED-2) — the persisted match is likewise arm-scoped: the reconciliation
220
+ * review round 2 (MED-2) — the persisted match is likewise arm-scoped: the reconciliation
223
221
  * runs with THIS arm's dispatch timestamp, so an earlier arm's identical persisted user message
224
222
  * (same immutable text, same session) can never prove a later arm's failed dispatch. Without a
225
223
  * pending arm there is nothing to prove — fail closed.
@@ -243,7 +241,7 @@ async function reconcileOpenCodeContextAcceptance(client: { sessionMessages?(ses
243
241
  }
244
242
 
245
243
  /**
246
- * #2534 — dispatch one attempt of the prepared turn, keeping first-turn marker consumption
244
+ * dispatch one attempt of the prepared turn, keeping first-turn marker consumption
247
245
  * keyed to PROMPT ACCEPTANCE rather than to the completion await's return. OpenCode's
248
246
  * `/message` POST is synchronous: it is ACCEPTED and its prompt persisted within moments of
249
247
  * dispatch, but the await only resolves when the whole assistant turn ends — so on a long turn