@cabane/companion 0.6.145 → 0.6.147

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.
package/dist/runtime.js CHANGED
@@ -73,18 +73,15 @@ var prepareHookSchema = z.object({
73
73
  // default is generous; a hook that hangs past this is killed and the turn
74
74
  // fails with a clear timeout message rather than pinning the companion.
75
75
  timeoutMs: z.number().int().positive().optional(),
76
- // CT943: re-invoke the hook on turns where a prepared result already exists,
77
- // in VALIDATE mode — the cached result is handed back as `prepared` and the
78
- // hook's stdout is ignored, so the cwd provably cannot move. Its only power
79
- // is to exit non-zero and fail the turn.
76
+ // Re-invoke the hook when a prepared result already exists, in VALIDATE
77
+ // mode: the cached result is handed back as `prepared` and stdout is
78
+ // ignored, so the cwd cannot move. Its only power is to exit non-zero and
79
+ // fail the turn. The hook resolves once per (conversation, agent), so a
80
+ // later message asking for a different environment is otherwise silently
81
+ // ignored; this lets a hook refuse it out loud.
80
82
  //
81
- // Why it exists: the hook runs exactly once per (conversation, agent), so a
82
- // later message asking for a different environment can't be honoured. Without
83
- // this, it is silently ignored — the host knows and says nothing, which is
84
- // the worst available outcome. An opt-in hook can now refuse it out loud.
85
- //
86
- // Default OFF, and deliberately: an operator hook that provisions
87
- // unconditionally must not start running on every turn because it upgraded.
83
+ // Default OFF: a hook that provisions unconditionally must not start
84
+ // running on every turn because the companion upgraded.
88
85
  validateCached: z.boolean().optional(),
89
86
  // Independent operator-owned reporter. Receives sanitized failure metadata
90
87
  // on stdin; never re-enters this hook and cannot change the turn outcome.
@@ -150,16 +147,15 @@ var runPrepareHook = (hook, input) => {
150
147
  CABANE_AGENT_USERNAME: input.agentUsername,
151
148
  CABANE_RUNTIME: input.runtime ?? "",
152
149
  CABANE_HOST_ACCESS: input.hostAccess ? "1" : "0",
153
- // CT319: the conversation anchor is gone. These are kept as DEPRECATED
154
- // back-compat constants so an old user prepare script that still reads
155
- // them doesn't crash; a hook should key off CABANE_TRIGGER_ENTRY_PATHS.
150
+ // DEPRECATED constants, kept so an old prepare script that reads them
151
+ // doesn't crash; a hook should key off CABANE_TRIGGER_ENTRY_PATHS.
156
152
  CABANE_ANCHOR_KIND: "workspace",
157
153
  CABANE_ANCHOR_PATH: "",
158
- // CT317: newline-joined — workspace paths never contain a newline, so a
154
+ // Newline-joined — workspace paths never contain a newline, so a
159
155
  // hook splits on `\n` unambiguously. Empty string when none.
160
156
  CABANE_TRIGGER_ENTRY_PATHS: input.triggerEntryPaths.join("\n"),
161
157
  CABANE_CONVERSATION_TITLE: input.title ?? "",
162
- // CT943: the dispatching message's text, and — in validate mode only —
158
+ // The dispatching message's text, and — in validate mode only —
163
159
  // the prepared result the hook is being asked to check this dispatch
164
160
  // against. Both empty/absent on an ordinary first run.
165
161
  CABANE_TRIGGER_MESSAGE: input.messageBody ?? "",
@@ -253,18 +249,17 @@ function configPath() {
253
249
  var localAgentConfigSchema = z2.object({
254
250
  cwd: z2.string().optional(),
255
251
  prepareHook: prepareHookSchema.optional(),
256
- // CT289: the Claude Code auto-memory escape hatch. Auto-memory is forced OFF
257
- // by default on every companion (memory belongs in the Cabane workspace, and a
258
- // shared companion would otherwise pool one cwd-keyed memory dir across users).
259
- // On a companion you run yourself, set `claudeCode: { autoMemory: true }` to hand
260
- // auto-memory back to your own `~/.claude/settings.json` — Cabane then stops
252
+ // The Claude Code auto-memory escape hatch. Auto-memory is forced OFF by
253
+ // default (memory belongs in the Cabane workspace, and a shared companion
254
+ // would pool one cwd-keyed memory dir across users). On a companion you run
255
+ // yourself, `claudeCode: { autoMemory: true }` hands auto-memory back to your
256
+ // own `~/.claude/settings.json` — Cabane then stops
261
257
  // injecting the off switch and your normal Claude Code memory workflow applies
262
258
  // (in coding mode, where the checkout's project settings are read).
263
259
  claudeCode: z2.object({ autoMemory: z2.boolean().optional() }).strict().optional(),
264
- // CT1693: this agent's exception to `runners.sandbox` — an ops agent that
265
- // runs with the machine on a sandboxed device, or one sandboxed agent on a
266
- // device that isn't. Absent is "inherit"; see `runnerSandboxFor`. Only on a
267
- // row keyed by the agent's id (`agentsConfigSchema`).
260
+ // This agent's exception to `runners.sandbox`: an ops agent that runs with
261
+ // the machine on a sandboxed device, or one sandboxed agent on a device that
262
+ // isn't. Absent is "inherit". Only on a row keyed by the agent's id.
268
263
  sandbox: z2.enum(["os", "none"]).optional()
269
264
  }).strict();
270
265
  var AGENT_ID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -280,9 +275,8 @@ var agentsConfigSchema = z2.record(z2.string(), localAgentConfigSchema).superRef
280
275
  }
281
276
  });
282
277
  var companionConfigSchema = z2.object({
283
- // The cabane instance this device is paired with. SJ515: https-enforced
284
- // (loopback exempt) so a hand-edited config can't smuggle a plaintext-http
285
- // base URL onto the MITM-able channel the device token + prompt ride.
278
+ // https-enforced (loopback exempt), so a hand-edited config can't put the
279
+ // device token and prompt on a plaintext channel.
286
280
  baseUrl: z2.string().url().refine(isAllowedBaseUrl, {
287
281
  message: "baseUrl must be https (loopback http is allowed for local dev only)"
288
282
  }),
@@ -300,91 +294,59 @@ var companionConfigSchema = z2.object({
300
294
  // agentId / username / `slug/username`. Hand-added by the operator; the companion
301
295
  // never writes this (it only persists credentials + the device, elsewhere).
302
296
  agents: agentsConfigSchema.optional(),
303
- // CT1095: the DEVICE-LEVEL prepare hook — one entry that applies to every agent
304
- // assigned to this device, unless that agent's own `agents` row names a hook of
305
- // its own.
306
- //
307
- // Until now `prepareHook` existed only under `agents[<id>]`, which made the
308
- // config a second silent roster: a device whose provisioning is genuinely
309
- // per-device (ours resolves a directory from the conversation and agent ids the
310
- // companion already hands the hook) still had to list every agent by id, and an
311
- // agent nobody remembered to add got no hook at all — no error, no log, just a
312
- // turn that started in the operator's `$HOME`. That is how @dale woke in
313
- // `/home/joshed` on 2026-08-17 with a perfectly correct dispatch.
314
- //
315
- // Absent → nothing changes for anyone, which is why this can land inert: a
316
- // device with no device-level entry behaves exactly as it did before. A hook
317
- // that fails still fails the dispatch loudly, as now.
297
+ // The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
298
+ // `agents` row names one. Per-agent hooks alone make the config a second
299
+ // silent roster: an agent nobody listed gets no hook and no error, and starts
300
+ // in the operator's `$HOME` (an agent woke in `/home/joshed` this way on
301
+ // 2026-08-17).
318
302
  prepareHook: prepareHookSchema.optional(),
319
- // CT1085 §9: `dashboardPort` and `autoOpen` are READ AND IGNORED by the CLI —
320
- // it binds no port and opens no browser. They stay in the schema (rather than
321
- // becoming a strict-mode parse error on an existing config file) and still mean
322
- // what they say to the Electron shell, the one front-end that renders a
323
- // dashboard. `logLevel` is unaffected: pino level, live-editable.
303
+ // `dashboardPort` and `autoOpen` are IGNORED by the CLI (no port, no browser)
304
+ // but stay in the schema so an existing file still parses; the Electron shell
305
+ // reads them. `logLevel` is pino's level, live-editable.
324
306
  dashboardPort: z2.number().int().min(1).max(65535).optional(),
325
307
  autoOpen: z2.boolean().optional(),
326
308
  logLevel: z2.enum(["warn", "info", "debug"]).optional(),
327
309
  logFormat: z2.enum(["human", "json"]).optional(),
328
- // CT270: the opencode runtime, when the operator runs one on this machine. The
329
- // operator installs opencode, starts `opencode serve` (auth via opencode's own
330
- // `/connect` — Cabane never sees provider keys), and points the companion at it
331
- // here. Setting this makes the device advertise the `opencode` runtime on its
332
- // heartbeat manifest (so the server offers DeepSeek/opencode models here and
333
- // routes those turns to this device) AND registers the opencode adapter in the
334
- // dispatcher. Absent → the device is claude-code-only, exactly as before.
310
+ // An `opencode serve` the operator runs here (auth via opencode's own
311
+ // `/connect`; Cabane never sees provider keys). Set, the device advertises the
312
+ // `opencode` runtime and registers its adapter.
335
313
  opencode: z2.object({
336
314
  serverUrl: z2.string().url()
337
315
  }).strict().optional(),
338
- // CT1082: the Claude Code runtime, when the user connected it on this machine.
339
- // The change this task exists for: Claude Code used to be the one harness a
340
- // device exposed with no configuration — a `claude --version` exit-0 was taken as
341
- // consent — and it is now the third config-driven harness, opted into exactly
342
- // like codex. CT1379: the binary that must be there is the Agent SDK's own
343
- // bundled executable — the file a turn actually launches — not the user's
344
- // `claude` on PATH, which the SDK never runs. The manifest advertises
345
- // claude-code only when this block says so AND that binary resolves.
316
+ // Whether the user connected Claude Code on this machine: consent, opted into
317
+ // like codex. The manifest advertises claude-code only when this says so AND
318
+ // the Agent SDK's bundled executable resolves (not `claude` on PATH, which the
319
+ // SDK never runs).
346
320
  //
347
- // The block's PRESENCE is also the migration marker (see `migrateConnectedHarnesses`).
348
- // Absent means the config predates this task — a device whose user was never
349
- // asked — and the migration grandfathers it on first start. So every config
350
- // written from here on carries the block explicitly, including a freshly-paired
351
- // one, which starts at `enabled: false`: a new device is connected to nothing
352
- // until its user says otherwise.
321
+ // The block's PRESENCE is also the migration marker
322
+ // (`migrateConnectedHarnesses`): absent means a config whose user was never
323
+ // asked. So every config written carries it, a fresh pair at `enabled: false`.
353
324
  //
354
- // Not to be confused with the per-agent `agents.<key>.claudeCode.autoMemory`
355
- // above — that's one agent's memory switch, this is the device's connected set.
325
+ // Not the per-agent `agents.<key>.claudeCode.autoMemory`, one agent's memory
326
+ // switch.
356
327
  claudeCode: z2.object({
357
328
  enabled: z2.boolean().optional()
358
329
  }).strict().optional(),
359
- // CT481: the codex runtime, when the operator runs Codex on this machine. Unlike
360
- // opencode (a long-lived `opencode serve` addressed by URL), Codex is a local CLI
361
- // the `@openai/codex-sdk` spawns per turn — so the config is just an opt-in flag,
362
- // not a server address. The operator installs Codex + logs in (`codex login`, or
363
- // `CODEX_API_KEY` — Cabane never sees the key) and sets `{ "codex": { "enabled":
364
- // true } }`. Presence of the block enables it (an explicit `enabled: false`
365
- // keeps the block but turns it off). Absent → the device doesn't offer codex.
366
- // CT1379: enabling is now half the answer, not all of it — the manifest and the
367
- // adapter registry additionally require the SDK's vendored `codex` binary to
368
- // resolve, because the enable flag alone advertised a runtime a device with a
369
- // half-installed platform package could not start.
330
+ // Codex is a local CLI the SDK spawns per turn, so this is an opt-in flag, not
331
+ // an address. The operator logs in (`codex login` or `CODEX_API_KEY`; Cabane
332
+ // never sees the key). The block's presence enables it; `enabled: false` turns
333
+ // it off. Half the answer: advertising also requires the SDK's vendored
334
+ // `codex` binary to resolve.
370
335
  codex: z2.object({
371
336
  enabled: z2.boolean().optional()
372
337
  }).strict().optional(),
373
- // CT1679: run each turn in a child runner process instead of in this one.
374
- // Absent, or `enabled: false`, is the in-process path, untouched. `sandbox`
375
- // wraps the runner in an OS sandbox where the host supports one; `'none'`
376
- // (the default) runs it as a plain child beside this process, a supported
377
- // configuration in its own right rather than a degraded one.
378
- // CT1684: `policy` is what the OS sandbox lets a turn reach, read and hold
379
- // (`runner/sandbox-policy.ts`). It is validated there rather than here, so a
380
- // bad policy refuses the turns it governs, with the field named, instead of
381
- // keeping the companion from starting at all.
338
+ // Run each turn in a child runner process. Absent or `enabled: false` is the
339
+ // in-process path. `sandbox: 'os'` wraps the runner in an OS sandbox where the
340
+ // host supports one; `'none'` (the default) is a plain child, supported in its
341
+ // own right. `policy` is what the sandbox lets a turn reach, read and hold,
342
+ // validated in `runner/sandbox-policy.ts` so a bad policy refuses the turns it
343
+ // governs, field named, instead of stopping the companion starting.
382
344
  runners: z2.object({
383
345
  enabled: z2.boolean().optional(),
384
346
  provider: z2.enum(["local-process", "docker"]).optional(),
385
347
  sandbox: z2.enum(["os", "none"]).optional(),
386
- // CT1681: the `docker` provider — a container per conversation and agent,
387
- // from one image, on a network whose only way out is an allow-listed proxy.
348
+ // A container per conversation and agent, from one image, on a network
349
+ // whose only way out is an allow-listed proxy.
388
350
  docker: z2.object({
389
351
  // The base image a container runs. Default: the one this companion
390
352
  // builds from its own Dockerfile on first use. One named here must
@@ -877,9 +839,8 @@ var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
877
839
  function deriveHarnessSnapshot(signals) {
878
840
  const advertised = new Set(
879
841
  buildCompanionManifest({
880
- // CT1082 + CT1379: connected AND the bundled binary the SDK launches is
881
- // there — the manifest's own rule, restated here through the same function
882
- // rather than re-decided. PATH is deliberately absent from both gates now.
842
+ // Connected AND the bundled binary present: the manifest's own rule. PATH
843
+ // is deliberately in neither gate.
883
844
  claudeCode: signals.claudeCodeConnected && signals.claudeBundled === "present",
884
845
  opencode: signals.opencodeConfigured,
885
846
  codex: signals.codexEnabled && signals.codexBundled === "present"
@@ -917,10 +878,8 @@ function deriveClaudeCode(signals, manifestHas) {
917
878
  ...base,
918
879
  state: "detected_not_exposed",
919
880
  version: signals.claudeVersion,
920
- // CT1379: connecting records INTENT; it is not the same as becoming
921
- // runnable. With the bundled binary missing, a connect succeeds and the
922
- // device still advertises nothing — so this says so up front rather than
923
- // promising routing it can't deliver.
881
+ // Connecting records INTENT, not runnability: with the bundled binary
882
+ // missing a connect succeeds and nothing is advertised, so say so.
924
883
  detail: signals.claudeBundled === "present" ? "Claude Code is installed here but not connected yet. Connect it to offer Claude Code from this device." : `Claude Code is installed here but not connected yet \u2014 and @cabane/companion's bundled Claude Code ${missingNoun(signals.claudeBundled)} is missing, so connecting alone won't make it runnable. ${repairAction()}`,
925
884
  enable: "claude-code"
926
885
  };
@@ -958,8 +917,7 @@ function deriveCodex(signals, manifestHas) {
958
917
  ...base,
959
918
  state: "detected_not_exposed",
960
919
  version: signals.codexVersion,
961
- // CT1379: same distinction as Claude Code — enabling is intent, not
962
- // launchability.
920
+ // As for Claude Code: enabling is intent, not launchability.
963
921
  detail: signals.codexBundled === "present" ? "Codex is installed but not enabled yet. Turn it on to offer Codex from this device." : `Codex is installed but not enabled yet \u2014 and @cabane/companion's bundled Codex ${missingNoun(signals.codexBundled)} is missing, so enabling alone won't make it runnable. ${repairAction()}`,
964
922
  enable: "codex"
965
923
  };
@@ -1040,12 +998,10 @@ async function probeHarnessSignals(cfg, deps = {}) {
1040
998
  return {
1041
999
  claudeOnPath: claudePresence.status === "present",
1042
1000
  claudeVersion,
1043
- // CT1082: the user's opt-in. Presence alone exposes nothing now, so this is
1044
- // the manifest gate and the probe above is only a suggestion.
1001
+ // The user's opt-in, the manifest gate; the probe above only suggests.
1045
1002
  claudeCodeConnected: isClaudeCodeConnected(cfg),
1046
- // CT1379: the executable a Claude turn actually launches. Resolved every time
1047
- // the signals are, so a binary that disappears between beats is caught on the
1048
- // next one rather than at turn time.
1003
+ // The executable a Claude turn launches, resolved with every probe so a
1004
+ // binary that disappears is caught on the next beat, not at turn time.
1049
1005
  claudeBundled: resolveBundled("claude-code").status,
1050
1006
  codexBundled: resolveBundled("codex").status,
1051
1007
  // A parseable `codex --version` is our presence signal (presence alone never
@@ -1054,7 +1010,7 @@ async function probeHarnessSignals(cfg, deps = {}) {
1054
1010
  codexVersion,
1055
1011
  codexEnabled: isCodexEnabled(cfg),
1056
1012
  opencodeConfigured: !!configuredServerUrl,
1057
- // A version came back ⟺ the serve answered its health endpoint (CT584).
1013
+ // A version came back ⟺ the serve answered its health endpoint.
1058
1014
  opencodeReachable: opencodeVersion !== null,
1059
1015
  opencodeVersion,
1060
1016
  opencodeDetectedUrl: opencodeVersion !== null ? opencodeProbeUrl : null
@@ -1320,7 +1276,7 @@ var CompanionStateHub = class {
1320
1276
  deviceId = null;
1321
1277
  deviceLabel = null;
1322
1278
  deviceError = null;
1323
- // CT586: the latest harness snapshot the supervisor probed, or null before the
1279
+ // The latest harness snapshot the supervisor probed, or null before the
1324
1280
  // first probe (see CompanionStatusJson.harnesses).
1325
1281
  harnesses = null;
1326
1282
  // ---- subscription (SSE) ----
@@ -1347,7 +1303,7 @@ var CompanionStateHub = class {
1347
1303
  this.deviceError = null;
1348
1304
  this.emitStatus();
1349
1305
  }
1350
- // ---- harnesses (CT586) ----
1306
+ // ---- harnesses ----
1351
1307
  // Replace the harness snapshot and push a status change (so the SSE feed flips
1352
1308
  // the Companion's Harnesses surface live). The supervisor calls this on each
1353
1309
  // heartbeat-cadence probe and on an on-demand recheck.
@@ -2292,7 +2248,7 @@ var LEASE_REFUSALS = /* @__PURE__ */ new Set([
2292
2248
  "dispatch_not_admitted",
2293
2249
  "turn_already_ended",
2294
2250
  "turn_belongs_elsewhere",
2295
- // Rooms: the context this start was composed from named an attempt the
2251
+ // The context this start was composed from named an attempt the
2296
2252
  // lease no longer picks (midnight rolled it, a degrade retired it). Nothing
2297
2253
  // was granted; the turn recomposes its context and starts again, once.
2298
2254
  "dispatch_context_stale"
@@ -2390,10 +2346,8 @@ var CabaneApi = class {
2390
2346
  this.opts = opts;
2391
2347
  }
2392
2348
  opts;
2393
- // CT25: re-entrancy guard for `drainOutbox`. The supervisor kicks a drain
2394
- // from several places (startup, SSE reconnect, the backoff timer); without
2395
- // this guard two overlapping drains could each pick up the same queued entry
2396
- // and double-send it (the server dedupes, but the wasted POSTs aren't free).
2349
+ // Re-entrancy guard for `drainOutbox`: drains are kicked from several places,
2350
+ // and two overlapping ones would double-send the same entry.
2397
2351
  draining = false;
2398
2352
  lastDeliveryWarning = 0;
2399
2353
  get base() {
@@ -2455,19 +2409,13 @@ var CabaneApi = class {
2455
2409
  }
2456
2410
  }
2457
2411
  }
2458
- // CT25: send-or-enqueue for a turn-row commit. Tries the immediate
2459
- // (retry-budgeted) send first so the happy path keeps its current latency,
2460
- // then on a *retryable* failure persists the row to the on-disk outbox
2461
- // instead of dropping it. Classification is deliberate:
2462
- // - success → done.
2463
- // - no outbox configured (one-shot CLI / tests) → rethrow, exactly the
2464
- // pre-CT25 behaviour.
2465
- // - cancel (the turn was stopped; `signal` aborted or an AbortError) →
2466
- // rethrow without queuing: the user asked us to stop, and the held text
2467
- // was already dropped — we must not resurrect it from the queue.
2468
- // - terminal 4xx (a deterministic rejection a replay can't fix) → rethrow,
2469
- // so the caller's error path runs as it does today (e.g. the committer
2470
- // re-arms its empty-final fallback).
2412
+ // Send-or-enqueue for a turn-row commit: the immediate (retry-budgeted) send
2413
+ // first, then on a *retryable* failure persist to the outbox.
2414
+ // - no outbox configured (one-shot CLI / tests) → rethrow.
2415
+ // - cancel (`signal` aborted or an AbortError) → rethrow without queuing:
2416
+ // the user asked us to stop, and the text must not resurrect from the queue.
2417
+ // - terminal 4xx (a replay can't fix it) → rethrow, so the caller's error
2418
+ // path runs.
2471
2419
  // - retryable (transport / 5xx — commit outcome may be unknown) → queue
2472
2420
  // and return. This acknowledges local delivery responsibility, not server
2473
2421
  // persistence. Replays retain the endpoint's identity: messages/thinking/
@@ -2492,13 +2440,10 @@ var CabaneApi = class {
2492
2440
  );
2493
2441
  }
2494
2442
  }
2495
- // CT25: flush the outbox FIFO. Removes an entry once the server acknowledges
2496
- // it (2xx) or rejects it terminally (any 4xx — a deleted conversation 404, a
2497
- // forbidden, etc. — which a replay can never satisfy, so we discard with a
2498
- // log rather than spin forever). A retryable failure (transport / 5xx) means
2499
- // the API is still down: stop the cycle and leave that entry and the rest
2500
- // queued for the next drain. Returns a summary the supervisor's backoff
2501
- // scheduler reads. Re-entrancy-guarded so overlapping kicks don't double-send.
2443
+ // Flush the outbox FIFO. An entry goes once the server acknowledges it (2xx)
2444
+ // or rejects it terminally (any 4xx, which a replay can never satisfy: discard
2445
+ // with a log rather than spin). A retryable failure means the API is still
2446
+ // down: stop and leave that entry and the rest for the next drain.
2502
2447
  async drainOutbox() {
2503
2448
  const outbox = this.opts.outbox;
2504
2449
  if (!outbox) return { remaining: 0, progressed: false };
@@ -2546,40 +2491,24 @@ var CabaneApi = class {
2546
2491
  }
2547
2492
  return { remaining: outbox.size(), progressed };
2548
2493
  }
2549
- // CT25: how many commits are currently queued for this workspace — the
2550
- // supervisor's drain scheduler reads it to decide whether to keep polling.
2494
+ // How many commits are queued; the drain scheduler reads it to decide whether
2495
+ // to keep polling.
2551
2496
  outboxSize() {
2552
2497
  return this.opts.outbox?.size() ?? 0;
2553
2498
  }
2554
- // CT262: the ONE turn-context fetch. Collapses the companion's old four-fetch
2555
- // choreography (getConversation + getMessage + getAgentSelf + getParticipantAgent)
2556
- // into a single call: the server composes the whole server portion of the
2557
- // `TurnRequest` — systemPrompt, per-turn prompt + vision content, effective
2558
- // run-config, `HostPolicy`, prior session, the user MCP definitions to resolve
2559
- // locally, plus the anchor/title + trigger-message summary the host needs.
2560
- // Agent-PAT authed; the workspace is implied by the PAT.
2561
- // CT714: `turnId` is the host-minted id for THIS turn, passed so the server can
2562
- // bind the minted turn token to it — the turn-control surface then rejects a
2563
- // token whose turn has ended. The dispatcher mints it before this call and
2564
- // reuses the same value on its active-run PATCH, so the token's turn id and the
2565
- // pair's `active_turn_id` agree.
2566
- // CT1380: `resumed` says this dispatch is a REPLAY of an interrupted turn, so
2567
- // the server should hand back `turnSeqFloor` — the turn's committed seq
2568
- // high-water mark. Only the supervisor knows (it minted the id or recovered
2569
- // it), so it is passed down rather than inferred here. It gates the COST of
2570
- // the aggregate, never the tenancy scoping, which the route applies
2571
- // unconditionally.
2572
- // `fresh` asks for the context a session that holds NOTHING needs: the read
2573
- // from the start of the conversation, the agent's own words included, and
2574
- // the first-turn material — what the adapter runs on when it refused to
2575
- // resume the stored session and is starting a fresh one.
2576
- // CT1543: `episode` is the dispatch event's own, echoed back so the server can
2577
- // tell this frame from a later wake on the same message. Omitted when the
2578
- // event didn't carry one, which is the only thing an older event can mean.
2579
- // CT1657: `signal` is the turn's own abort. It matters here more than on any
2580
- // other read, because this call now waits up to ~8s across five attempts: a
2581
- // Stop landing in that window used to leave the whole schedule running, and
2582
- // the person watched a turn they had already stopped keep trying.
2499
+ // The ONE turn-context fetch: the server composes the whole server portion of
2500
+ // the `TurnRequest`. Agent-PAT authed; the workspace is implied by the PAT.
2501
+ // - `turnId`: the host-minted id the server binds the turn token to, so the
2502
+ // turn-control surface rejects a token whose turn has ended.
2503
+ // - `resumed`: a REPLAY of an interrupted turn, so the server returns
2504
+ // `turnSeqFloor`. Only the supervisor knows. It gates the cost of the
2505
+ // aggregate, never the tenancy scoping.
2506
+ // - `fresh`: the context a session holding NOTHING needs (the whole read,
2507
+ // own words included, plus first-turn material), for an adapter that
2508
+ // refused to resume the stored session.
2509
+ // - `episode`: the dispatch event's own, so the server can tell this frame
2510
+ // from a later wake on the same message. Omitted by an older event.
2511
+ // - `signal`: the turn's abort, so a Stop cuts the ~8s retry schedule short.
2583
2512
  getTurnContext(conversationId, messageId2, turnId, resumed, fresh, episode, signal) {
2584
2513
  const q = `conversationId=${encodeURIComponent(conversationId)}&messageId=${encodeURIComponent(messageId2)}` + (turnId ? `&turnId=${encodeURIComponent(turnId)}` : "") + (resumed ? "&resumed=1" : "") + (fresh ? "&fresh=1" : "") + (episode !== void 0 ? `&episode=${encodeURIComponent(String(episode))}` : "");
2585
2514
  return this.request("GET", `/api/agent/turn-context?${q}`, void 0, {
@@ -2587,9 +2516,8 @@ var CabaneApi = class {
2587
2516
  ...signal ? { signal } : {}
2588
2517
  });
2589
2518
  }
2590
- // CT1380: the local half of a resumed turn's seq floor (see
2591
- // `Outbox.maxSeqForTurn`). 0 when no outbox is configured — a one-shot CLI or
2592
- // test has no durable queue, so nothing can be hiding in it.
2519
+ // The local half of a resumed turn's seq floor (`Outbox.maxSeqForTurn`). 0
2520
+ // with no outbox: nothing can be hiding in a queue that doesn't exist.
2593
2521
  outboxMaxSeqForTurn(turnId) {
2594
2522
  return this.opts.outbox?.maxSeqForTurn(turnId) ?? 0;
2595
2523
  }
@@ -2605,13 +2533,11 @@ var CabaneApi = class {
2605
2533
  (e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
2606
2534
  );
2607
2535
  }
2608
- // CT1677: ADOPT the issuance a turn context carried as the execution source of
2609
- // the work about to run on it. Made once per context, before preparation emits
2610
- // or queues anything, so every row that work commits can name a source the
2611
- // server has durably recorded and will never reap. Retried like any write: the
2612
- // server's adoption is idempotent. A 409 `execution_source_unavailable` is a
2613
- // refusal — nothing to adopt, the reaper got there first — and the caller
2614
- // fetches a fresh context rather than working on this one.
2536
+ // ADOPT a turn context's issuance as the execution source of its work, once,
2537
+ // before preparation emits or queues anything, so every row can name a source
2538
+ // the server durably recorded and will never reap. Adoption is idempotent, so
2539
+ // retried. A 409 `execution_source_unavailable` means the reaper got there
2540
+ // first; the caller fetches a fresh context.
2615
2541
  adoptExecutionSource(issuanceId, body, signal) {
2616
2542
  return this.request(
2617
2543
  "POST",
@@ -2620,10 +2546,10 @@ var CabaneApi = class {
2620
2546
  signal ? { retry: true, signal } : { retry: true }
2621
2547
  );
2622
2548
  }
2623
- // CT1680: the launcher mints the runner credential for one dispatch, on its
2624
- // PAT and from its device, and hands it to the runner in place of both. A
2625
- // retry is safe: the server returns the same runner id and retires the
2626
- // credential the lost response carried.
2549
+ // The launcher mints the runner credential for one dispatch, on its PAT and
2550
+ // from its device, and hands it to the runner in place of both. A retry is
2551
+ // safe: the server returns the same runner id and retires the credential the
2552
+ // lost response carried.
2627
2553
  mintRunnerCredential(body) {
2628
2554
  return this.request("POST", "/api/agent/runner-credentials", body, { retry: true });
2629
2555
  }
@@ -2643,7 +2569,7 @@ var CabaneApi = class {
2643
2569
  // invocation. The server records the effect and receipt in one transaction;
2644
2570
  // an ok:false response is a recorded refusal for the tool to report.
2645
2571
  // Retries retain the caller's callId and recover the same receipt, even
2646
- // when the turn settled between attempts (CT1447): the route consults the
2572
+ // when the turn settled between attempts: the route consults the
2647
2573
  // receipt before it refuses an ended turn, so a 4xx proves no attempt wrote.
2648
2574
  // The caller's signal bounds how long the model waits for the tool result.
2649
2575
  turnAct(workspaceId, turnId, act, signal) {
@@ -2654,36 +2580,23 @@ var CabaneApi = class {
2654
2580
  signal ? { retry: true, signal } : { retry: true }
2655
2581
  );
2656
2582
  }
2657
- // CT1292: RENEW THE TURN'S LEASE — ask the server whether this turn is still
2658
- // running. The dispatcher calls this on a cadence for the life of the SDK loop,
2659
- // and out of cadence the moment a commit is refused, so a loop whose turn was
2660
- // terminalized underneath it stops instead of running to the six-hour cap.
2583
+ // RENEW THE TURN'S LEASE: is this turn still running? Called on a cadence for
2584
+ // the life of the SDK loop and the moment a commit is refused, so a loop whose
2585
+ // turn was terminalized underneath it stops.
2661
2586
  //
2662
- // Single-shot and NEVER retried, on purpose. A renewal is a question about
2663
- // right now, not a durable write: a failed one has no answer worth replaying,
2664
- // and the next tick asks again in a few seconds anyway. Retrying would only
2665
- // stretch one tick over the next one's slot.
2587
+ // Single-shot, NEVER retried: a failed renewal has no answer worth replaying
2588
+ // and the next tick asks again; retrying stretches one tick over the next.
2666
2589
  //
2667
- // ALL FAILURE IS `unknown`, and this is the safety property the whole feature
2668
- // rests on. The only thing that may stop a running loop is the server SAYING
2669
- // the turn is over; a 500, a dropped connection, an expired token or a body we
2670
- // cannot parse says nothing about the turn, and reading any of them as death
2671
- // would kill healthy turns every time the API hiccups — strictly worse than the
2672
- // gap this closes. So the failure direction is fixed here, at the boundary,
2673
- // rather than left to each caller to remember.
2590
+ // ALL FAILURE IS `unknown`. Only the server SAYING the turn is over may stop a
2591
+ // running loop; a 500, a dropped connection, an expired token or an
2592
+ // unparseable body says nothing about the turn, and reading it as death would
2593
+ // kill healthy turns on every API hiccup. Fixed here, at the boundary, so no
2594
+ // caller has to remember.
2674
2595
  //
2675
- // AND IT IS DEADLINED (Codo's blocking finding #1). `fetch` has no default
2676
- // timeout, so a request that neither resolves nor rejects — a black-holed
2677
- // connection, a half-open socket surviving a network change — would hang this
2678
- // promise forever. That is not merely a slow probe: the caller single-flights
2679
- // on it, so one stalled request latches the check permanently, every later
2680
- // cadence tick returns immediately, and the loop this exists to stop runs to
2681
- // the six-hour cap even after connectivity comes back — the exact defect the
2682
- // task is about, reintroduced through the mechanism meant to close it.
2683
- //
2684
- // The deadline turns that into an ordinary `unknown`: the request rejects, the
2685
- // in-flight latch clears, and the NEXT tick asks again. Well under the renewal
2686
- // cadence so a stalled probe is finished with before its successor is due.
2596
+ // DEADLINED, because `fetch` has no default timeout: a black-holed request
2597
+ // would hang forever, and the caller single-flights on it, so one stall would
2598
+ // latch the check and let the loop run to the cap. The deadline makes it an
2599
+ // ordinary `unknown` and the next tick asks again.
2687
2600
  async checkTurnLease(workspaceId, conversationId, agentId, turnId) {
2688
2601
  const q = `turnId=${encodeURIComponent(turnId)}`;
2689
2602
  try {
@@ -2692,11 +2605,7 @@ var CabaneApi = class {
2692
2605
  `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/turn-lease?${q}`,
2693
2606
  void 0,
2694
2607
  {
2695
- // CT1657: the one GET that opts OUT of the default retry. The reasoning
2696
- // is right above — a failed probe has no answer worth replaying and the
2697
- // next tick asks again in seconds, so retrying inside this one would
2698
- // stretch a tick over its successor's slot, which is what the deadline
2699
- // exists to prevent.
2608
+ // The one GET that opts OUT of the default retry; see above.
2700
2609
  retry: false,
2701
2610
  signal: AbortSignal.timeout(this.opts.leaseCheckTimeoutMs ?? LEASE_CHECK_TIMEOUT_MS)
2702
2611
  }
@@ -2730,9 +2639,8 @@ var CabaneApi = class {
2730
2639
  }
2731
2640
  return this.request("PATCH", path, body);
2732
2641
  }
2733
- // CT93: send-or-enqueue for an active-run flag write, with last-writer-wins
2734
- // across the (conversation, agent) pair. Mirrors `durableCommit`, with two
2735
- // deliberate differences:
2642
+ // Send-or-enqueue for an active-run flag write, last-writer-wins per
2643
+ // (conversation, agent) pair. Like `durableCommit`, except:
2736
2644
  // - No abort signal is threaded. The clear is the turn's teardown and must
2737
2645
  // land even when the turn was *cancelled* (the abort signal is set) — a
2738
2646
  // cancelled turn still has to drop its "working" indicator.
@@ -2786,9 +2694,6 @@ var CabaneApi = class {
2786
2694
  );
2787
2695
  }
2788
2696
  }
2789
- // CT29: per-device liveness moved off the per-workspace agent PAT and onto the
2790
- // device token — see `DeviceApi.heartbeat`. There is no agent-PAT heartbeat
2791
- // anymore.
2792
2697
  // Commit the agent's first/last words (message) or a runtime_notice through
2793
2698
  // the public messages endpoint. Intermediate working text uses reportText.
2794
2699
  // Attribution comes from the agent credential; a new speech row can offer
@@ -2937,7 +2842,7 @@ var DeviceApi = class {
2937
2842
  AbortSignal.timeout(5e3)
2938
2843
  );
2939
2844
  }
2940
- // CT1146: report that a dispatch addressed to THIS device can't be run, because
2845
+ // Report that a dispatch addressed to THIS device can't be run, because
2941
2846
  // the agent isn't one this device runs (and still wasn't after a forced
2942
2847
  // assignments refresh). The server clears the Working flag, retires the dispatch
2943
2848
  // and posts an explanatory system message.
@@ -2951,7 +2856,7 @@ var DeviceApi = class {
2951
2856
  return this.request("POST", "/api/companion/dispatch-drop", body);
2952
2857
  }
2953
2858
  // Per-device liveness ping. Reports the companion build version and the env-var
2954
- // names the operator's secret store exposes (never values), so CT30's UI can
2859
+ // names the operator's secret store exposes (never values), so the UI can
2955
2860
  // warn pre-emptively about an agent that needs a secret this device lacks.
2956
2861
  heartbeat(body) {
2957
2862
  return this.request("POST", "/api/companion/heartbeat", body ?? {});
@@ -3188,95 +3093,66 @@ var TURN_PROTOCOL_VERSION = 1;
3188
3093
  // packages/agent-runtime/src/host-policy.ts
3189
3094
  import { z as z5 } from "zod";
3190
3095
  var hostPolicySchema = z5.object({
3191
- // Host filesystem + shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the
3192
- // notebook read/write pair, git-worktree tools, and the `REPL` host code-exec
3193
- // tool. Off under the locked assistant surface (today's `DISALLOWED_TOOLS`);
3194
- // on under `coding` mode.
3096
+ // Host filesystem and shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the notebook pair,
3097
+ // git-worktree tools and `REPL`. Off on the locked assistant surface; on in the coding posture.
3195
3098
  hostFs: z5.boolean(),
3196
- // Public web (today's `WebSearch` / `WebFetch`, the `DEFAULT_WEB_TOOLS`). Public
3197
- // web, not host reach — granted by default today, but expressible as a grant.
3099
+ // Public web (`WebSearch` / `WebFetch`). Not host reach; a grant like the rest.
3198
3100
  web: z5.boolean(),
3199
- // Browser automation (the Playwright MCP surface). Varies by host: a companion has
3200
- // it, the house executor does not (CT230).
3101
+ // Browser automation (the Playwright MCP surface). Varies by host.
3201
3102
  browser: z5.boolean(),
3202
- // User-configured MCP servers permitted. False for the house executor
3203
- // (CT227: Cabane agents run no user MCP servers), true for a personal companion.
3103
+ // User-configured MCP servers permitted.
3204
3104
  userMcp: z5.boolean(),
3205
- // Subagent / sub-dispatch (`Task` / `Agent`). A per-host GRANT (see the CT261
3206
- // amendment above): `false` on the locked assistant/house surface (banned via
3207
- // `DISALLOWED_TOOLS`), `true` under coding / custom where the fan-out is left to
3208
- // the coding allow-hook (which reaches sub-agent tool calls, CT680) or the user
3209
- // allowlist. The subagent completes within the turn, so
3210
- // it's not the turn-model invariant `scheduling` is.
3105
+ // Subagent dispatch (`Task` / `Agent`) — a grant: `false` on the locked surface, `true` in the
3106
+ // coding posture, where the coding allow-hook reaches sub-agent tool calls. A subagent completes
3107
+ // within the turn, so it's not an invariant like `scheduling`.
3211
3108
  subagents: z5.boolean(),
3212
3109
  // ── Hard platform invariants — always denied, never granted ────────────────
3213
- // Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*`
3214
- // families, `Monitor`, `Workflow`, …). A turn is one query that resolves when
3215
- // `result` fires; a scheduled callback fires after the reply window has closed
3216
- // and strands the agent (the CT155/CT156 rule).
3110
+ // Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*` families,
3111
+ // `Monitor`, `Workflow`, …). A turn is one query that resolves on `result`; a scheduled callback
3112
+ // fires after the reply window has closed and strands the agent.
3217
3113
  scheduling: z5.literal("never"),
3218
- // Human-facing UI prompts (`AskUserQuestion`). A Cabane conversation has no
3219
- // handler to answer a structured prompt, so the call hangs the turn
3220
- // (`UNSUPPORTED_TOOLS`). The agent asks in its reply instead.
3114
+ // Human-facing UI prompts (`AskUserQuestion`). Nothing in a Cabane conversation can answer a
3115
+ // structured prompt, so the call hangs the turn. The agent asks in its reply instead.
3221
3116
  uiPrompts: z5.literal("never")
3222
3117
  });
3223
3118
 
3224
3119
  // packages/agent-runtime/src/turn-event.ts
3225
3120
  import { z as z6 } from "zod";
3226
3121
  var turnEventSchema = z6.discriminatedUnion("type", [
3227
- // The runtime's opaque session state, emitted when the adapter learns it (e.g.
3228
- // the SDK `system/init` frame). The platform stores `state` verbatim per
3229
- // (conversation, agent) and hands it back on the next turn; only the adapter
3230
- // knows what it means. Today's companion captures the raw SDK session id here; a
3231
- // future adapter may encode more (e.g. `{sdkSessionId, cwd}`) — still one
3232
- // opaque string to the platform.
3122
+ // The runtime's opaque session state, emitted when the adapter learns it (e.g. the SDK
3123
+ // `system/init` frame). The platform stores `state` verbatim per (conversation, agent) and
3124
+ // hands it back next turn; only the adapter knows what it means.
3233
3125
  //
3234
- // CT315: `degraded: true` means the adapter started this FRESH session despite
3235
- // a stored one it was handed — a cwd/directory mismatch (the SJ527 guard) or
3236
- // legacy/foreign state it refused to resume. It is NOT set for a genuine
3237
- // first turn (`no_session`). The signal matters because the server built this
3238
- // turn's catch-up manifest assuming the prior session carried everything up to
3239
- // the high-water mark (CT298's two-channel invariant); a degrade breaks that,
3240
- // so the mark now over-reaches an empty session. The host relays `degraded`
3241
- // on settle and the server rewinds the mark, so the NEXT turn rebuilds a full
3242
- // catch-up (this failing turn is unavoidably lossy — the degrade is only known
3243
- // on the companion, after the server committed the manifest). Runtime-neutral: a
3244
- // plain boolean, not a runtime-specific reason string (that stays in the
3245
- // adapter's `onWarn` log). Additive + optional — an old receiver ignores it.
3126
+ // `degraded: true`: the adapter started this fresh session despite a stored one (a cwd
3127
+ // mismatch, or legacy/foreign state it refused to resume) — never for a genuine first turn.
3128
+ // The server built this turn's catch-up manifest assuming the prior session held everything up
3129
+ // to the high-water mark; a degrade breaks that. The host relays `degraded` on settle and the
3130
+ // server rewinds the mark, so the next turn rebuilds a full catch-up (this turn is unavoidably
3131
+ // lossy — the degrade is known only after the manifest was committed). A plain boolean; the
3132
+ // runtime-specific reason stays in the adapter's `onWarn` log. Optional.
3246
3133
  z6.object({
3247
3134
  type: z6.literal("session"),
3248
3135
  state: z6.string(),
3249
3136
  degraded: z6.boolean().optional()
3250
3137
  }),
3251
- // One readable thinking summary. Maps `onThinking({ text })`. Transient —
3252
- // surfaced live, never persisted as durable content.
3138
+ // One readable thinking summary. Transient — surfaced live, never persisted as content.
3253
3139
  z6.object({ type: z6.literal("thinking"), text: z6.string() }),
3254
- // Assistant text. Maps `onAssistantText({ text, final })` — `text`→`body`,
3255
- // `final`→`terminal`. `terminal: false` is interim narration (commits as a
3256
- // `progress` row); `terminal: true` is the turn's closing reply (commits as
3257
- // the `final` row).
3140
+ // Assistant text. `terminal: false` is interim narration (commits as `progress`);
3141
+ // `terminal: true` is the turn's closing reply (commits as `final`).
3258
3142
  z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
3259
- // Runtime/provider-authored prose about the turn — a failure's explanation, or
3260
- // a line the harness wrote on a turn it recovered (CT1750). Unlike
3261
- // `text`, this is not the agent's narration or reply: the pump persists it as
3262
- // `runtime_notice`, and the transcript renders it in the shared SystemNote
3263
- // voice. Adapters emit it only from positive runtime evidence; the web never
3264
- // classifies English strings.
3143
+ // Runtime/provider-authored prose about the turn — a failure's explanation, or a line the
3144
+ // harness wrote on a turn it recovered. Not the agent's words: the pump persists it as
3145
+ // `runtime_notice`, drawn in the SystemNote voice. Adapters emit it only from positive runtime
3146
+ // evidence; the web never classifies English strings.
3265
3147
  z6.object({ type: z6.literal("runtime_notice"), body: z6.string() }),
3266
- // A tool-activity transition. Maps `onToolActivity` — `toolUseId`→`id`,
3267
- // `toolName`→`name` (already prefix-stripped: `cabane_read`, not
3268
- // `mcp__cabane__cabane_read`), `summary` is the short card label. `phase`
3269
- // pairs `start` with a later `done`/`error` on the same `id`.
3148
+ // A tool-activity transition. `name` is prefix-stripped (`cabane_read`, not
3149
+ // `mcp__cabane__cabane_read`); `summary` is the short card label; `phase` pairs `start` with a
3150
+ // later `done`/`error` on the same `id`.
3270
3151
  //
3271
- // `input`/`result` are typed `unknown` (the raw tool payloads), widening
3272
- // today's already-truncated `input: string` / `result: string` — see
3273
- // docs/turn-protocol.md: rendering/truncation for the tool log is a
3274
- // pump/consumer concern applied to the value, not baked into the event type.
3275
- // CT496: `mcpServer` is the MCP server the call came from (`cabane`,
3276
- // `playwright`, …), derived off the raw `mcp__<server>__` name before `name`
3277
- // dropped the prefix; null for a host / built-in tool. The client tags Cabane
3278
- // MCP calls (`=== 'cabane'`) with a subtle glyph. Nullable + optional so a
3279
- // pre-CT496 producer that never sets it is unaffected (treated as null).
3152
+ // `input`/`result` are `unknown` (the raw tool payloads): rendering and truncation for the tool
3153
+ // log are a consumer concern (docs/turn-protocol.md). `mcpServer` is the MCP server the call
3154
+ // came from (`cabane`, `playwright`, …), derived before `name` dropped the prefix; null for a
3155
+ // host tool. Optional, read as null when absent.
3280
3156
  z6.object({
3281
3157
  type: z6.literal("tool"),
3282
3158
  id: z6.string(),
@@ -3287,45 +3163,25 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3287
3163
  result: z6.unknown().optional(),
3288
3164
  mcpServer: z6.string().nullable().optional()
3289
3165
  }),
3290
- // The turn's terminal outcome. Maps the `{ ok, reason }` both loops return
3291
- // inline. `ok:false` carries a machine reason (`no_session`, an error code);
3292
- // the host's clear-PATCH surfaces it as `errorReason`.
3293
- //
3294
- // CT277: an optional `usage` — the SDK result's token counts, which were
3295
- // dropped on the floor before. `inputTokens` is the full context the model saw
3296
- // (uncached + cache-read + cache-creation input), so it doubles as the
3297
- // context-window cost; `outputTokens` the generated tokens. Optional ⇒
3298
- // backward-compatible: an old companion / adapter that never sets it, and a
3299
- // receiver that never reads it, are unaffected (the turn's token columns stay
3300
- // null → the UI shows `—`).
3166
+ // The turn's terminal outcome. `ok:false` carries a machine reason (`no_session`, an error
3167
+ // code), surfaced by the host's clear-PATCH as `errorReason`.
3301
3168
  //
3302
- // CT597: two optional cache-token counts — the slice of `inputTokens` that was
3303
- // a cache read vs. a cache creation, when the runtime breaks it out (claude-code
3304
- // does). Additive + optional: a runtime that doesn't report them omits them, and
3305
- // the server just leaves the cache columns null. Captured now because honest
3306
- // costing later prices a cache-read token far below a fresh input token.
3169
+ // `usage`: the SDK result's token counts. `inputTokens` is the full context the model saw
3170
+ // (uncached + cache-read + cache-creation). The cache counts, when the runtime breaks them out,
3171
+ // are a subset of it, kept because a cache-read token costs far less. All optional; absent
3172
+ // leaves the columns null and the UI shows `—`.
3307
3173
  //
3308
- // CT699: `inputTokens` (and the cache slice) is a BILLING quantity — for
3309
- // claude-code/codex it's the runtime's CUMULATIVE total summed across every model
3310
- // request in the agentic turn, so it grows with the tool-call count and is NOT
3311
- // "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
3312
- // read and `contextWindow` the window it is measured against, both as the
3313
- // harness reported them (claude-code's `getContextUsage`, or its final
3314
- // request's input and `modelUsage` window when that call gives nothing). A
3315
- // runtime that can't source them (codex's cumulative-only usage) omits both,
3316
- // and no surface shows a reading for that turn.
3174
+ // `inputTokens` is a billing quantity — for claude-code/codex the cumulative total across every
3175
+ // model request in the turn, growing with tool calls, not "how full is the window".
3176
+ // `contextTokens` is the occupancy read and `contextWindow` its window, as the harness reported
3177
+ // them (claude-code's `getContextUsage`, else its final request's input and `modelUsage`
3178
+ // window). A runtime that can't source them (codex) omits both.
3317
3179
  //
3318
- // CT601: two more optional carry-homes on the terminal result, alongside
3319
- // `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
3320
- // claude-code learns it from the `system/init` frame mid-run (even for a
3321
- // subscription-default token, where the configured model is null), codex records
3322
- // the model the thread was started with. `resolvedConfig` is the resolved
3323
- // per-runtime knobs the harness reported (`{ reasoningEffort }` for codex; none
3324
- // for claude-code today). Both only known after the run streams — so they ride
3325
- // the terminal event home, the host relays them on settle, and the server writes
3326
- // `message_turn_metadata.resolved_model` / `resolved_config`. Optional ⇒
3327
- // backward-compatible: an old adapter/companion omits them, a cancel has no result
3328
- // event at all, and the columns stay null → the UI shows `—`.
3180
+ // `resolvedModel` is the concrete model the runtime ran (claude-code learns it from
3181
+ // `system/init` mid-run, even for a subscription default; codex records the model the thread
3182
+ // started with); `resolvedConfig` the knobs the harness reported (`{ reasoningEffort }` for
3183
+ // codex). Known only after the run streams, so they ride this event home and the host relays
3184
+ // them on settle. A cancel has no result event at all.
3329
3185
  z6.object({
3330
3186
  type: z6.literal("result"),
3331
3187
  ok: z6.boolean(),
@@ -3337,9 +3193,8 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3337
3193
  cacheCreationTokens: z6.number().optional(),
3338
3194
  contextTokens: z6.number().optional(),
3339
3195
  contextWindow: z6.number().optional(),
3340
- // CT1751: the rest of the harness's own context reading, when it gave
3341
- // one: where it will compact, its `/context` breakdown, and every
3342
- // compaction it reported during the turn. Claude Code only.
3196
+ // The rest of the harness's context reading, when it gave one: where it will compact, its
3197
+ // `/context` breakdown, and every compaction it reported. Claude Code only.
3343
3198
  contextCompactAt: z6.number().optional(),
3344
3199
  contextCategories: z6.array(
3345
3200
  z6.object({
@@ -3362,10 +3217,9 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3362
3217
  thinking: z6.string().optional(),
3363
3218
  reasoningEffort: z6.string().optional()
3364
3219
  }).optional(),
3365
- // CT1275: the harness-reported MCP inventory from this turn's init frame.
3366
- // This is deliberately diagnostic-only: names, statuses and a count, never
3367
- // server definitions, credentials or session ids. `initReceived:false`
3368
- // distinguishes a missing init frame from a real empty inventory.
3220
+ // The harness-reported MCP inventory from this turn's init frame. Diagnostic only: names,
3221
+ // statuses and a count — never server definitions, credentials or session ids.
3222
+ // `initReceived:false` tells a missing init frame from a real empty inventory.
3369
3223
  mcpInventory: z6.object({
3370
3224
  initReceived: z6.boolean(),
3371
3225
  servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
@@ -3468,9 +3322,8 @@ function isContentBearingEvent(event) {
3468
3322
  return normalizeCommitText(event.text) !== "";
3469
3323
  case "tool":
3470
3324
  return true;
3471
- // CT1750: a runtime notice is shown, but it is the harness speaking about
3472
- // the turn, not the turn's output — a success whose only output is a CLI
3473
- // line still produced nothing.
3325
+ // A runtime notice is shown, but it is the harness speaking about the turn, not its output — a
3326
+ // success whose only output is a CLI line still produced nothing.
3474
3327
  case "runtime_notice":
3475
3328
  case "session":
3476
3329
  case "result":
@@ -3542,11 +3395,10 @@ var turnRequestSchema = z8.object({
3542
3395
  mcpUrl: z8.string(),
3543
3396
  bearer: z8.string(),
3544
3397
  activeConversationId: z8.string(),
3545
- // Scoped turn-control MCP URL. Codex/opencode mount it as cabane_companion
3546
- // with the bearer and active-conversation header. It supplies ask,
3547
- // withdraw_ask, wake_me, cancel_wake and skip_turn. Claude Code mounts the
3548
- // companion's in-process instance instead. Optional for compatibility;
3549
- // the companion populates it in build-options.ts.
3398
+ // Scoped turn-control MCP URL. Codex and opencode mount it as `cabane_companion` with the bearer
3399
+ // and active-conversation header, for ask, withdraw_ask, wake_me, cancel_wake and skip_turn.
3400
+ // Claude Code mounts the companion's in-process instance instead. Optional; the companion
3401
+ // populates it.
3550
3402
  turnControlUrl: z8.string().optional(),
3551
3403
  // Workspace coordinate retained for envelope compatibility. Current
3552
3404
  // adapters use MCP, whose workspace operations take workspaceId explicitly.
@@ -3559,19 +3411,17 @@ var turnRequestSchema = z8.object({
3559
3411
  cwd: z8.string().optional(),
3560
3412
  env: z8.record(z8.string(), z8.string()).optional(),
3561
3413
  mcpServers: resolvedMcpServersSchema.optional(),
3562
- // CT1698: the values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` →
3563
- // complete header value). The adapter merges them into the harness
3564
- // process's environment and nowhere else; the host never merges them into
3565
- // its own. Absent when no server references one.
3414
+ // The values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` → complete header value). The
3415
+ // adapter merges them into the harness process's environment and nowhere else; the host never
3416
+ // merges them into its own. Absent when no server references one.
3566
3417
  secretEnv: z8.record(z8.string(), z8.string()).optional(),
3567
- // CT289: machine-local claude-code adapter knobs the operator sets on a
3568
- // companion they run themselves — the auto-memory escape hatch. `autoMemory:
3569
- // true` opts back into Claude Code's auto-memory (governed by the operator's
3570
- // own `.claude/settings.json`); absent/false leaves the adapter's force-off
3571
- // default in place (see `buildClaudeCodeOptions`).
3418
+ // Machine-local claude-code knobs for an operator running their own companion — the auto-memory
3419
+ // escape hatch. `autoMemory: true` opts back into Claude Code's auto-memory (governed by the
3420
+ // operator's `.claude/settings.json`); absent/false keeps the adapter's force-off default
3421
+ // (`buildClaudeCodeOptions`).
3572
3422
  claudeCode: z8.object({ autoMemory: z8.boolean().optional() }).optional()
3573
3423
  }),
3574
- // Host-owned injected servers (host-filled) — e.g. the send server.
3424
+ // Host-owned injected servers (host-filled).
3575
3425
  extra: z8.object({
3576
3426
  mcpServers: hostInjectedServersSchema
3577
3427
  })
@@ -3649,8 +3499,8 @@ async function emitToolResults(msg, emit, pending, onError) {
3649
3499
  // result payload off the block.
3650
3500
  input: started?.input,
3651
3501
  result: block.content,
3652
- // CT496: carry the server captured at `start` (the pretty name here has
3653
- // already lost its prefix). Null when the start wasn't seen.
3502
+ // The server captured at `start` (the pretty name here has lost its prefix). Null when the
3503
+ // start wasn't seen.
3654
3504
  mcpServer: started?.mcpServer ?? null
3655
3505
  },
3656
3506
  onError
@@ -3775,12 +3625,11 @@ function sinkEmitter(sink, _onError) {
3775
3625
  summary: event.summary,
3776
3626
  input: truncate(input, TOOL_IO_MAX_CHARS),
3777
3627
  result: result === null ? "" : truncate(result, TOOL_IO_MAX_CHARS),
3778
- // CT1752: only when the event carried an input — a completion whose
3779
- // start this process never saw has none, and a zero would overwrite
3780
- // the size its start recorded.
3628
+ // Only when the event carried an input: a completion whose start this process never saw has
3629
+ // none, and a zero would overwrite the size its start recorded.
3781
3630
  ...event.input === void 0 ? {} : { inputChars: input.length },
3782
3631
  ...result === null ? {} : { resultChars: result.length },
3783
- // CT496: carry the originating MCP server (null when absent / a host tool).
3632
+ // The originating MCP server (null when absent / a host tool).
3784
3633
  mcpServer: event.mcpServer ?? null,
3785
3634
  phase: event.phase
3786
3635
  });
@@ -3874,22 +3723,18 @@ var TurnPump = class {
3874
3723
  // The turn's final reply text, captured for the dashboard feed / logging.
3875
3724
  finalReplyBody = null;
3876
3725
  emittedFinalSource = "none";
3877
- // CT1383: provider tool id → the persisted identity of its CURRENT call. The
3878
- // provider's id is only a lookup key: it is unique within one adapter
3879
- // invocation at best (Codex restarts at `item_0` on every invocation, and a
3880
- // resumed turn keeps its turn id), so the row and the outbox entry are keyed
3881
- // on what the pump mints — a host id for the host's `(turn_id, tool_use_id)`
3882
- // upsert, and a seq for the outbox's `(turnId, seq)` file. Both are minted
3883
- // together; minting only the id would let two calls overwrite each other's
3884
- // queued body.
3726
+ // Provider tool id → the persisted identity of its current call. The provider's id is only a
3727
+ // lookup key, unique within one adapter invocation at best (Codex restarts at `item_0` every
3728
+ // invocation, and a resumed turn keeps its turn id), so the row and the outbox entry are keyed on
3729
+ // what the pump mints: a host id for the host's `(turn_id, tool_use_id)` upsert, and a seq for the
3730
+ // outbox's `(turnId, seq)` file. Minted together; minting only the id would let two calls
3731
+ // overwrite each other's queued body.
3885
3732
  //
3886
- // In memory on purpose: it dies with the process, and so does the runtime.
3887
- // After a crash no in-flight `done` awaits a pre-crash `start` — the resumed
3888
- // invocation starts its calls afresh — so there is nothing to recover.
3733
+ // In memory on purpose: it dies with the process, and so does the runtime. After a crash no
3734
+ // in-flight `done` awaits a pre-crash `start` — the resumed invocation starts its calls afresh.
3889
3735
  toolCallByProviderId = /* @__PURE__ */ new Map();
3890
- // The callback consumer the classification (via `sinkEmitter`) — and the
3891
- // frozen committer test — drive. Each callback applies the choreography and
3892
- // commits through the injected sink.
3736
+ // The callback consumer the classification (via `sinkEmitter`) and the committer test drive.
3737
+ // Each callback applies the choreography and commits through the injected sink.
3893
3738
  sink;
3894
3739
  // Whether a message has landed under this span — first words, at least. A
3895
3740
  // span that spoke settles; one that never did is a pass.
@@ -3981,7 +3826,7 @@ var TURN_CONTROL_FIELD_DESCRIPTIONS = {
3981
3826
  wake_me: {
3982
3827
  afterSeconds: "Relative delay in seconds from NOW \u2014 this call (e.g. 300 = five minutes). Provide EITHER this or `at`, not both. Floor 60s, horizon 14 days \u2014 enforced server-side.",
3983
3828
  at: "Absolute ISO-8601 timestamp WITH a zone (`Z` or `\xB1HH:MM`), e.g. `2026-07-16T09:00:00-07:00`. YOU compute it from a natural-language phrase using the current datetime in your turn context. Provide EITHER this or `afterSeconds`, not both.",
3984
- note: `A note to your future self \u2014 becomes the body of the wake message that re-dispatches you, up to ${MAX_WAKE_NOTE_BYTES} characters. Write the condition to re-check ("check whether the PR merged").`
3829
+ note: `A note to your future self \u2014 becomes the body of the wake message that re-dispatches you, up to ${MAX_WAKE_NOTE_BYTES} characters. Write the condition to re-check in a sentence or two ("check whether the PR merged"), not a plan. You never need to name yourself: the wake reaches you on its own.`
3985
3830
  },
3986
3831
  withdraw_ask: {
3987
3832
  askId: "The open ask to withdraw. Ask ids ride the conversation's message rows (`ask: { id, status, items }`), and your turn context lists the ones you have open.",
@@ -3994,7 +3839,7 @@ var MEANINGS = {
3994
3839
  ask: "put a structured question to a person",
3995
3840
  cancel_wake: "cancel your own armed wake in this conversation",
3996
3841
  skip_turn: "end without posting a reply",
3997
- wake_me: "schedule your own return",
3842
+ wake_me: "schedule your own return; when it fires, say first what you came back to check",
3998
3843
  withdraw_ask: "withdraw an open ask in this conversation with a reason"
3999
3844
  };
4000
3845
  function turnControlPromptGuidance(prefix) {
@@ -4215,52 +4060,41 @@ function buildClaudeCodeOptions(req, augment) {
4215
4060
  // pinned `model` arrives as a bare SDK id — the host already resolved the
4216
4061
  // namespaced catalog id to it.
4217
4062
  ...model ? { model } : {},
4218
- // SJ530: surface thinking summaries — default adaptive+summarized, and fill in
4219
- // `display:'summarized'` on a configured adaptive/enabled block that omits one.
4220
- // Read from the adapter's own dialect slice (`runtimeOptions['claude-code']`).
4063
+ // Surface thinking summaries: default adaptive+summarized, and fill in `display:'summarized'`
4064
+ // on a configured adaptive/enabled block that omits one. Read from the adapter's dialect slice
4065
+ // (`runtimeOptions['claude-code']`).
4221
4066
  thinking: withThinkingSummaries(readThinking(config.runtimeOptions)),
4222
4067
  ...config.effort ? { effort: config.effort } : {},
4223
4068
  // Never merge arbitrary user `~/.claude` config; without `strictMcpConfig` the
4224
4069
  // user's existing `cabane` MCP registration could shadow ours.
4225
4070
  settingSources: [],
4226
4071
  strictMcpConfig: true,
4227
- // CT289: force auto-memory off unless the operator opted back in (see above).
4072
+ // Auto-memory off unless the operator opted back in (see above).
4228
4073
  ...devControlsAutoMemory ? {} : { settings: { autoMemoryEnabled: false } },
4229
4074
  mcpServers,
4230
4075
  ...cwd ? { cwd } : {},
4231
- // Extra env (a companion prepare hook's tokens/ports; the in-app's debug flags)
4232
- // merged OVER the inherited environment.
4076
+ // Extra env (a companion prepare hook's tokens/ports) merged over the inherited environment.
4233
4077
  //
4234
- // CT1503: and the background-tasks kill switch LAST, so nothing in
4235
- // `process.env` or `req.local.env` can turn it back on. Claude Code converts
4236
- // a foreground command that hits its Bash ceiling (600 000 ms) into a
4237
- // background task instead of erroring; the agent then reads a phantom task
4238
- // id inside a live turn. A later turn that resumed such a session was then
4239
- // seen getting every tool call aborted with kind `background` — which the CLI
4240
- // renders as "The user doesn't want to take this action right now", a
4241
- // cancellation wearing a refusal's words. The automatic conversion is
4242
- // REPRODUCED and is what this flag fixes; that the stopped task is what
4243
- // poisoned the resumed session is an INFERENCE from one incident, consistent
4244
- // with the transcript but never reproduced, and this change does not rest on
4245
- // it. `Dl()` reads this variable on the Bash tool's own call
4246
- // path and sets `canAutoBackground: false` and `turnAbortBackgrounds: false`
4247
- // together, so a timed-out command comes back as a readable error instead.
4078
+ // The background-tasks kill switch goes last, so nothing in `process.env` or `req.local.env`
4079
+ // turns it back on. Claude Code converts a foreground command that hits its Bash ceiling
4080
+ // (600 000 ms) into a background task instead of erroring, and the agent then holds a phantom
4081
+ // task id inside a live turn; a resumed session was seen with every tool call aborted as
4082
+ // `background` ("The user doesn't want to take this action right now"). The conversion is
4083
+ // reproduced and fixed by this flag; that it poisoned the resumed session is an inference
4084
+ // from one incident, and this change doesn't rest on it. `Dl()` reads the variable on the
4085
+ // Bash tool's call path and sets `canAutoBackground: false` and `turnAbortBackgrounds: false`,
4086
+ // so a timed-out command returns a readable error.
4248
4087
  //
4249
- // The cost is acknowledged and deliberate: it ALSO removes deliberate
4250
- // `run_in_background` (the parameter leaves the tool schema). That is the
4251
- // only switch this harness offers, and there is no operator opt-out here on
4252
- // purpose — an escape hatch would contradict the guarantee. It is a targeted
4253
- // mitigation for the automatic conversion, NOT a cleanup mechanism: step 0
4254
- // measured that this flag changes no process-survival outcome on any turn-end
4255
- // path. Cleanup is its own change.
4088
+ // The cost is deliberate: it also removes `run_in_background` (the parameter leaves the tool
4089
+ // schema). It is the only switch, and there is no opt-out — an escape hatch would contradict
4090
+ // the guarantee. It changes no process-survival outcome on any turn-end path (measured);
4091
+ // cleanup is a separate concern.
4256
4092
  //
4257
- // Note `env` REPLACES the subprocess environment rather than merging (the
4258
- // SDK's own doc on `Options.env`), which is why `process.env` is spread here
4259
- // and why this key is now always present — previously it appeared only when
4260
- // `req.local.env` was set, and an absent `env` inherits `process.env`.
4093
+ // `env` replaces the subprocess environment rather than merging (`Options.env`), which is why
4094
+ // `process.env` is spread here and the key is always present.
4261
4095
  //
4262
- // CT1698: `secretEnv` joins here, and only here — the values the MCP
4263
- // headers above reference. It is this child's environment, not the host's.
4096
+ // `secretEnv` — the values the MCP headers above reference — joins here and only here: it is
4097
+ // this child's environment, not the host's.
4264
4098
  env: {
4265
4099
  ...process.env,
4266
4100
  ...req.local.env,
@@ -4751,8 +4585,7 @@ function makeRequest(overrides2 = {}) {
4751
4585
  systemPrompt: "system",
4752
4586
  prompt: "hi there",
4753
4587
  content: [{ type: "text", text: "hi there" }],
4754
- // CT302: model is required — the adapter rejects a null model, so the
4755
- // conformance fixture carries a real bare SDK id.
4588
+ // The adapter rejects a null model, so the fixture carries a real bare SDK id.
4756
4589
  config: { model: "claude-opus-4-8" },
4757
4590
  policy: CABANE_POLICY,
4758
4591
  session: null,
@@ -4865,7 +4698,7 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4865
4698
  phase: "start",
4866
4699
  summary: "sleep.md",
4867
4700
  input: { path: "sleep.md" },
4868
- // CT496: derived off the raw `mcp__cabane__` name.
4701
+ // Derived from the raw `mcp__cabane__` name.
4869
4702
  mcpServer: "cabane"
4870
4703
  },
4871
4704
  {
@@ -4883,9 +4716,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4883
4716
  ]
4884
4717
  },
4885
4718
  {
4886
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result;
4887
- // the adapter stops before the closing reply — no final text, no `result`
4888
- // event. CT1295: the server writes the `kind:'stopped'` marker.
4719
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops
4720
+ // before the closing reply — no final text, no `result` event (the server writes the stopped
4721
+ // marker).
4889
4722
  name: "cancel mid-stream",
4890
4723
  request: makeRequest(),
4891
4724
  nativeStream: [
@@ -4937,12 +4770,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4937
4770
  ]
4938
4771
  },
4939
4772
  {
4940
- // CT1144: the empty-runtime anomaly's exact captured trace — `system/init`
4941
- // then a successful `result`, with nothing in between. Distinct from
4942
- // `empty-final` below: THAT turn did work (a tool ran) and just didn't speak;
4943
- // this one produced nothing at all. The adapter's job is unchanged either way
4944
- // — report faithfully what the runtime said — so it must still emit
4945
- // `result{ok:true}` here; the host is what decides that a success with no
4773
+ // The empty-runtime anomaly's captured trace: `system/init` then a successful `result`, nothing
4774
+ // in between. Distinct from `empty-final` (a tool ran, no speech); this produced nothing. The
4775
+ // adapter reports faithfully — `result{ok:true}` — and the host decides that a success with no
4946
4776
  // content-bearing event is a failed turn.
4947
4777
  name: "zero-content success (empty runtime result)",
4948
4778
  request: makeRequest(),
@@ -4950,9 +4780,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4950
4780
  expected: [sessionEvent("s1"), { type: "result", ok: true }]
4951
4781
  },
4952
4782
  {
4953
- // CT1220: on resume, a completed background task can emit a notification and
4954
- // its own zero-token result before the prompt's frames. That result is not the
4955
- // turn boundary; decoding continues through the prompt's reply and real usage.
4783
+ // On resume, a completed background task can emit a notification and its own zero-token result
4784
+ // before the prompt's frames. That result is not the turn boundary; decoding continues through
4785
+ // the reply and real usage.
4956
4786
  name: "task-notification result does not end a resumed turn",
4957
4787
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
4958
4788
  nativeStream: [
@@ -5028,8 +4858,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5028
4858
  ]
5029
4859
  },
5030
4860
  {
5031
- // CT1220: the filter is deliberately narrow. An origin-less zero-token result
5032
- // keeps today's break-on-first-result behavior, even if more frames follow.
4861
+ // The filter is deliberately narrow: an origin-less zero-token result still ends the turn, even
4862
+ // if more frames follow.
5033
4863
  name: "origin-less result still ends a resumed turn",
5034
4864
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5035
4865
  nativeStream: [
@@ -5100,11 +4930,10 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5100
4930
  ]
5101
4931
  },
5102
4932
  {
5103
- // CT558/CT592/CT731: a subscription cap. The SDK emits a rejected
5104
- // `rate_limit_event` with a named subscription window; the terminal error result
5105
- // then classifies as the structured `usage_capped` reason. The held provider
5106
- // line independently classifies to the same kind, so it lands as a runtime
5107
- // notice rather than agent-authored progress.
4933
+ // A subscription cap. The SDK emits a rejected `rate_limit_event` with a named subscription
4934
+ // window; the terminal error result classifies as `usage_capped`. The held provider line
4935
+ // independently classifies to the same kind, so it lands as a runtime notice, not agent
4936
+ // progress.
5108
4937
  name: "subscription cap \u2192 usage_capped",
5109
4938
  request: makeRequest(),
5110
4939
  nativeStream: [
@@ -5123,9 +4952,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5123
4952
  ]
5124
4953
  },
5125
4954
  {
5126
- // CT1336: a failed turn can still contain the agent's own partial narration.
5127
- // No text classification means no authorship rewrite, even though the
5128
- // structured result is the same usage cap.
4955
+ // A failed turn can still contain the agent's own partial narration. No text classification
4956
+ // means no authorship rewrite, even though the structured result is the same usage cap.
5129
4957
  name: "partial narration before subscription cap stays progress",
5130
4958
  request: makeRequest(),
5131
4959
  nativeStream: [
@@ -5141,8 +4969,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5141
4969
  ]
5142
4970
  },
5143
4971
  {
5144
- // CT1336: even recognized runtime prose stays authored when its kind
5145
- // disagrees with the structured terminal failure.
4972
+ // Recognized runtime prose stays authored when its kind disagrees with the structured terminal
4973
+ // failure.
5146
4974
  name: "cap notice before server failure stays progress",
5147
4975
  request: makeRequest(),
5148
4976
  nativeStream: [
@@ -5164,10 +4992,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5164
4992
  ]
5165
4993
  },
5166
4994
  {
5167
- // CT1336 review: `classifyErrorText` intentionally searches arbitrary error
5168
- // blobs for status tokens. Assistant narration needs the stricter whole-
5169
- // notice classifier, or an incidental count is de-authored when the turn
5170
- // later fails with the matching provider kind.
4995
+ // `classifyErrorText` deliberately searches arbitrary error blobs for status tokens. Assistant
4996
+ // narration needs the stricter whole-notice classifier, or an incidental count would be
4997
+ // de-authored when the turn later fails with the matching provider kind.
5171
4998
  name: "incidental 5xx-shaped narration before server failure stays progress",
5172
4999
  request: makeRequest(),
5173
5000
  nativeStream: [
@@ -5189,9 +5016,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5189
5016
  ]
5190
5017
  },
5191
5018
  {
5192
- // CT558/CT592: a cap signalled only by the result's
5193
- // `terminal_reason:'blocking_limit'` (no discrete rate-limit event) still
5194
- // classifies as the cap (`usage_capped`).
5019
+ // A cap signalled only by the result's `terminal_reason:'blocking_limit'` (no rate-limit event)
5020
+ // still classifies as `usage_capped`.
5195
5021
  name: "blocking_limit terminal reason \u2192 usage_capped",
5196
5022
  request: makeRequest(),
5197
5023
  nativeStream: [
@@ -5205,8 +5031,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5205
5031
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "usage_capped" }]
5206
5032
  },
5207
5033
  {
5208
- // CT558: an expired login surfaces as an `auth_status` error frame; the failing
5209
- // result classifies as `auth_expired`.
5034
+ // An expired login surfaces as an `auth_status` error frame; the failing result classifies as
5035
+ // `auth_expired`.
5210
5036
  name: "expired login \u2192 auth_expired",
5211
5037
  request: makeRequest(),
5212
5038
  nativeStream: [
@@ -5220,10 +5046,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5220
5046
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "auth_expired" }]
5221
5047
  },
5222
5048
  {
5223
- // CT592: a transient provider outage surfaced in the terminal result text (no
5224
- // structured event/enum) — a 529 overload — classifies as `server_error` via
5225
- // the text fallback. It must NOT read as the cap and must not stamp the
5226
- // connector `limited` (the whole point of the honest taxonomy).
5049
+ // A transient provider outage in the terminal result text (no structured event or enum) — a 529
5050
+ // overload — classifies as `server_error` via the text fallback. It must not read as the cap or
5051
+ // stamp the connector `limited`.
5227
5052
  name: "provider overload text \u2192 server_error",
5228
5053
  request: makeRequest(),
5229
5054
  nativeStream: [
@@ -5236,11 +5061,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5236
5061
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "server_error" }]
5237
5062
  },
5238
5063
  {
5239
- // CT601: the resolved model is captured off `system/init.model` and attached
5240
- // to the terminal `result` — even on a plain resume (this fixture resumes, so
5241
- // no `session` event fires, proving the model doesn't ride the session event).
5242
- // The configured request model is irrelevant here; what's asserted is that the
5243
- // concrete id the HARNESS reported comes home.
5064
+ // The resolved model is captured off `system/init.model` and attached to the terminal `result`
5065
+ // even on a plain resume (this fixture resumes, so no `session` event fires). What's asserted is
5066
+ // that the concrete id the harness reported comes home.
5244
5067
  name: "resolved model captured from system/init",
5245
5068
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5246
5069
  nativeStream: [init("s1", "claude-opus-4-8"), assistantText("Done."), resultSuccess("s1")],
@@ -5250,9 +5073,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5250
5073
  ]
5251
5074
  },
5252
5075
  {
5253
- // Session resume echo: the stored state matches the current cwd, so we resume
5254
- // its SDK id; `system/init` echoes that same id, so NO session event fires
5255
- // (the id didn't change).
5076
+ // Session resume echo: the stored state matches the cwd, so its SDK id is resumed and
5077
+ // `system/init` echoes the same id — no session event fires.
5256
5078
  name: "session resume (no re-emit on echo)",
5257
5079
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5258
5080
  nativeStream: [init("s1"), assistantText("Back again."), resultSuccess("s1")],
@@ -5262,16 +5084,14 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5262
5084
  ]
5263
5085
  },
5264
5086
  {
5265
- // SJ527 cwd-mismatch degrade: the stored state was created under a DIFFERENT
5266
- // cwd, so the adapter refuses to resume and starts fresh — `system/init`
5267
- // reports a new id, and the emitted session event records the CURRENT cwd.
5087
+ // Cwd-mismatch degrade: the stored state was created under another cwd, so the adapter starts
5088
+ // fresh — `system/init` reports a new id, and the session event records the current cwd.
5268
5089
  name: "session cwd-mismatch degrades to fresh",
5269
5090
  request: makeRequest({ session: encodeSession({ sdkSessionId: "old", cwd: "/env/gone" }) }),
5270
5091
  nativeStream: [init("fresh1"), assistantText("Fresh start."), resultSuccess("fresh1")],
5271
5092
  expected: [
5272
- // CT315: the degrade is flagged on the session event so the host relays it
5273
- // on settle and the server rewinds the catch-up mark (recovering the
5274
- // pre-mark context the discarded session no longer carries).
5093
+ // The degrade is flagged on the session event so the host relays it on settle and the server
5094
+ // rewinds the catch-up mark (recovering context the discarded session no longer carries).
5275
5095
  sessionEvent("fresh1", CWD, true),
5276
5096
  { type: "text", body: "Fresh start.", terminal: true },
5277
5097
  { type: "result", ok: true }
@@ -5914,8 +5734,6 @@ function createOpencodeAdapter(deps = {}) {
5914
5734
  const transport = deps.transport ?? (deps.serverUrl ? createHttpOpencodeTransport({ baseUrl: deps.serverUrl }) : null);
5915
5735
  return {
5916
5736
  name: "opencode",
5917
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
5918
- // argument went with the named tool surface it selected against.
5919
5737
  promptAddendum: () => OPENCODE_ADDENDUM,
5920
5738
  dialectSchema: opencodeDialectSchema,
5921
5739
  async *runTurn(request, signal, hooks) {
@@ -6078,15 +5896,12 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6078
5896
  ]
6079
5897
  },
6080
5898
  {
6081
- // CT301: a STREAMING provider's tool input arrives late. opencode re-emits the
6082
- // tool part as a full snapshot as the args parse: DeepSeek sends `input: {}` on
6083
- // the early `pending`/`running` frame and the full args only on a later snapshot
6084
- // (live-captured against opencode 1.17: `pending {}` → `running {full}` →
6085
- // `completed {full}`). The `start` event fires immediately off the first (empty)
6086
- // frame — that's the live card — but the `done` event (and the DB row it
6087
- // upserts) must carry the LATEST non-empty input + its summary, not the
6088
- // first-seen `{}`. This is the case the pre-CT301 suite structurally couldn't
6089
- // catch: every other tool fixture bakes the full input into the first snapshot.
5899
+ // A streaming provider's tool input arrives late. opencode re-emits the tool part as a full
5900
+ // snapshot as the args parse: DeepSeek sends `input: {}` on the early `pending`/`running` frame
5901
+ // and the full args on a later one (captured on opencode 1.17: `pending {}` → `running {full}` →
5902
+ // `completed {full}`). `start` fires off the first (empty) frame — the live card — but `done`
5903
+ // (and the row it upserts) must carry the latest non-empty input and its summary. Every other
5904
+ // tool fixture bakes the full input into the first snapshot, so only this one catches it.
6090
5905
  name: "streaming tool input \u2014 late args refresh the done event (CT301)",
6091
5906
  request: makeRequest2(),
6092
5907
  nativeStream: [
@@ -6124,9 +5939,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6124
5939
  ]
6125
5940
  },
6126
5941
  {
6127
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
6128
- // adapter stops before the closing reply — no final text, no `result` event.
6129
- // CT1295: the server writes the `kind:'stopped'` marker.
5942
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops before
5943
+ // the closing reply — no final text, no `result` event (the server writes the stopped marker).
6130
5944
  name: "cancel mid-stream",
6131
5945
  request: makeRequest2(),
6132
5946
  nativeStream: [
@@ -6166,10 +5980,9 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6166
5980
  ]
6167
5981
  },
6168
5982
  {
6169
- // CT558/CT592: a provider 429 throttle. The wrapped `RateLimitError` is
6170
- // unwrapped by `readSessionError` then text-classified — a bare "rate limit
6171
- // (429)" with no cap noun is the throttle (`rate_limited`), distinct from the
6172
- // account cap. opencode is text-only so it inherits the CT592 split for free.
5983
+ // A provider 429 throttle. The wrapped `RateLimitError` is unwrapped by `readSessionError` and
5984
+ // text-classified: a bare "rate limit (429)" with no cap noun is the throttle (`rate_limited`),
5985
+ // not the account cap.
6173
5986
  name: "provider 429 \u2192 rate_limited (throttle, no reset)",
6174
5987
  request: makeRequest2(),
6175
5988
  nativeStream: [
@@ -6185,9 +5998,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6185
5998
  ]
6186
5999
  },
6187
6000
  {
6188
- // CT592: a subscription cap surfaced as text ("usage limit reached") → the cap
6189
- // (`usage_capped`). opencode carries no reset hint, so it's reset-less (the
6190
- // server renders "try again later").
6001
+ // A subscription cap surfaced as text ("usage limit reached") → `usage_capped`. opencode carries
6002
+ // no reset hint, so it's reset-less (the server says "try again later").
6191
6003
  name: "usage cap text \u2192 usage_capped (no reset)",
6192
6004
  request: makeRequest2(),
6193
6005
  nativeStream: [
@@ -6203,9 +6015,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6203
6015
  ]
6204
6016
  },
6205
6017
  {
6206
- // CT592: a transient provider outage (5xx / overload) surfaced as text → the
6207
- // honest `server_error`, never the cap. This is the outage class that used to
6208
- // fall through to an opaque `unknown`.
6018
+ // A transient provider outage (5xx / overload) surfaced as text → `server_error`, never the cap.
6209
6019
  name: "provider overload text \u2192 server_error",
6210
6020
  request: makeRequest2(),
6211
6021
  nativeStream: [
@@ -6223,7 +6033,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6223
6033
  ]
6224
6034
  },
6225
6035
  {
6226
- // CT558: an expired/invalid credential classifies as `auth_expired`.
6036
+ // An expired or invalid credential classifies as `auth_expired`.
6227
6037
  name: "auth failure \u2192 auth_expired",
6228
6038
  request: makeRequest2(),
6229
6039
  nativeStream: [
@@ -6239,8 +6049,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6239
6049
  ]
6240
6050
  },
6241
6051
  {
6242
- // CT1351: an unclassified failure remains as informative as a classified one:
6243
- // its provider text is visible while the raw machine reason stays unchanged.
6052
+ // An unclassified failure stays informative: its provider text is visible, and the raw machine
6053
+ // reason is unchanged.
6244
6054
  name: "unclassified failure keeps a runtime notice and raw reason",
6245
6055
  request: makeRequest2(),
6246
6056
  nativeStream: [errored("something local broke")],
@@ -6261,8 +6071,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6261
6071
  ]
6262
6072
  },
6263
6073
  {
6264
- // CT1351: empty, whitespace-only, and bare status-token failures still set
6265
- // the machine reason but do not add an empty/unhelpful transcript notice.
6074
+ // Empty, whitespace-only and bare status-token failures still set the machine reason but add no
6075
+ // empty or unhelpful transcript notice.
6266
6076
  name: "empty failure text emits no runtime notice",
6267
6077
  request: makeRequest2(),
6268
6078
  nativeStream: [errored("")],
@@ -6294,12 +6104,10 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6294
6104
  ]
6295
6105
  },
6296
6106
  {
6297
- // CT1144: the same zero-content success the claude-code suite pins, in
6298
- // opencode's vocabulary — the session goes idle having emitted no part at
6299
- // all. No opencode sample of the anomaly has been seen in the wild; the
6300
- // fixture exists because the host's classification is runtime-neutral, so a
6301
- // runtime that CAN produce this envelope must be shown producing it in the
6302
- // shape the classifier reads.
6107
+ // The zero-content success the claude-code suite pins, in opencode's vocabulary — the session
6108
+ // goes idle having emitted no part. No opencode sample has been seen; the fixture exists because
6109
+ // the host's classification is runtime-neutral, so a runtime that can produce this envelope must
6110
+ // be shown producing it in the shape the classifier reads.
6303
6111
  name: "zero-content success (empty runtime result)",
6304
6112
  request: makeRequest2(),
6305
6113
  nativeStream: [idle()],
@@ -6356,10 +6164,9 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6356
6164
  ]
6357
6165
  },
6358
6166
  {
6359
- // CT298: the stored state matches the current directory, so the adapter resumes
6360
- // its id and the transport reports `created:false` on the same id — NO session
6361
- // event fires (the id didn't change). This is the resume-echo case CT294
6362
- // removed; restoring it is the crux of restoring cross-turn memory.
6167
+ // The stored state matches the directory, so the adapter resumes its id and the transport reports
6168
+ // `created:false` on the same id — no session event fires. Resume is what carries cross-turn
6169
+ // memory.
6363
6170
  name: "session resume (no re-emit on echo)",
6364
6171
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
6365
6172
  nativeStream: [textPart("p1", "Back again."), idle()],
@@ -6369,12 +6176,10 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6369
6176
  ]
6370
6177
  },
6371
6178
  {
6372
- // CT298: on a resumed turn the transport mints NO client message id (supplying
6373
- // one no-ops the turn), so the decoder can't be handed a `skipMessageId` up
6374
- // front. It learns the user-message id from the stream — the `message.updated`
6375
- // user frame precedes that message's echoed part — and drops the echo, so the
6376
- // injected per-turn context still can't leak into Steps. Only the assistant
6377
- // reply surfaces. No session event (resume echo on the same id).
6179
+ // On a resumed turn the transport mints no client message id (doing so no-ops the turn), so the
6180
+ // decoder gets no `skipMessageId`. It learns the user-message id from the stream — the
6181
+ // `message.updated` user frame precedes the echoed part — and drops the echo, so the injected
6182
+ // per-turn context can't leak into Steps. Only the reply surfaces; no session event.
6378
6183
  name: "session resume \u2014 prompt echo filtered via stream-derived user id",
6379
6184
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
6380
6185
  nativeStream: [
@@ -6390,17 +6195,15 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6390
6195
  ]
6391
6196
  },
6392
6197
  {
6393
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was
6394
- // created under a DIFFERENT directory, so the adapter refuses to resume and
6395
- // starts fresh — the transport creates a new session, and the emitted session
6396
- // event records the CURRENT directory.
6198
+ // Directory-mismatch degrade: the stored state was created under another directory, so the
6199
+ // adapter starts fresh — the transport creates a new session, and the session event records the
6200
+ // current directory.
6397
6201
  name: "session directory-mismatch degrades to fresh",
6398
6202
  request: makeRequest2({ session: encodeSession2({ sessionId: "old", directory: "/env/gone" }) }),
6399
6203
  nativeStream: [textPart("p1", "Fresh start."), idle()],
6400
6204
  expected: [
6401
- // CT315: the degrade is flagged on the session event (parity with
6402
- // claude-code) so the host relays it on settle and the server rewinds the
6403
- // catch-up mark, recovering the context the discarded session dropped.
6205
+ // The degrade is flagged on the session event so the host relays it on settle and the server
6206
+ // rewinds the catch-up mark, recovering context the discarded session dropped.
6404
6207
  sessionEvent2(NEW_SESSION_ID, DIR, true),
6405
6208
  { type: "text", body: "Fresh start.", terminal: true },
6406
6209
  { type: "result", ok: true }
@@ -6418,11 +6221,9 @@ var CODEX_ADDENDUM = [
6418
6221
  "absent without attempting discovery and invocation, and if a call fails, report the recorded",
6419
6222
  "error.",
6420
6223
  turnControlPromptGuidance("mcp__cabane_companion__"),
6421
- // CT1552: every ancillary tool is written the way Codex's own tool list shows
6422
- // it — qualified. The bare `read_binary` / `upload` / … this list used to mix
6423
- // in sat two lines under the instruction to invoke the exact qualified names,
6424
- // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
6425
- // that sentence is about operation names, after the qualified forms are given.
6224
+ // Every ancillary tool is written as Codex's own tool list shows it — qualified — so the sentence
6225
+ // instructing exact qualified names doesn't contradict itself. The bare `query` / `mutate` below
6226
+ // stay: that sentence is about operation names, after the qualified forms are given.
6426
6227
  "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
6427
6228
  "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
6428
6229
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
@@ -6666,10 +6467,9 @@ async function* decodeCodexStream(events, ctx) {
6666
6467
  threadId,
6667
6468
  cwd: ctx.cwd ?? "",
6668
6469
  promptFingerprint: ctx.promptFingerprint ?? null,
6669
- // CT1645: the baseline this turn INHERITED, carried through unchanged.
6670
- // This early write exists so a turn that dies mid-stream still leaves
6671
- // the thread id recorded; it cannot yet know what this turn will spend.
6672
- // The closing write below records the new baseline.
6470
+ // The baseline this turn inherited, carried through unchanged. This early write records the
6471
+ // thread id even if the turn dies mid-stream; it can't yet know what the turn will spend. The
6472
+ // closing write records the new baseline.
6673
6473
  usageBaseline: ctx.usageBaseline ?? null
6674
6474
  });
6675
6475
  if (threadId !== ctx.resumedThreadId || state !== ctx.storedState) {
@@ -6953,9 +6753,8 @@ function buildConfig(req, baseInstructionsFile) {
6953
6753
  return {
6954
6754
  mcp_servers,
6955
6755
  experimental_use_rmcp_client: true,
6956
- // CT871: the no-host-access prompt replacement — Cabane's composed prompt
6957
- // becomes Codex's base instructions, and Codex's own environment preamble goes
6958
- // with the harness prompt it belonged to.
6756
+ // The no-host-access prompt replacement — Cabane's composed prompt becomes Codex's base
6757
+ // instructions, and Codex's environment preamble goes with the harness prompt it belonged to.
6959
6758
  ...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
6960
6759
  ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
6961
6760
  };
@@ -7052,16 +6851,11 @@ function createSdkCodexTransport(opts = {}) {
7052
6851
  const codexOptions = {
7053
6852
  ...opts.apiKey ? { apiKey: opts.apiKey } : {},
7054
6853
  ...opts.codexPathOverride ? { codexPathOverride: opts.codexPathOverride } : {},
7055
- // The `--config` overrides (MCP servers + rmcp flag). CT1501: rendered to
7056
- // finished `--config` strings by US and passed through `configOverrides`,
7057
- // the SDK's raw passthrough — NOT through `config`, which would flatten
7058
- // them itself. A user MCP server named `my.server` cannot go in an
7059
- // override's dotted path at all (the CLI splits the path on `.` and
7060
- // ignores TOML quoting), so it has to ride inside an inline-table value,
7061
- // and the SDK's flattener cannot be steered into producing one. This is
7062
- // also what makes the PUBLISHED companion correct, where the vendored
7063
- // patch never reached — and why that patch is gone. See
7064
- // `config-overrides.ts`.
6854
+ // The `--config` overrides (MCP servers + rmcp flag), rendered to finished `--config` strings
6855
+ // here and passed through `configOverrides`, the SDK's raw passthrough — not `config`, which
6856
+ // flattens them itself. A user MCP server named `my.server` can't go in an override's dotted
6857
+ // path (the CLI splits on `.` and ignores TOML quoting), so it must ride inside an inline-table
6858
+ // value, which the SDK's flattener can't produce. See `config-overrides.ts`.
7065
6859
  configOverrides: buildConfigOverrides(spec.config),
7066
6860
  ...spec.secretEnv ? { env: codexProcessEnv(spec.secretEnv) } : {}
7067
6861
  };
@@ -7079,8 +6873,6 @@ function createCodexAdapter(deps = {}) {
7079
6873
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
7080
6874
  return {
7081
6875
  name: "codex",
7082
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
7083
- // argument went with the named tool surface it selected against.
7084
6876
  promptAddendum: () => CODEX_ADDENDUM,
7085
6877
  dialectSchema: codexDialectSchema,
7086
6878
  async *runTurn(request, signal, hooks) {
@@ -7172,17 +6964,14 @@ function createCodexAdapter(deps = {}) {
7172
6964
  cwd: req.local.cwd,
7173
6965
  resumedThreadId: resumeThreadId,
7174
6966
  degraded,
7175
- // CT1075: which prompt the thread carries once this turn lands. If we sent
7176
- // one, it's that one — a later copy is what the model reads, so a changed
7177
- // prompt supersedes the older copy still sitting above it. If we withheld
7178
- // it, the thread still carries whatever it did before. Read off the spec
7179
- // rather than recomputed, so the recorded fact can't disagree with what
7180
- // was actually sent.
6967
+ // Which prompt the thread carries once this turn lands. If we sent one, it's that one — the
6968
+ // later copy is what the model reads. If we withheld it, the thread carries what it did. Read
6969
+ // off the spec, so the record can't disagree with what was sent.
7181
6970
  promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
7182
6971
  storedState: req.session,
7183
6972
  usageBaseline: threadUsageBaseline,
7184
- // CT601: Codex reports no model in-stream, so record what the thread was
7185
- // started with (null on the default token). Same for the reasoning effort.
6973
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
6974
+ // default token). Same for the reasoning effort.
7186
6975
  resolvedModel: spec.model,
7187
6976
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
7188
6977
  });
@@ -7231,11 +7020,9 @@ function makeRequest3(overrides2 = {}) {
7231
7020
  systemPrompt: PROMPT,
7232
7021
  prompt: "hi there",
7233
7022
  content: [{ type: "text", text: "hi there" }],
7234
- // CT601: the resolved model + reasoning effort ride the terminal `result` off
7235
- // the run spec's model. The behavioral fixtures below don't assert them, so the
7236
- // default is the model-less ("let Codex choose") token → no `resolvedModel`
7237
- // emitted → their expected results stay unchanged; a dedicated capture fixture
7238
- // sets a real model + effort.
7023
+ // The resolved model and reasoning effort ride the terminal `result` off the run spec's model.
7024
+ // Behavioural fixtures use the model-less token (no `resolvedModel` emitted); a dedicated capture
7025
+ // fixture sets a real model and effort.
7239
7026
  config: { model: null },
7240
7027
  policy: COMPANION_POLICY2,
7241
7028
  session: null,
@@ -7287,13 +7074,12 @@ var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint =
7287
7074
  });
7288
7075
  var CODEX_CONFORMANCE_FIXTURES = [
7289
7076
  {
7290
- // A plain text reply: the held agent message seals as the terminal final at
7291
- // `turn.completed`; the usage counters ride the `result`.
7077
+ // A plain text reply: the held agent message seals as the terminal final at `turn.completed`;
7078
+ // usage rides the `result`.
7292
7079
  //
7293
- // CT1283: the counters NEST — `cached_input_tokens` is a slice of
7294
- // `input_tokens`, `reasoning_output_tokens` a slice of `output_tokens` — so
7295
- // input is 100 (not 120) and output is 50 (not 60), with the cache read broken
7296
- // out beside them. This fixture pinned the double-count before CT1283.
7080
+ // The counters nest — `cached_input_tokens` is a slice of `input_tokens`,
7081
+ // `reasoning_output_tokens` a slice of `output_tokens` — so input is 100 (not 120) and output 50
7082
+ // (not 60), with the cache read broken out beside them.
7297
7083
  name: "clean turn",
7298
7084
  request: makeRequest3(),
7299
7085
  nativeStream: [
@@ -7310,10 +7096,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7310
7096
  expected: [
7311
7097
  sessionEvent3(NEW_THREAD_ID),
7312
7098
  { type: "text", body: "Hello there.", terminal: true },
7313
- // CT1645: the closing session write — the thread's ending totals, recorded
7314
- // as the baseline the NEXT turn subtracts. This is a fresh thread, so the
7315
- // `result` usage below is the frame verbatim (nothing to subtract yet);
7316
- // what makes it per-turn is this figure being carried into turn two.
7099
+ // The closing session write — the thread's ending totals, recorded as the baseline the next turn
7100
+ // subtracts. A fresh thread, so the `result` usage below is the frame verbatim.
7317
7101
  sessionEvent3(NEW_THREAD_ID, DIR2, false, fingerprintPrompt(PROMPT), {
7318
7102
  inputTokens: 100,
7319
7103
  outputTokens: 50,
@@ -7385,8 +7169,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7385
7169
  ]
7386
7170
  },
7387
7171
  {
7388
- // CT1211: recorded MCP success shape. Codex exposes the text compatibility
7389
- // copy and the native object together; normalization must keep the object.
7172
+ // The recorded MCP success shape: Codex exposes the text copy and the native object together;
7173
+ // normalization must keep the object.
7390
7174
  name: "MCP success prefers native structured content",
7391
7175
  request: makeRequest3(),
7392
7176
  nativeStream: [
@@ -7436,8 +7220,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7436
7220
  ]
7437
7221
  },
7438
7222
  {
7439
- // CT1211 incident shape: MCP `isError` keeps the failed phase, while the raw
7440
- // camel-case structured payload survives instead of the generic `error` token.
7223
+ // MCP `isError` keeps the failed phase, while the raw camel-case structured payload survives
7224
+ // instead of the generic `error` token.
7441
7225
  name: "failed MCP call preserves structured receipt while remaining error phase",
7442
7226
  request: makeRequest3(),
7443
7227
  nativeStream: [
@@ -7489,8 +7273,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7489
7273
  ]
7490
7274
  },
7491
7275
  {
7492
- // A transport/tool failure has no MCP result payload and still uses the real
7493
- // adapter error message. CT1211 must not replace this fallback with `undefined`.
7276
+ // A transport/tool failure with no MCP result payload still uses the real adapter error message,
7277
+ // never `undefined`.
7494
7278
  name: "failed MCP call without result keeps error message fallback",
7495
7279
  request: makeRequest3(),
7496
7280
  nativeStream: [
@@ -7530,8 +7314,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7530
7314
  ]
7531
7315
  },
7532
7316
  {
7533
- // CT1147 stays intact when a connector includes structured metadata beside an
7534
- // image-only MCP response: no image/base64 payload enters activity receipts.
7317
+ // A connector's structured metadata beside an image-only MCP response: no image/base64 payload
7318
+ // enters activity receipts.
7535
7319
  name: "image-only MCP result remains empty despite structured metadata",
7536
7320
  request: makeRequest3(),
7537
7321
  nativeStream: [
@@ -7624,12 +7408,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
7624
7408
  ]
7625
7409
  },
7626
7410
  {
7627
- // CT715: web_search LIFECYCLE. Unlike the other three tool kinds, a `web_search`
7628
- // item carries NO `status` field — completion is signaled by the frame TYPE
7629
- // (`item.started` → `item.completed`). The `start` fires off the first frame
7630
- // (empty query, no output card); the `done` must resolve off `item.completed`
7631
- // and carry the populated query the completed frame filled in. Before CT715 the
7632
- // card was pinned at "running" forever (status derived from a missing field).
7411
+ // web_search lifecycle. A `web_search` item carries no `status` — completion is signalled by the
7412
+ // frame type (`item.started` → `item.completed`). The `start` fires off the first frame (empty
7413
+ // query); the `done` must resolve off `item.completed` with the populated query, not stay
7414
+ // "running".
7633
7415
  name: "web_search lifecycle \u2014 completes off frame type, populated query on done",
7634
7416
  request: makeRequest3(),
7635
7417
  nativeStream: [
@@ -7660,9 +7442,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7660
7442
  ]
7661
7443
  },
7662
7444
  {
7663
- // CT715: a non-text web action (`action.type: "other"`) legitimately completes
7664
- // with an EMPTY query — that's Codex's own data, not our bug. It must still
7665
- // resolve to `done` (empty query acceptable; stuck-running is not).
7445
+ // A non-text web action (`action.type: "other"`) legitimately completes with an empty query —
7446
+ // Codex's own data. It must still resolve to `done`.
7666
7447
  name: "web_search lifecycle \u2014 empty-query completion still resolves to done",
7667
7448
  request: makeRequest3(),
7668
7449
  nativeStream: [
@@ -7693,9 +7474,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7693
7474
  ]
7694
7475
  },
7695
7476
  {
7696
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
7697
- // adapter stops before the closing reply — no final text, no `result` event.
7698
- // CT1295: the server writes the `kind:'stopped'` marker.
7477
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops before
7478
+ // the closing reply — no final text, no `result` event (the server writes the stopped marker).
7699
7479
  name: "cancel mid-stream",
7700
7480
  request: makeRequest3(),
7701
7481
  nativeStream: [
@@ -7776,11 +7556,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7776
7556
  ]
7777
7557
  },
7778
7558
  {
7779
- // CT592: Codex used to classify NOTHING — every `turn.failed` became a raw
7780
- // `error:<text>` `unknown`. It now routes the failure text through the shared
7781
- // classifier (Codex is text-only), so a provider 429 / outage / auth / cap
7782
- // failure splits into the honest taxonomy instead of all reading `unknown`.
7783
- // This is a real behavior change, not a refactor. One fixture per kind:
7559
+ // The failure text goes through the shared classifier (Codex is text-only), so a provider 429 /
7560
+ // outage / auth / cap failure splits into the honest taxonomy. One fixture per kind:
7784
7561
  name: "turn.failed 429 \u2192 rate_limited (throttle)",
7785
7562
  request: makeRequest3(),
7786
7563
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
@@ -7827,8 +7604,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7827
7604
  ]
7828
7605
  },
7829
7606
  {
7830
- // CT592: a Codex failure text the classifier can't place still keeps today's
7831
- // raw `error:<text>` reason (the `unknown` fallback) — nothing regresses.
7607
+ // A failure text the classifier can't place keeps the raw `error:<text>` reason (the `unknown`
7608
+ // fallback).
7832
7609
  name: "turn.failed unclassified \u2192 raw error reason (unchanged)",
7833
7610
  request: makeRequest3(),
7834
7611
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
@@ -7852,8 +7629,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7852
7629
  ]
7853
7630
  },
7854
7631
  {
7855
- // CT1351: empty, whitespace-only, and bare status-token failures still set
7856
- // the machine reason but do not add an empty/unhelpful transcript notice.
7632
+ // Empty, whitespace-only and bare status-token failures still set the machine reason but add no
7633
+ // empty or unhelpful transcript notice.
7857
7634
  name: "empty failure text emits no runtime notice",
7858
7635
  request: makeRequest3(),
7859
7636
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
@@ -7882,11 +7659,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7882
7659
  ]
7883
7660
  },
7884
7661
  {
7885
- // CT481: fail LOUD on unknown model metadata. An unknown `--model` makes Codex
7886
- // emit an `error` item ("… Defaulting to fallback metadata …") and then go
7887
- // haywire (the spurious tool call scripted after it MUST never be emitted). The
7888
- // adapter ends the turn the moment the signal lands, with a machine reason — so
7889
- // a future Codex id-drop errors clean instead of degrading mid-tool-turn.
7662
+ // Fail loud on unknown model metadata. An unknown `--model` makes Codex emit an `error` item
7663
+ // ("… Defaulting to fallback metadata …") and misbehave (the spurious tool call scripted after it
7664
+ // must never be emitted). The adapter ends the turn when the signal lands, with a machine reason.
7890
7665
  name: "unknown model metadata \u2192 fail loud (guard)",
7891
7666
  request: makeRequest3(),
7892
7667
  nativeStream: [
@@ -7932,12 +7707,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
7932
7707
  ]
7933
7708
  },
7934
7709
  {
7935
- // CT1144: the same zero-content success the claude-code suite pins, in
7936
- // Codex's vocabulary — the thread starts, the turn completes, and nothing is
7937
- // emitted in between. No Codex sample of the anomaly has been seen in the
7938
- // wild; the fixture exists because the host's classification is runtime-
7939
- // neutral, so a runtime that CAN produce this envelope must be shown
7940
- // producing it in the shape the classifier reads.
7710
+ // The zero-content success the claude-code suite pins, in Codex's vocabulary — the thread
7711
+ // starts, the turn completes, nothing in between. No Codex sample has been seen; the fixture
7712
+ // exists because the host's classification is runtime-neutral, so a runtime that can produce
7713
+ // this envelope must be shown producing it in the shape the classifier reads.
7941
7714
  name: "zero-content success (empty runtime result)",
7942
7715
  request: makeRequest3(),
7943
7716
  nativeStream: [threadStarted(NEW_THREAD_ID), turnCompleted()],
@@ -8006,11 +7779,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
8006
7779
  ]
8007
7780
  },
8008
7781
  {
8009
- // CT601: Codex reports no model in-stream, so the resolved model + reasoning
8010
- // effort come from what the thread was STARTED with (the run spec) and ride the
8011
- // terminal `result`. A real model + a `runtimeOptions.codex.modelReasoningEffort`
8012
- // are asserted through to `resolvedModel` (bare, `openai/`-stripped) and
8013
- // `resolvedConfig.reasoningEffort`.
7782
+ // Codex reports no model in-stream, so the resolved model and reasoning effort come from what the
7783
+ // thread was started with (the run spec) and ride the terminal `result`: a real model and a
7784
+ // `runtimeOptions.codex.modelReasoningEffort` are asserted through to `resolvedModel` (bare,
7785
+ // `openai/`-stripped) and `resolvedConfig.reasoningEffort`.
8014
7786
  name: "resolved model + reasoning effort captured from run spec",
8015
7787
  request: makeRequest3({
8016
7788
  config: {
@@ -8031,15 +7803,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8031
7803
  ]
8032
7804
  },
8033
7805
  {
8034
- // Session resume: the stored state matches the current directory, so the adapter
8035
- // resumes its thread id and `thread.started` echoes the SAME id — NO session
8036
- // event fires (the id didn't change AND the state is unchanged). This is what
8037
- // carries cross-turn memory.
7806
+ // Session resume: the stored state matches the directory, so the thread id is resumed and
7807
+ // `thread.started` echoes it — no session event fires. This is what carries cross-turn memory.
8038
7808
  //
8039
- // CT1075: the stored state also says the thread already holds the composed
8040
- // prompt, so this turn withholds the preamble and the state it would write is
8041
- // byte-identical to the stored one. The silence is now proof of BOTH: an id that
8042
- // didn't change and a prompt that didn't need re-sending.
7809
+ // The stored state also says the thread already holds the composed prompt, so this turn
7810
+ // withholds the preamble and the state it would write is byte-identical: the silence proves both
7811
+ // an unchanged id and a prompt that needed no re-send.
8043
7812
  name: "session resume (no re-emit on echo)",
8044
7813
  request: makeRequest3({
8045
7814
  session: encodeSession3({
@@ -8056,16 +7825,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8056
7825
  ]
8057
7826
  },
8058
7827
  {
8059
- // CT1075: a resumed thread the platform's state does NOT vouch for — a state
8060
- // written before CT1075 existed, so it carries no `promptFingerprint` field. The
8061
- // adapter reads the absence as "we can't say which prompt this thread holds",
8062
- // sends the full prompt exactly as it did pre-CT1075, and re-emits the state with
8063
- // the fingerprint recorded — on the SAME thread id, which pre-CT1075 emitted
8064
- // nothing at all. That re-emit is the upgrade path: without it a long-lived
8065
- // thread would re-send the whole prompt forever.
7828
+ // A resumed thread whose stored state carries no `promptFingerprint` field. The adapter reads the
7829
+ // absence as "can't say which prompt this thread holds", sends the full prompt, and re-emits the
7830
+ // state with the fingerprint — on the same thread id. That re-emit is the upgrade path: without
7831
+ // it a long-lived thread would re-send the whole prompt forever.
8066
7832
  //
8067
- // This is the fixture that fails if the static half is withheld on a thread we
8068
- // have no evidence about.
7833
+ // This fixture fails if the standing half is withheld on a thread we have no evidence about.
8069
7834
  name: "session resume with a pre-CT1075 state re-emits with the prompt fingerprint",
8070
7835
  request: makeRequest3({
8071
7836
  session: JSON.stringify({ threadId: "th1", cwd: DIR2 })
@@ -8078,14 +7843,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8078
7843
  ]
8079
7844
  },
8080
7845
  {
8081
- // CT1075 (review finding): the thread carries a DIFFERENT prompt than the one
8082
- // this turn composed — a charter edit, a renamed agent, a redeployed prompt
8083
- // body. The stored fingerprint doesn't match, so the new prompt is sent exactly
8084
- // as on a fresh thread and the state is re-recorded against it.
7846
+ // The thread carries a different prompt than this turn composed — a charter edit, a renamed
7847
+ // agent, a redeployed prompt body. The fingerprint doesn't match, so the new prompt is sent as on
7848
+ // a fresh thread and the state re-recorded.
8085
7849
  //
8086
- // This is the fixture that fails if a stale prompt is treated as good enough. A
8087
- // boolean flag would have passed the resume fixture above and silently withheld
8088
- // every future charter edit from this thread.
7850
+ // This fixture fails if a stale prompt is treated as good enough; a boolean flag would pass the
7851
+ // resume fixture above and withhold every future charter edit.
8089
7852
  name: "session resume with a stale prompt fingerprint re-sends the prompt",
8090
7853
  request: makeRequest3({
8091
7854
  session: encodeSession3({
@@ -8103,15 +7866,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8103
7866
  ]
8104
7867
  },
8105
7868
  {
8106
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was created
8107
- // under a DIFFERENT directory, so the adapter refuses to resume and starts
8108
- // fresh — `thread.started` reports a NEW id, and the emitted session event
8109
- // records the CURRENT directory with `degraded: true` so the host rewinds the
8110
- // catch-up mark.
7869
+ // Directory-mismatch degrade: the stored state was created under another directory, so the
7870
+ // adapter starts fresh — `thread.started` reports a new id, and the session event records the
7871
+ // current directory with `degraded: true` so the host rewinds the catch-up mark.
8111
7872
  //
8112
- // CT1075: the refused resume means the new thread is EMPTY, so the full prompt
8113
- // goes in even though the stored state vouched for a copy — of a thread we're
8114
- // no longer in. The emitted state records the fresh thread's own answer.
7873
+ // The refused resume means the new thread is empty, so the full prompt goes in even though the
7874
+ // stored state vouched for a copy in a thread we're no longer in.
8115
7875
  name: "session directory-mismatch degrades to fresh",
8116
7876
  request: makeRequest3({
8117
7877
  session: encodeSession3({
@@ -8160,7 +7920,7 @@ var ConnectorHealthStore = class {
8160
7920
  lastError: outcome.errorReason ?? null
8161
7921
  });
8162
7922
  }
8163
- // CT639: the store's current opinion for one runtime, or undefined when it has
7923
+ // The store's current opinion for one runtime, or undefined when it has
8164
7924
  // no observation. Read by the dispatcher at idle-timeout fire: an idle reap
8165
7925
  // while this runtime is definitively `limited` (a present, future `limitedUntil`
8166
7926
  // recorded by a sibling/earlier turn's classified cap settle) is the account cap
@@ -8464,8 +8224,7 @@ function createTurnContainment(turnId, deps = {}) {
8464
8224
  `--unit=${unit}`,
8465
8225
  `--property=TimeoutStopSec=${CONTAINMENT_STOP_GRACE_SEC}s`,
8466
8226
  // Bind the scope's life to the companion's own unit, so stopping or
8467
- // restarting the companion still takes its in-flight turns with it —
8468
- // which is what happens today, and what the scope would otherwise undo.
8227
+ // restarting the companion still takes its in-flight turns with it.
8469
8228
  // Omitted outside a unit, where there is nothing to bind to.
8470
8229
  ...ownUnit ? [`--property=PartOf=${ownUnit}`] : [],
8471
8230
  "--"
@@ -8785,7 +8544,7 @@ var mcpHttpServerSchema = z15.object({
8785
8544
  type: z15.literal("http"),
8786
8545
  url: z15.string().url(),
8787
8546
  headers: z15.record(z15.string(), z15.string()).optional(),
8788
- // CT1698: header → the name of a `secretEnv` entry holding its value. Set by
8547
+ // Header → the name of a `secretEnv` entry holding its value. Set by
8789
8548
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8790
8549
  // header whose `${VAR}` it resolved.
8791
8550
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -8794,7 +8553,7 @@ var mcpSseServerSchema = z15.object({
8794
8553
  type: z15.literal("sse"),
8795
8554
  url: z15.string().url(),
8796
8555
  headers: z15.record(z15.string(), z15.string()).optional(),
8797
- // CT1698: header → the name of a `secretEnv` entry holding its value. Set by
8556
+ // Header → the name of a `secretEnv` entry holding its value. Set by
8798
8557
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8799
8558
  // header whose `${VAR}` it resolved.
8800
8559
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -8807,9 +8566,8 @@ var thinkingConfigSchema = z15.discriminatedUnion("type", [
8807
8566
  ]);
8808
8567
  var effortSchema = z15.enum(["low", "medium", "high", "xhigh", "max"]);
8809
8568
  var runConfigSchema = z15.object({
8810
- // CT788: the host-access binary replaced the `assistant`/`coding`/`custom` mode
8811
- // trio + its custom tool lists — `true` grants the host filesystem/shell, absent
8812
- // is the locked surface. Kept in lockstep with `@cabane/shared`'s
8569
+ // `true` grants the host filesystem/shell; absent is the locked surface. Kept
8570
+ // in lockstep with `@cabane/shared`'s
8813
8571
  // `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
8814
8572
  // stripped, so an older companion riding a newer server never rejects the config).
8815
8573
  hostAccess: z15.boolean().optional(),
@@ -8836,7 +8594,7 @@ var RUNNER_ENV = {
8836
8594
  dispatch: "CABANE_DISPATCH",
8837
8595
  // The turn id the launcher resolved, and whether this is a replay of a turn
8838
8596
  // an earlier process started. The seq floor for a replay is derived inside
8839
- // the turn from the outbox and the server, as it always was.
8597
+ // the turn from the outbox and the server.
8840
8598
  turnId: "CABANE_TURN_ID",
8841
8599
  resumed: "CABANE_RESUMED",
8842
8600
  // Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
@@ -8872,10 +8630,10 @@ var runnerSpecSchema = z16.object({
8872
8630
  // Whether the harness starts under the OS sandbox. The launcher sends 'os'
8873
8631
  // only when this host can make one.
8874
8632
  sandbox: z16.enum(["os", "none"]),
8875
- // CT1684: the device's sandbox policy, validated by the launcher. Absent
8633
+ // The device's sandbox policy, validated by the launcher. Absent
8876
8634
  // when the sandbox is off.
8877
8635
  sandboxPolicy: sandboxPolicySchema.optional(),
8878
- // CT1691: every companion store the launcher's latest walk found, denied
8636
+ // Every companion store the launcher's latest walk found, denied
8879
8637
  // to the harness. Absent when the sandbox is off.
8880
8638
  companionStores: z16.array(z16.string()).optional(),
8881
8639
  logLevel: z16.string(),
@@ -8884,15 +8642,15 @@ var runnerSpecSchema = z16.object({
8884
8642
  // True when this (conversation, agent) pair was already told its session
8885
8643
  // could not be saved: the notice is once per pair, not once per process.
8886
8644
  sessionWriteNoticeClaimed: z16.boolean(),
8887
- // CT1680: the runner's identity, minted with its credential. The start
8645
+ // The runner's identity, minted with its credential. The start
8888
8646
  // PATCH carries it so the lease knows which runner holds the span.
8889
8647
  runnerId: z16.string(),
8890
- // CT1681: the launcher keeps the runtime's session files beside the attempt
8648
+ // The launcher keeps the runtime's session files beside the attempt
8891
8649
  // and restores them into this runner's home, so the runner names the attempt
8892
8650
  // each context was composed for and waits for the launcher to say it's in
8893
8651
  // place. Absent: nothing to wait for (a runner on the launcher's own disk).
8894
8652
  attemptState: z16.boolean().optional(),
8895
- // CT1681: MCP `${NAME}` secrets come from the launcher, by name, instead of
8653
+ // MCP `${NAME}` secrets come from the launcher, by name, instead of
8896
8654
  // this machine's secrets.json. Absent: the runner reads the device's file.
8897
8655
  secretsFromLauncher: z16.boolean().optional()
8898
8656
  });
@@ -9521,7 +9279,7 @@ function createTurnControlMcpServer(acts, skipState) {
9521
9279
  return performAct(acts, {
9522
9280
  kind: "ask",
9523
9281
  callId: randomUUID2(),
9524
- // CT1350: the swap rides the ask, so the server commits both halves
9282
+ // The swap rides the ask, so the server commits both halves
9525
9283
  // in one transaction — the person never sees neither or both.
9526
9284
  ...args.replaces ? { replaces: args.replaces } : {},
9527
9285
  ask: hasArray ? {
@@ -9572,7 +9330,7 @@ function createTurnControlMcpServer(acts, skipState) {
9572
9330
  },
9573
9331
  { annotations: ACT_TOOL, alwaysLoad: true }
9574
9332
  ),
9575
- // CT1350: the standalone withdrawal. Not annotated destructive — an ask
9333
+ // The standalone withdrawal. Not annotated destructive — an ask
9576
9334
  // is a question, not data, and a harness that gated this on a live
9577
9335
  // operator would hang a turn nobody is watching (the `cancel_wake`
9578
9336
  // reasoning). Not idempotent either: a second call with the same id is
@@ -9591,7 +9349,7 @@ function createTurnControlMcpServer(acts, skipState) {
9591
9349
  }),
9592
9350
  { annotations: ACT_TOOL, alwaysLoad: true }
9593
9351
  ),
9594
- // CT990: the inverse. No arguments — it can only ever stand down the
9352
+ // The inverse. No arguments — it can only ever stand down the
9595
9353
  // caller's own wake in the conversation it's holding a turn in.
9596
9354
  tool(
9597
9355
  CANCEL_WAKE_TOOL,
@@ -9634,7 +9392,7 @@ function buildCompanionTurnRequest(params) {
9634
9392
  // adapter's `ResolvedMcpServers`.
9635
9393
  ...Object.keys(params.mcpServers).length > 0 ? { mcpServers: params.mcpServers } : {},
9636
9394
  ...params.secretEnv && Object.keys(params.secretEnv).length > 0 ? { secretEnv: params.secretEnv } : {},
9637
- // CT289: the auto-memory escape hatch, when the operator set it.
9395
+ // The auto-memory escape hatch, when the operator set it.
9638
9396
  ...params.claudeCode ? { claudeCode: params.claudeCode } : {}
9639
9397
  },
9640
9398
  // Host-injected turn-control tools, under their own namespace.
@@ -9888,7 +9646,7 @@ var TranscriptWriter = class {
9888
9646
  }
9889
9647
  this.line({ type: "_meta", ...meta });
9890
9648
  }
9891
- // Append a raw SDK message verbatim.
9649
+ // Append one event verbatim.
9892
9650
  write(msg) {
9893
9651
  this.line(msg);
9894
9652
  }
@@ -9896,7 +9654,7 @@ var TranscriptWriter = class {
9896
9654
  close(outcome) {
9897
9655
  this.line({ type: "_outcome", ...outcome });
9898
9656
  }
9899
- // CT1145: keep an independent copy of an empty successful envelope after the
9657
+ // Keep an independent copy of an empty successful envelope after the
9900
9658
  // footer has landed. The ordinary 200-file FIFO keeps rotating as before;
9901
9659
  // this bucket retains the newest 50 anomalies regardless of ordinary traffic.
9902
9660
  // Best-effort like every other transcript operation: observability can never
@@ -10053,12 +9811,9 @@ var TurnCommitter = class {
10053
9811
  // Renders each `TurnEvent` onto the pump's callback sink (tool-log truncation),
10054
9812
  // routing a throwing commit to `onError`.
10055
9813
  emit;
10056
- // CT261: route one `TurnEvent` from the adapter. `text` / `tool` / `thinking`
10057
- // flow through the pump (rendered + committed); `session` / `result` are the
10058
- // dispatcher's concern and ignored here. A throwing commit (a flaky/4xx HTTP
10059
- // POST) is routed to the error hook and swallowed — a failed commit is
10060
- // warn-only and must never fail the turn (the role the classification's
10061
- // `safeEmit` used to play before the adapter owned the decode).
9814
+ // `text` / `tool` / `thinking` flow through the pump; `session` / `result` are
9815
+ // the dispatcher's and ignored here. A throwing commit is routed to the error
9816
+ // hook and swallowed: a failed commit must never fail the turn.
10062
9817
  async ingestEvent(event) {
10063
9818
  if (event.type === "session" || event.type === "result") return;
10064
9819
  try {
@@ -10237,18 +9992,15 @@ var TurnExecution = class {
10237
9992
  turnLog;
10238
9993
  abortController = new AbortController();
10239
9994
  outcome = initialOutcome();
10240
- // CT1503: this turn's shell containment, once the claude-code adapter is built.
10241
- // Null before that and on a turn that never reached adapter construction — a
10242
- // startup failure has nothing to reap, and reaping is a no-op rather than a
10243
- // special case.
9995
+ // This turn's shell containment, once the claude-code adapter is built. Null
9996
+ // before that, so a startup failure's reap is a no-op.
10244
9997
  containment = null;
10245
9998
  seqCounter = 0;
10246
- // CT11: per-turn monotonic counter mirroring the in-process dispatcher's.
10247
- //
10248
- // CT1380: 0 for a NEW turn only — a resume is seeded in `fetchContext` first.
9999
+ // Per-turn monotonic seq. Starts at 0 for a NEW turn only; a resume is seeded
10000
+ // in `fetchContext` first.
10249
10001
  nextSeq = () => ++this.seqCounter;
10250
- // CT1380: a REPLAY, told to us by the supervisor — not derivable here, since
10251
- // `turnId` is set on every dispatch. `resumedFromSeq` is what it seeded from.
10002
+ // A REPLAY, told to us by the supervisor — not derivable here, since `turnId`
10003
+ // is set on every dispatch. `resumedFromSeq` is what it seeded from.
10252
10004
  resumed;
10253
10005
  resumedFromSeq;
10254
10006
  // Whether the span this resumed run continues already has first words —
@@ -10256,8 +10008,8 @@ var TurnExecution = class {
10256
10008
  resumedSpoke = false;
10257
10009
  turnContext;
10258
10010
  resolvedMcpServers;
10259
- // CT1698: the harness's secret environment for this turn. Only ever handed
10260
- // to the adapter; never logged, never merged into `process.env`.
10011
+ // The harness's secret environment for this turn. Only ever handed to the
10012
+ // adapter; never logged, never merged into `process.env`.
10261
10013
  secretEnv = {};
10262
10014
  effectiveCwd;
10263
10015
  hookEnv;
@@ -10271,19 +10023,15 @@ var TurnExecution = class {
10271
10023
  turnControlIntentFetched = false;
10272
10024
  applyRecordedTurnControlIntent = async () => {
10273
10025
  };
10274
- // CT1292: the committer is built before the lease watchdog arms, so the
10275
- // failed-commit hint reaches it through this slot. Safe as a no-op until the
10276
- // watchdog replaces it — the committer only fires it from inside the adapter
10277
- // loop, which runs after.
10026
+ // The committer is built before the lease watchdog arms, so the failed-commit
10027
+ // hint reaches it through this slot. A no-op until then is safe: the committer
10028
+ // only fires it from inside the adapter loop, which runs after.
10278
10029
  noteCommitFailed = () => {
10279
10030
  };
10280
10031
  disarmWatchdogs = () => {
10281
10032
  };
10282
- // CT1503: reap this turn's shell containment. Idempotent (the containment drops
10283
- // what it has already stopped) and total — a reap that fails is reported and
10284
- // swallowed, because a turn that produced a good answer must not be recorded as
10285
- // failed on account of a leftover process, and the failure is visible in the log
10286
- // either way.
10033
+ // Idempotent, and a failed reap is logged and swallowed: a turn that produced a
10034
+ // good answer must not be recorded as failed on account of a leftover process.
10287
10035
  async reapContainment() {
10288
10036
  const containment = this.containment;
10289
10037
  if (!containment) return;
@@ -10309,20 +10057,18 @@ var TurnExecution = class {
10309
10057
  );
10310
10058
  }
10311
10059
  }
10312
- // Codo's stack review, blocking finding #2: set the moment `acquireLease`'s
10313
- // PATCH returns — the lease is granted and this is the pair's running span,
10314
- // so every exit after this point must settle THE SPAN, not just clear the
10060
+ // Set the moment `acquireLease`'s PATCH returns: this is the pair's running
10061
+ // span, so every exit after this must settle THE SPAN, not just clear the
10315
10062
  // participant flag.
10316
10063
  admitted = false;
10317
- // Rooms: one recompose per turn when the lease refuses a stale context.
10064
+ // One recompose per turn when the lease refuses a stale context.
10318
10065
  recomposedForLease = false;
10319
- // CT1677: the execution source the CURRENT context was adopted as. Reassigned
10320
- // when a `dispatch_context_stale` recompose adopts a replacement, so a piece of
10321
- // work — preparation, the committer, a notice — captures it when that work
10322
- // begins and never reads it again: earlier work keeps its source. Undefined
10323
- // only before the first adoption; no work runs until it is set.
10066
+ // The execution source the CURRENT context was adopted as. A
10067
+ // `dispatch_context_stale` recompose reassigns it, so each piece of work
10068
+ // (preparation, the committer, a notice) captures it when it begins and keeps
10069
+ // that source. No work runs before it is set.
10324
10070
  executionSourceId;
10325
- // CT1677: one fresh fetch per turn when adoption is refused.
10071
+ // One fresh fetch per turn when adoption is refused.
10326
10072
  refetchedForAdoption = false;
10327
10073
  concluded(reason, errorReason) {
10328
10074
  return new TurnConcluded(reason, errorReason);
@@ -10390,21 +10136,18 @@ var TurnExecution = class {
10390
10136
  this.supervisor.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
10391
10137
  return { ok: false, durationMs, reason, runtime: this.turnContext?.runtime };
10392
10138
  }
10393
- // The post-admission analogue of `concludeBeforeRun` (Codo's stack review,
10394
- // blocking finding #2): this turn WAS admitted — it holds the conversation's
10395
- // lease — so the clear must NAME the turn and DECLARE its terminal outcome.
10396
- // A bare pre-run clear here would reset the participant flag while the span
10397
- // stayed running until the lost sweep. `outcome` follows the settle wire's
10398
- // rule: a failure names its reason; without one the turn settled — the
10399
- // runtime-unavailable exit posts its user-facing `final` before concluding,
10400
- // so the turn genuinely produced its terminal output.
10139
+ // The admitted turn holds the conversation's lease, so the clear must NAME the
10140
+ // turn and DECLARE its outcome; a bare clear resets the participant flag and
10141
+ // leaves the span running until the lost sweep. A failure names its reason;
10142
+ // without one the turn settled (the runtime-unavailable exit has already posted
10143
+ // its `final`).
10401
10144
  async concludeAdmittedRun(reason, errorReason) {
10402
10145
  const { payload, turnLog, startedAt, turnId } = this;
10403
10146
  const body = {
10404
10147
  activeRunStartedAt: null,
10405
10148
  turnId,
10406
- // CT1379: `runtimeIncomplete` is a real failure that deliberately carries
10407
- // no `errorReason`, so the inference alone would read it as settled.
10149
+ // `runtimeIncomplete` is a real failure that deliberately carries no
10150
+ // `errorReason`, so the inference alone would read it as settled.
10408
10151
  outcome: errorReason || this.outcome.runtimeIncomplete ? "failed" : "settled"
10409
10152
  };
10410
10153
  if (errorReason) body.errorReason = errorReason.slice(0, 200);
@@ -10498,11 +10241,10 @@ var TurnExecution = class {
10498
10241
  throw this.concluded(reason, reason);
10499
10242
  }
10500
10243
  }
10501
- // CT1677: register this context's issuance as the execution source of the
10502
- // work about to run on it. A context with no issuance has nothing to adopt,
10503
- // and that ends the turn exactly as a failed adoption does: this companion
10504
- // never commits a row whose provenance it cannot name. Only a refusal —
10505
- // nothing to adopt any more — earns a fresh fetch.
10244
+ // Register this context's issuance as the execution source of the work about to
10245
+ // run on it. No issuance ends the turn as a failed adoption does: this companion
10246
+ // never commits a row whose provenance it cannot name. Only a refusal earns a
10247
+ // fresh fetch.
10506
10248
  async adoptSource(context) {
10507
10249
  const { payload, turnId, turnLog } = this;
10508
10250
  if (!context.issuanceId) {
@@ -10551,9 +10293,8 @@ var TurnExecution = class {
10551
10293
  secretEnv,
10552
10294
  missing
10553
10295
  } = resolveMcpSecrets(
10554
- // CT262: the user MCP DEFINITIONS (placeholder form) come from the turn
10555
- // context now, not a separate `getAgentSelf` run-config fetch. CT1698: and
10556
- // the room connections' entries, whose values arrive in `secretEnv`.
10296
+ // User MCP definitions in placeholder form, plus the room connections'
10297
+ // entries, whose values arrive in `secretEnv`.
10557
10298
  this.turnContext.mcpServers ?? {},
10558
10299
  secretStore,
10559
10300
  this.turnContext.secretEnv
@@ -10666,15 +10407,12 @@ var TurnExecution = class {
10666
10407
  agentUsername: this.opts.agentUsername,
10667
10408
  runtime: this.turnContext.runtime,
10668
10409
  hostAccess: this.turnContext.policy.hostFs,
10669
- // CT317/CT319: the trigger message's referenced-entry paths — what the
10670
- // tasker prepare hook keys its per-task env off. Defaults to `[]` for
10671
- // an older API. The conversation anchor is gone (CT319).
10410
+ // The trigger message's referenced-entry paths, which a prepare hook
10411
+ // may key its per-task env off. `[]` from an older API.
10672
10412
  triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
10673
10413
  title: this.turnContext.conversation.title,
10674
- // CT943: the dispatching message's text — where an `env:` directive
10675
- // rides. The server has always sent the trigger body on the turn
10676
- // context (for the live feed); this is the first thing to read it as
10677
- // an INPUT, so a dispatch can ask for its environment in words.
10414
+ // Where an `env:` directive rides, so a dispatch can ask for its
10415
+ // environment in words.
10678
10416
  messageBody: this.turnContext.message.body
10679
10417
  },
10680
10418
  { turnId, deviceId: this.opts.deviceId },
@@ -10720,37 +10458,30 @@ var TurnExecution = class {
10720
10458
  payload.agentId,
10721
10459
  {
10722
10460
  activeRunStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
10723
- // CT1679: a runner leases under its launcher's boot id — the one the
10724
- // device heartbeats — or the lost-run sweep reads it as a restart.
10461
+ // A runner leases under its launcher's boot id, the one the device
10462
+ // heartbeats, or the lost-run sweep reads it as a restart.
10725
10463
  bootId: this.opts.inRunner?.bootId ?? bootId,
10726
- // CT1680: the runner holding the span, beside the device's boot id.
10727
10464
  ...this.opts.inRunner ? { runnerId: this.opts.inRunner.runnerId } : {},
10728
- // CT1380: the durable record that this turn resumed, and from where.
10465
+ // The durable record that this turn resumed, and from where.
10729
10466
  ...this.resumedFromSeq !== void 0 ? { resumedFromSeq: this.resumedFromSeq } : {},
10730
- // CT1454: the issuance the context THIS attempt runs on came from. Read
10731
- // off `this.turnContext`, which is the fetch that composed this request —
10732
- // so a retransmitted start echoes the same id and reuses the same record,
10733
- // and a resumed attempt echoes its own, newer one. The post-admission
10734
- // fresh-session recompose (`freshRequest`) deliberately does not touch it:
10735
- // it adopts only the prompt, never the run-config, so its issuance must
10736
- // not become this attempt's evidence.
10467
+ // The issuance of the fetch that composed this request, so a
10468
+ // retransmitted start reuses the same record and a resumed attempt
10469
+ // echoes its own. `freshRequest` deliberately leaves it alone: it adopts
10470
+ // only the prompt, never the run-config.
10737
10471
  ...this.turnContext.issuanceId ? { issuanceId: this.turnContext.issuanceId } : {},
10738
- // CT33: hand the server this turn's id so the new-run chokepoint's
10739
- // `closeAbandonedTurns` sweep excludes it. The prepare hook may have
10740
- // already emitted a "preparing" activity row for this turn above (it
10741
- // runs before this flip), which would otherwise make the sweep mistake
10742
- // this live turn for an abandoned one and close it with a `stopped`.
10472
+ // So `closeAbandonedTurns` excludes this turn: a "preparing" row emitted
10473
+ // before this flip would otherwise read as abandoned and be stopped.
10743
10474
  turnId,
10744
10475
  // The newest message this turn's read covered: the lease records it,
10745
10476
  // and settle moves the cursor to it. Omitted when the server sent
10746
10477
  // none (an older API), so the cursor stays where it was.
10747
10478
  ...this.turnContext.readThrough !== void 0 ? { readThrough: this.turnContext.readThrough } : {},
10748
10479
  ...this.turnContext.readRevision !== void 0 ? { readRevision: this.turnContext.readRevision } : {},
10749
- // Rooms: the attempt this request's session belongs to (null = fresh).
10480
+ // The attempt this request's session belongs to (null = fresh).
10750
10481
  ...this.turnContext.attemptId !== void 0 ? { attemptId: this.turnContext.attemptId } : {},
10751
- // CT1543: the frame this start acts on. The turn-context fetch already
10752
- // checked it, but that was before an assembly this turn awaited — the
10753
- // lease checks it again where the stamp is actually taken.
10482
+ // The frame this start acts on. The context fetch checked it before an
10483
+ // assembly this turn awaited; the lease re-checks where the stamp is
10484
+ // taken.
10754
10485
  ...payload.episode !== void 0 ? { dispatchMessageId: payload.messageId, dispatchEpisode: payload.episode } : {}
10755
10486
  }
10756
10487
  );
@@ -10790,21 +10521,18 @@ var TurnExecution = class {
10790
10521
  this.request = buildCompanionTurnRequest({
10791
10522
  turnContext: this.turnContext,
10792
10523
  baseUrl: this.opts.baseUrl,
10793
- // ACC8: always present; `fetchTurnContext` refused a context without one.
10524
+ // Always present: `fetchContext` refuses a context without one.
10794
10525
  turnToken: this.turnContext.turnToken,
10795
- // SJ524: the hook-resolved cwd overrides the static local cwd.
10796
10526
  ...this.effectiveCwd ? { cwd: this.effectiveCwd } : {},
10797
- // CT1103: the prepare-hook env plus the authoritative host-access token.
10798
10527
  env: this.turnEnv,
10799
10528
  mcpServers: this.resolvedMcpServers,
10800
10529
  secretEnv: this.secretEnv,
10801
10530
  turnControlServer,
10802
- // CT238: this turn's conversation, forwarded as the active-conversation
10803
- // header so a cross-thread post/spawn stamps its origin.
10531
+ // Sent as the active-conversation header so a cross-conversation post
10532
+ // stamps its origin.
10804
10533
  activeConversationId: payload.conversationId,
10805
- // Workspace coordinate retained in the compatible TurnRequest envelope.
10806
10534
  workspaceId,
10807
- // CT289: the operator's auto-memory escape hatch (machine-local), when set.
10535
+ // The operator's machine-local auto-memory escape hatch, when set.
10808
10536
  ...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
10809
10537
  });
10810
10538
  }
@@ -10841,8 +10569,8 @@ var TurnExecution = class {
10841
10569
  if (this.opts.claudeCodeAvailable?.() ?? true) {
10842
10570
  this.containment = createTurnContainment(this.turnId, {
10843
10571
  log: (msg, meta) => turnLog.debug(meta ?? {}, msg),
10844
- // CT1679: a group outside the runner's own is reported, so a launcher
10845
- // can stop it if the runner dies without reaping it.
10572
+ // A group outside the runner's own is reported, so a launcher can stop
10573
+ // it if the runner dies without reaping it.
10846
10574
  ...this.opts.inRunner?.onProcessGroup ? { onProcessGroup: this.opts.inRunner.onProcessGroup } : {}
10847
10575
  });
10848
10576
  const containment = this.containment;
@@ -10863,8 +10591,8 @@ var TurnExecution = class {
10863
10591
  const serverUrl = this.opts.opencodeServerUrl;
10864
10592
  adapters.push(
10865
10593
  createOpencodeAdapter(
10866
- // CT1679: the transport's per-server turn mutex is per process; in a
10867
- // runner the turn also holds the device-wide lock every runner shares.
10594
+ // The transport's per-server turn mutex is per process; in a runner the
10595
+ // turn also holds the device-wide lock every runner shares.
10868
10596
  this.opts.inRunner ? {
10869
10597
  transport: this.opts.inRunner.opencodeTransport(serverUrl),
10870
10598
  onWarn
@@ -10911,7 +10639,7 @@ var TurnExecution = class {
10911
10639
  kind: "runtime_notice",
10912
10640
  turnId,
10913
10641
  parentMessageId: payload.messageId,
10914
- // CT1677: the notice is written by this attempt's source, like its speech.
10642
+ // Written by this attempt's source, like its speech.
10915
10643
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {}
10916
10644
  });
10917
10645
  } catch (postErr) {
@@ -10925,16 +10653,13 @@ var TurnExecution = class {
10925
10653
  throw this.concluded(`runtime_unavailable:${err.runtime}`);
10926
10654
  }
10927
10655
  }
10928
- // The adapter refused to resume this pair's stored session and is starting a
10929
- // fresh one. The request it holds was composed for a session that had the
10930
- // conversation so far — the read is what landed since the cursor, the
10931
- // agent's own words left out because the session held them. A fresh session
10932
- // holds none of it, so fetch the context again as a first turn on an empty
10933
- // session (the whole conversation, own words included, the first-turn
10934
- // material) and hand the adapter that to run on — before it speaks or acts.
10935
- // The lease already granted stands; the read reaches at least its mark. A
10936
- // fetch that fails returns null and the adapter falls back to the request
10937
- // it has, flagged degraded, so the server rewinds the cursor at settle.
10656
+ // The adapter refused to resume the stored session and is starting fresh. The
10657
+ // request it holds assumed the session had the conversation so far (only what
10658
+ // landed since the cursor, the agent's own words left out); a fresh session
10659
+ // has none of it. So refetch as a first turn on an empty session and hand the
10660
+ // adapter that, before it speaks or acts. The granted lease stands. A failed
10661
+ // fetch returns null: the adapter runs the request it has, flagged degraded,
10662
+ // and the server rewinds the cursor at settle.
10938
10663
  async freshRequest(reason) {
10939
10664
  const { payload, turnId, turnLog } = this;
10940
10665
  try {
@@ -10945,9 +10670,8 @@ var TurnExecution = class {
10945
10670
  false,
10946
10671
  true,
10947
10672
  void 0,
10948
- // CT1657: the turn's abort. This recompose happens mid-run with the
10949
- // adapter waiting on it, so a Stop here must end the wait rather than
10950
- // hold the turn open for the rest of the retry schedule.
10673
+ // The adapter is waiting on this, so a Stop must end the wait rather
10674
+ // than hold the turn open for the rest of the retry schedule.
10951
10675
  this.abortController.signal
10952
10676
  );
10953
10677
  turnLog.debug(
@@ -10999,19 +10723,17 @@ var TurnExecution = class {
10999
10723
  conversationId: payload.conversationId,
11000
10724
  agentId: payload.agentId,
11001
10725
  turnId,
11002
- // CT113: the message this turn answers — passed back on every commit so
11003
- // the reply records the Q→A edge the in-process dispatcher stamps directly.
10726
+ // The message this turn answers, on every commit so the reply records the
10727
+ // Q→A edge.
11004
10728
  parentMessageId: payload.messageId,
11005
10729
  signal: abortController.signal,
11006
10730
  log: turnLog,
11007
10731
  nextSeq: this.nextSeq,
11008
10732
  spoke: this.resumedSpoke,
11009
- // CT1677: admitted by now, so this is the source the model's work runs on.
10733
+ // Admitted by now, so this is the source the model's work runs on.
11010
10734
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {},
11011
- // CT1292: a commit that didn't land may mean this turn's lease is gone.
11012
- // `noteCommitFailed` is a field slot the lease watchdog fills when it
11013
- // arms (it starts as a no-op) — the committer only fires it from inside
11014
- // the adapter loop, which runs after the watchdog has replaced it.
10735
+ // A commit that didn't land may mean the lease is gone; see
10736
+ // `noteCommitFailed`.
11015
10737
  onCommitFailed: (err) => this.noteCommitFailed(err)
11016
10738
  });
11017
10739
  const usesHttpTurnControl = turnRuntime === "codex" || turnRuntime === "opencode";
@@ -11115,9 +10837,9 @@ var TurnExecution = class {
11115
10837
  workspaceId,
11116
10838
  payload.conversationId,
11117
10839
  payload.agentId,
11118
- // CT1680: named for its turn, which a runner credential
11119
- // requires once its start is admitted; the server then binds
11120
- // the capture to that turn's attempt rather than the pair's.
10840
+ // Named for its turn, which a runner credential requires once
10841
+ // admitted; the server binds the capture to that turn's attempt
10842
+ // rather than the pair's.
11121
10843
  { agentSessionId: event.state, turnId }
11122
10844
  );
11123
10845
  } catch (err) {
@@ -11222,9 +10944,7 @@ var TurnExecution = class {
11222
10944
  }
11223
10945
  const body = {
11224
10946
  activeRunStartedAt: null,
11225
- // CT277: the server keys the turn-end metadata UPDATE (ended_at + tokens)
11226
- // off this turn id, so the settle PATCH must carry it — the start PATCH
11227
- // isn't enough. Always sent (every settle closes a real turn).
10947
+ // The server keys the turn-end metadata (ended_at, tokens) off this id.
11228
10948
  turnId
11229
10949
  };
11230
10950
  if (o.turnUsage) {
@@ -11392,11 +11112,11 @@ var Dispatcher = class {
11392
11112
  this.aborts = opts.aborts ?? /* @__PURE__ */ new Map();
11393
11113
  }
11394
11114
  opts;
11395
- // CT1288: keyed (conversation|agent) → per-TURN controllers. Stop is
11396
- // pair-scoped and must reach every live loop for the pair.
11115
+ // (conversation|agent) → per-TURN controllers. Stop is pair-scoped and must
11116
+ // reach every live loop for the pair.
11397
11117
  aborts;
11398
- // CT1109: pairs already told "your session couldn't be saved" — the notice is
11399
- // once per (conversation, agent), while the rejection repeats every turn.
11118
+ // Pairs already told their session couldn't be saved: the notice is once per
11119
+ // pair, while the rejection repeats every turn.
11400
11120
  sessionWriteNotified = /* @__PURE__ */ new Set();
11401
11121
  notifyStart(info) {
11402
11122
  try {
@@ -11413,11 +11133,6 @@ var Dispatcher = class {
11413
11133
  // The supervisor supplies a stable turnId on both fresh and resumed dispatch.
11414
11134
  // resumed is separate: only replay seeds sequence/speech recovery from the
11415
11135
  // prior span. A direct caller omitting turnId gets a fresh id.
11416
- //
11417
- // CT1261: a short `handle` creates a `TurnExecution` and runs it. The
11418
- // supervisor seam hands the execution exactly the three cross-turn duties —
11419
- // the abort registry, the session-write notice latch, the observer — and
11420
- // nothing else.
11421
11136
  async handle(payload, opts = {}) {
11422
11137
  const key = runKey(payload.conversationId, payload.agentId);
11423
11138
  const supervisor = {
@@ -11463,8 +11178,8 @@ var Dispatcher = class {
11463
11178
  }
11464
11179
  return new TurnExecution(this.opts, supervisor, payload, opts).run();
11465
11180
  }
11466
- // SJ383: cancel a specific (conversation, agent) run if one is in flight in
11467
- // THIS companion process. Returns true if an in-flight run was aborted.
11181
+ // Cancel a (conversation, agent) run in flight in THIS process. True if one
11182
+ // was aborted.
11468
11183
  cancel(conversationId, agentId) {
11469
11184
  const key = runKey(conversationId, agentId);
11470
11185
  const pair = this.aborts.get(key);
@@ -13161,7 +12876,7 @@ var Outbox = class {
13161
12876
  }
13162
12877
  workspaceId;
13163
12878
  log;
13164
- // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
12879
+ // Resolved lazily (per call) so tests that swap HOME between
13165
12880
  // cases route writes at the right tmpdir.
13166
12881
  dir() {
13167
12882
  return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
@@ -13227,7 +12942,7 @@ var Outbox = class {
13227
12942
  );
13228
12943
  return entries;
13229
12944
  }
13230
- // CT1380: the highest `seq` this turn has queued but not yet delivered — the
12945
+ // The highest `seq` this turn has queued but not yet delivered — the
13231
12946
  // half of a resumed turn's seq floor the SERVER CANNOT SEE. A commit that
13232
12947
  // failed transiently before the crash is sitting in this directory with its
13233
12948
  // seq already spent, and the restart's drain is fire-and-forget
@@ -13451,24 +13166,20 @@ var CompanionSupervisor = class {
13451
13166
  config;
13452
13167
  log;
13453
13168
  hub;
13454
- // CT1379: the bundled-executable resolver — the execution-presence half of both
13455
- // routing gates. Synchronous and cheap (a module resolve plus a stat), so unlike
13456
- // the CLI probes it needs no boot-frozen fallback: a caller that asks before the
13457
- // first harness refresh simply resolves now.
13169
+ // The execution-presence half of both routing gates. Synchronous and cheap (a
13170
+ // module resolve plus a stat), so a caller before the first harness refresh
13171
+ // simply resolves now.
13458
13172
  resolveBundled;
13459
- // CT1379: the last bundled status LOGGED per harness, so a missing binary is
13460
- // reported at startup and on each healthy↔missing transition rather than on
13461
- // every 30s beat (the CT484 latch pattern, per runtime).
13173
+ // The last bundled status LOGGED per harness, so a missing binary is reported
13174
+ // at startup and on each healthy↔missing transition, not every beat.
13462
13175
  loggedBundledStatus = /* @__PURE__ */ new Map();
13463
- // CT571: the probed per-harness versions, reported on each heartbeat manifest.
13464
- // CT586: no longer boot-frozen — the per-heartbeat harness re-probe refreshes
13465
- // it, so a harness updated mid-run reports its new version without a restart.
13176
+ // Refreshed by the per-heartbeat re-probe, so a harness updated mid-run
13177
+ // reports its new version without a restart.
13466
13178
  harnessVersions;
13467
- // CT586: the latest harness detection signals (presence + reachability), probed
13468
- // on the heartbeat cadence + on demand. Feeds BOTH the heartbeat manifest's
13469
- // live presence and the Companion's Harnesses surface off ONE source, so the UI
13470
- // can never disagree with what the manifest advertises. Null until the first
13471
- // probe (the heartbeat then falls back to the boot `claudeCode`).
13179
+ // The latest harness signals (presence, reachability), probed on the heartbeat
13180
+ // cadence and on demand. ONE source for both the manifest and the Harnesses
13181
+ // surface, so the UI never disagrees with what is advertised. Null until the
13182
+ // first probe.
13472
13183
  harnessSignals = null;
13473
13184
  // Fresh per attempt, never the cached heartbeat signal: installing a harness
13474
13185
  // between attempts must be observed immediately.
@@ -13481,8 +13192,8 @@ var CompanionSupervisor = class {
13481
13192
  unrunnableRetryDelaysMs;
13482
13193
  deviceApi = null;
13483
13194
  deviceSub = null;
13484
- // ACC8: whether the device stream is open, which is what every workspace's
13485
- // link on the dashboard reports.
13195
+ // Whether the device stream is open, which is what every workspace's link on
13196
+ // the dashboard reports.
13486
13197
  deviceConnected = false;
13487
13198
  recoveryChecked = false;
13488
13199
  recovering = false;
@@ -13494,8 +13205,7 @@ var CompanionSupervisor = class {
13494
13205
  pollTimer = null;
13495
13206
  refreshing = false;
13496
13207
  stopped = false;
13497
- // CT484: latch so the companion/server version-skew warning is logged once, not
13498
- // on every 30s heartbeat.
13208
+ // The version-skew warning is logged once, not every heartbeat.
13499
13209
  versionSkewWarned = false;
13500
13210
  // This device's id, captured from the heartbeat / assignments response. The SSE
13501
13211
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
@@ -13505,12 +13215,12 @@ var CompanionSupervisor = class {
13505
13215
  // The in-flight assignments pull, exposed so a caller that needs fresh data
13506
13216
  // (pull-on-miss) can `await` the running pull rather than racing past it.
13507
13217
  inFlightRefresh = null;
13508
- // CT566: device-global connector-health memory — every dispatcher records its
13509
- // turn's classified outcome here (per runtime); the heartbeat echoes it so the
13510
- // server's per-connector quota state survives a restart/race.
13218
+ // Device-global connector-health memory: every dispatcher records its turn's
13219
+ // classified outcome per runtime; the heartbeat echoes it so the server's
13220
+ // quota state survives a restart or race.
13511
13221
  connectorHealth = new ConnectorHealthStore();
13512
- // ACC10: which dispatches this process is running, so the copy of a frame that
13513
- // arrives on the other stream doesn't run the turn a second time.
13222
+ // Which dispatches this process is running, so a redelivered frame doesn't
13223
+ // run the turn a second time.
13514
13224
  claims = new DispatchClaims();
13515
13225
  constructor(opts) {
13516
13226
  this.config = opts.config;
@@ -13531,10 +13241,10 @@ var CompanionSupervisor = class {
13531
13241
  }
13532
13242
  this.latchBootBundledState();
13533
13243
  }
13534
- // CT1681: what a runner container said it can run, probed once at start;
13535
- // null until then, and when no container could be started.
13244
+ // What a runner container said it can run, probed once at start; null until
13245
+ // then, and when no container could be started.
13536
13246
  runnerImage = null;
13537
- // CT1681: the companion every container mounts — this one's own install.
13247
+ // The companion every container mounts: this one's own install.
13538
13248
  runnerCompanion() {
13539
13249
  return companionSource({
13540
13250
  configured: this.config.runners?.docker?.companionDir,
@@ -13646,10 +13356,7 @@ var CompanionSupervisor = class {
13646
13356
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
13647
13357
  this.pollTimer.unref?.();
13648
13358
  }
13649
- // The set of SSE subscriber `finished` promises — start.ts awaits these to
13650
- // keep the foreground process alive. With the poll loop running there's always
13651
- // at least the loops keeping the event loop alive; the SSE promises let a
13652
- // clean teardown resolve.
13359
+ // start.ts awaits these; they resolve on a clean teardown.
13653
13360
  finishedPromises() {
13654
13361
  return [...this.deviceSub ? [this.deviceSub.finished] : []];
13655
13362
  }
@@ -13677,39 +13384,26 @@ var CompanionSupervisor = class {
13677
13384
  bootId,
13678
13385
  version: COMPANION_VERSION,
13679
13386
  exposedSecretNames: store.names(),
13680
- // Report each runtime only when this device can actually run it:
13681
- // CT1379 claude-code and codex when the SDK's own bundled binary is there
13682
- // and the user asked for the harness, CT270 opencode when the operator
13683
- // configured an `opencode serve`.
13387
+ // Each runtime only when this device can actually run it.
13684
13388
  manifest: buildCompanionManifest({
13685
- // CT1082/CT1379: connected AND launchable. Execution presence is the
13686
- // live bundled-binary resolution (refreshed on this beat), consent is
13687
- // the user's `claudeCode.enabled`. Claude Code rides neither alone.
13389
+ // Consented (`claudeCode.enabled`) AND launchable (the live bundled
13390
+ // binary). Neither alone.
13688
13391
  claudeCode: this.claudeCodeOffered(),
13689
13392
  opencode: !!this.config.opencode?.serverUrl,
13690
- // CT481/CT1379: advertise codex when the operator enabled it AND its
13691
- // vendored binary is there. The enable flag alone used to be enough,
13692
- // which is how a device with a half-installed 331 MB platform package
13693
- // advertised a runtime that could not start.
13393
+ // Enabled AND its vendored binary present: a half-installed 331 MB
13394
+ // platform package otherwise advertises a runtime that cannot start.
13694
13395
  codex: this.codexOffered(),
13695
- // CT1379: and say WHY when an asked-for harness is absent from the list
13696
- // above. Diagnostic only — the server never routes on it — and always
13396
+ // WHY an asked-for harness is absent above. Diagnostic only, and always
13697
13397
  // sent, so an empty array clears a previously reported issue.
13698
13398
  runtimeIssues: this.runtimeIssues(),
13699
- // CT571/CT586: each runtime's `version` from the latest harness probe
13700
- // (fail-soft to null). Informational only — the server matches on name.
13399
+ // Informational only; the server matches on name.
13701
13400
  versions: this.harnessVersions,
13702
- // CT1681: shown on the device page.
13401
+ // Shown on the device page.
13703
13402
  ...runnersEnabled(this.config) ? { runner: this.runnerManifest() } : {}
13704
13403
  }),
13705
- // CT566: echo the last classified credential state per runtime, when the
13706
- // companion has seen any. Omitted (undefined) until the first observed
13707
- // failure/heal, so a fresh companion's beat is unchanged and the server's
13708
- // manifest synthesis (status-less rows) still runs.
13404
+ // Omitted until the first observed failure or heal, so the server's
13405
+ // status-less manifest synthesis still runs for a fresh companion.
13709
13406
  ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
13710
- // CT584: include enumerated models only when the probe SUCCEEDED (non-null).
13711
- // A null (probe failed / no opencode) omits the field, and the server then
13712
- // leaves this device's stored availability untouched.
13713
13407
  ...opencodeModels !== null ? { models: opencodeModels } : {}
13714
13408
  });
13715
13409
  this.hub.setDevice({ deviceId: res.deviceId });
@@ -13724,11 +13418,8 @@ var CompanionSupervisor = class {
13724
13418
  );
13725
13419
  }
13726
13420
  }
13727
- // CT484: belt-and-suspenders beside shipping the companion in lockstep inside the
13728
- // artifact — if the server reports a build version that differs from this
13729
- // companion's, warn loudly (once). This is exactly the skew that bit the M1 walk:
13730
- // an npm-pinned companion lagging a from-develop server. The elimination (bundled
13731
- // companion) makes it match by construction; this catches a companion run out of band.
13421
+ // Warn once when the server's build version differs from this companion's: an
13422
+ // npm-pinned companion lagging the server is the usual cause of odd failures.
13732
13423
  checkVersionSkew(serverVersion) {
13733
13424
  if (this.versionSkewWarned) return;
13734
13425
  if (!serverVersion || serverVersion === COMPANION_VERSION) return;
@@ -13925,10 +13616,9 @@ var CompanionSupervisor = class {
13925
13616
  `Stopped running ${runner.displayName} (unassigned)`
13926
13617
  );
13927
13618
  }
13928
- // CT1379: can this machine LAUNCH the harness — is the SDK's own bundled
13929
- // executable there? Live (the per-beat re-resolve), falling back to resolving
13930
- // right now before the first refresh lands. Execution presence ONLY; consent is
13931
- // the caller's half.
13619
+ // Can this machine LAUNCH the harness — is the SDK's bundled executable there?
13620
+ // The per-beat result, or resolved now before the first refresh. Execution
13621
+ // presence ONLY; consent is the caller's half.
13932
13622
  bundledStatus(runtime) {
13933
13623
  const cached2 = runtime === "codex" ? this.harnessSignals?.codexBundled : this.harnessSignals?.claudeBundled;
13934
13624
  return cached2 ?? this.resolveBundled(runtime).status;
@@ -13939,23 +13629,19 @@ var CompanionSupervisor = class {
13939
13629
  bundledResolution(runtime) {
13940
13630
  return this.resolveBundled(runtime);
13941
13631
  }
13942
- // CT1082/CT1379: does this device OFFER claude-code — connected by its user, and
13943
- // able to start the binary a turn runs? The ONE signal both the heartbeat
13944
- // manifest and the dispatcher's adapter registry read, so what the device
13945
- // advertises and what it can select can't disagree (the CT833 invariant, with
13946
- // consent in front of it and the real executable underneath it).
13632
+ // Does this device OFFER claude-code — connected by its user, and able to start
13633
+ // the binary a turn runs? The ONE signal both the heartbeat manifest and the
13634
+ // adapter registry read, so what the device advertises and what it can select
13635
+ // can't disagree.
13947
13636
  claudeCodeOffered() {
13948
13637
  return isClaudeCodeConnected(this.config) && this.bundledStatus("claude-code") === "present";
13949
13638
  }
13950
- // CT1379: and the same question for codex, which until now was the enable flag
13951
- // alone — so an enabled device with no vendored binary advertised a runtime it
13952
- // could not start. Same shape as Claude Code, same predicate on both sides.
13639
+ // The same question for codex: enabled AND its vendored binary present.
13953
13640
  codexOffered() {
13954
13641
  return isCodexEnabled(this.config) && this.bundledStatus("codex") === "present";
13955
13642
  }
13956
- // CT1379: the manifest's diagnostic half, derived from the same two facts as the
13957
- // two predicates above so an issue and an advertisement can never both be made
13958
- // for one harness.
13643
+ // Derived from the same two facts as the predicates above, so an issue and an
13644
+ // advertisement are never both made for one harness.
13959
13645
  runtimeIssues() {
13960
13646
  return runtimeIssuesFor({
13961
13647
  claudeCode: {
@@ -13965,26 +13651,16 @@ var CompanionSupervisor = class {
13965
13651
  codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") }
13966
13652
  });
13967
13653
  }
13968
- // CT1379: a missing binary is reported ONCE, and again only when the answer
13969
- // CHANGES — with the runtime, the platform/architecture and the resolution
13970
- // result, which is enough to find the occurrence in `companion.log` without a
13971
- // repro and carries nothing that could be a credential or a vendor's output.
13654
+ // A missing binary is reported ONCE, and again only when the answer CHANGES,
13655
+ // with runtime, platform/arch and status: enough to find it in `companion.log`
13656
+ // without a repro, and nothing that could be a credential or vendor output.
13972
13657
  //
13973
- // Two things this deliberately does NOT do (Codo's review, blocking finding #2):
13974
- //
13975
- // - It does not re-announce the state the device booted in. `warnAboutHarnessReadiness`
13976
- // already said that on stderr at startup, in the specified words, at the one
13977
- // moment a person is watching a terminal — and the `--daemon` launcher runs
13978
- // that preflight in the FOREGROUND for exactly that reason, because the
13979
- // child's output goes to a file. The boot state is latched in the
13980
- // constructor, so the first refresh reports nothing new; it lands here as a
13981
- // debug record instead, present for diagnosis without competing for
13982
- // attention with the warning the operator already read.
13983
- // - It does not report a transition in generic prose. A binary that goes
13984
- // missing at 3am is the case the operator will actually meet, and a line
13985
- // that says only "binary is missing" leaves them with no repair and no
13986
- // restart instruction. Transitions carry the specified copy verbatim — the
13987
- // same sentence the startup path prints.
13658
+ // - The boot state is not re-announced: `warnAboutHarnessReadiness` said it
13659
+ // on stderr at startup, when a person is watching (the `--daemon` launcher
13660
+ // runs that preflight in the FOREGROUND because the child logs to a file).
13661
+ // It is latched in the constructor and recorded at debug.
13662
+ // - A transition carries the same repair copy the startup path prints, not
13663
+ // generic prose: a binary missing at 3am needs the repair and the restart.
13988
13664
  logBundledTransitions() {
13989
13665
  for (const runtime of ["claude-code", "codex"]) {
13990
13666
  const intended = runtime === "codex" ? isCodexEnabled(this.config) : isClaudeCodeConnected(this.config);
@@ -14007,9 +13683,8 @@ var CompanionSupervisor = class {
14007
13683
  this.log.debug(meta, "companion: bundled harness binary is missing (harness not in use)");
14008
13684
  }
14009
13685
  }
14010
- // CT1379: the boot state, latched so the first refresh reports no news, and
14011
- // recorded once as evidence. Runs in the constructor — the resolve is a module
14012
- // lookup plus a stat, so it costs nothing and needs no await.
13686
+ // The boot state, latched so the first refresh reports no news, and recorded
13687
+ // once as evidence. Synchronous: a module lookup plus a stat.
14013
13688
  latchBootBundledState() {
14014
13689
  for (const runtime of ["claude-code", "codex"]) {
14015
13690
  const resolution = this.resolveBundled(runtime);
@@ -14027,35 +13702,34 @@ var CompanionSupervisor = class {
14027
13702
  );
14028
13703
  }
14029
13704
  }
14030
- // CT1679: the locks every runner on this device shares, held here.
13705
+ // The locks every runner on this device shares.
14031
13706
  runnerLocks = new DeviceLocks();
14032
- // CT1679: `runners.sandbox`, checked against this host once. A host that
14033
- // can't make the sandbox says so once and runs its turns unwrapped.
13707
+ // `runners.sandbox`, checked against this host once. A host that can't make
13708
+ // the sandbox says so once and runs its turns unwrapped.
14034
13709
  sandboxUsable;
14035
13710
  sandboxHost = {
14036
13711
  osSandboxUsable: () => this.sandboxUsable ??= osSandboxUsable(this.log)
14037
13712
  };
14038
- // CT1684: `runners.policy`, parsed once. An invalid one is kept as the
14039
- // error, which every sandboxed turn is refused with.
13713
+ // `runners.policy`, parsed once. An invalid one is kept as the error, which
13714
+ // every sandboxed turn is refused with.
14040
13715
  parsedSandboxPolicy;
14041
13716
  sandboxPolicy() {
14042
13717
  this.parsedSandboxPolicy ??= parseSandboxPolicy(this.config.runners?.policy);
14043
13718
  return this.parsedSandboxPolicy;
14044
13719
  }
14045
- // CT1691: the walk for other companions' stores, run off the turn path.
13720
+ // The walk for other companions' stores, run off the turn path.
14046
13721
  storeWatch;
14047
13722
  companionStores() {
14048
13723
  this.storeWatch ??= companionStoreWatch(this.log);
14049
13724
  return this.storeWatch;
14050
13725
  }
14051
- // CT1693: the sandbox one agent's turns run under — its `agents` row, then
14052
- // the device's. Resolved from the current config every time it's asked, so
14053
- // a reload changes it; only the host probe is kept.
13726
+ // The sandbox one agent's turns run under: its `agents` row, then the
13727
+ // device's. Resolved from the current config on every ask, so a reload
13728
+ // changes it; only the host probe is kept.
14054
13729
  runnerSandboxFor(row) {
14055
13730
  return runnerSandboxFor(this.config, row, this.sandboxHost);
14056
13731
  }
14057
- // CT1693: what the heartbeat says about this device's runners, resolved
14058
- // against the agents it runs right now.
13732
+ // Resolved against the agents this device runs right now.
14059
13733
  runnerManifest() {
14060
13734
  const agents = [...this.workspaces.values()].flatMap(
14061
13735
  (wr) => [...wr.agents.values()].map((agent) => ({
@@ -14069,7 +13743,7 @@ var CompanionSupervisor = class {
14069
13743
  ...runnerSandboxReport(this.config, agents, this.sandboxHost)
14070
13744
  };
14071
13745
  }
14072
- // CT1679: one provider for the device, built on first use.
13746
+ // One provider for the device, built on first use.
14073
13747
  runnerProvider() {
14074
13748
  this.providerForRunners ??= this.dockerRunners() ? this.createDockerRunners() : createLocalProcessProvider();
14075
13749
  return this.providerForRunners;
@@ -14111,8 +13785,7 @@ var CompanionSupervisor = class {
14111
13785
  log: this.log
14112
13786
  });
14113
13787
  }
14114
- // CT1681: the runner command for this device's provider: the image's own
14115
- // companion in a container, this process's otherwise.
13788
+ // The image's own companion in a container, this process's otherwise.
14116
13789
  runnerCommandFor() {
14117
13790
  if (this.runnerCommand) return this.runnerCommand;
14118
13791
  return this.dockerRunners() ? ["cabane-companion", "run-turn"] : selfRunnerCommand();
@@ -14144,36 +13817,20 @@ var CompanionSupervisor = class {
14144
13817
  aborts: ctx.aborts,
14145
13818
  runConfig: ctx.runConfig,
14146
13819
  log: this.log.child({ agentName: ctx.agentDisplayName ?? ctx.agentUsername }),
14147
- // CT833: register the claude-code adapter only when this device actually
14148
- // offers claude-code — read per turn (not captured here), so a harness
14149
- // installed or connected after boot works on the next turn exactly as it
14150
- // appears on the next beat. CT1082: "offers" means connected as well as
14151
- // runnable, so a disconnected harness can't be selected either.
14152
- // CT1379: and the same for the bundled binary — the predicate is shared with
14153
- // the heartbeat above, so a runtime advertised by one and refused by the
14154
- // other is not expressible.
13820
+ // Read PER TURN off the heartbeat's own predicates, so a harness installed
13821
+ // or connected after boot works on the next turn exactly as it appears on
13822
+ // the next beat, and a runtime advertised by one and refused by the other
13823
+ // is not expressible.
14155
13824
  claudeCodeAvailable: () => this.claudeCodeOffered(),
14156
- // CT270: the opencode server URL (operator-configured), when this device
14157
- // offers the opencode runtime. Threaded so an opencode turn selects the
14158
- // opencode adapter; unset leaves the device claude-code-only.
14159
13825
  ...this.config.opencode?.serverUrl ? { opencodeServerUrl: this.config.opencode.serverUrl } : {},
14160
- // CT481: register the codex adapter when this device offers codex; a miss
14161
- // leaves an `openai/…` turn to fail loudly (no silent claude-code fallback).
14162
- // CT1379: read PER TURN, like claude-code above, and off the same predicate
14163
- // the heartbeat uses — it was captured here at dispatcher-construction time
14164
- // from the enable flag alone, so a device could register an adapter for a
14165
- // binary it did not have and keep it registered after the binary came back.
14166
13826
  codexAvailable: () => this.codexOffered(),
14167
- // CT556: per-turn timeout watchdog windows, from the companion's own env
14168
- // (`AGENT_IDLE_TIMEOUT_MS` / `AGENT_TOTAL_TIMEOUT_MS`). Unset → the
14169
- // dispatcher's baked-in defaults (CT1503: 15 min idle / 6h total). An
14170
- // override set here still wins, and is deliberately not validated against
14171
- // the tool ceilings it may reorder — see DEFAULT_AGENT_IDLE_TIMEOUT_MS.
13827
+ // Unset uses the dispatcher's defaults. An override is deliberately not
13828
+ // validated against the tool ceilings it may reorder; see
13829
+ // DEFAULT_AGENT_IDLE_TIMEOUT_MS.
14172
13830
  ...positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") !== void 0 ? { idleTimeoutMs: positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") } : {},
14173
13831
  ...positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") !== void 0 ? { totalTimeoutMs: positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") } : {},
14174
13832
  observer: this.hub.observerFor(ctx.workspaceId, ctx.agentId, ctx.workspaceSlug),
14175
13833
  transcriptDir: transcriptsDir(),
14176
- // CT566: the shared connector-health memory the turn records into on settle.
14177
13834
  connectorHealth: this.connectorHealth,
14178
13835
  ...runnersEnabled(this.config) ? {
14179
13836
  runner: {
@@ -14197,11 +13854,10 @@ var CompanionSupervisor = class {
14197
13854
  this.workspaces.delete(workspaceId);
14198
13855
  this.hub.removeWorkspace(workspaceId);
14199
13856
  }
14200
- // ---- SSE event routing (per workspace, BY agentId) ----
14201
- // ACC10: the device stream's frames — dispatch and cancel, addressed to this
14202
- // device and authenticated as it. ACC8: the only stream this companion opens.
14203
- // It keeps no cursor: it replays what is still offered on every connect
14204
- // instead of resuming from a `Last-Event-ID`.
13857
+ // ---- SSE event routing (BY agentId) ----
13858
+ // Dispatch and cancel, addressed to this device. The stream keeps no cursor:
13859
+ // it replays what is still offered on every connect instead of resuming from
13860
+ // a `Last-Event-ID`.
14205
13861
  async handleDeviceMessage(ev) {
14206
13862
  const wire = this.parseFrame(ev);
14207
13863
  if (!wire) return;
@@ -14237,9 +13893,9 @@ var CompanionSupervisor = class {
14237
13893
  return null;
14238
13894
  }
14239
13895
  }
14240
- // ACC10: the dedupe gate. A reconnect replays every dispatch still offered,
14241
- // including one whose turn is already running here; the first copy runs the
14242
- // turn and a later one waits for it and then does nothing.
13896
+ // A reconnect replays every dispatch still offered, including one whose turn
13897
+ // is already running here; the first copy runs it and a later one waits for
13898
+ // it and then does nothing.
14243
13899
  //
14244
13900
  // A cancel needs no gate: `dispatcher.cancel` on an already-aborted run is a
14245
13901
  // no-op that reports `false`, so a second copy is inert.
@@ -14255,8 +13911,6 @@ var CompanionSupervisor = class {
14255
13911
  if (claim) this.claims.release(wire.id);
14256
13912
  }
14257
13913
  }
14258
- // Everything below here is the routing as it was: it takes a frame and knows
14259
- // nothing about which stream carried it.
14260
13914
  async routeFrame(wr, wire) {
14261
13915
  if (wire.type === "device:cancel_requested") {
14262
13916
  const payload2 = {
@@ -14322,17 +13976,12 @@ var CompanionSupervisor = class {
14322
13976
  await tail;
14323
13977
  if (wr.chains.get(chainKey) === tail) wr.chains.delete(chainKey);
14324
13978
  }
14325
- // CT1146: tell the server this device cannot run a dispatch it was addressed to.
14326
- //
13979
+ // Tell the server this device cannot run a dispatch it was addressed to.
14327
13980
  // Returns whether the report LANDED. The server checks the recorded dispatch
14328
- // destination even if the agent's assignment has moved. Reporting promptly
14329
- // explains the roster miss; the thirty-minute unanswered-offer deadline is
14330
- // only the backstop when no report lands.
13981
+ // destination even if the assignment has moved.
14331
13982
  //
14332
- // Bounded retry, because the common failure is transient (a 503 mid-deploy, a
14333
- // dropped socket) and one more attempt a second later usually lands. Bounded
14334
- // hard — this runs inline on the event loop, and an unbounded wait here would
14335
- // wedge the workspace's whole stream.
13983
+ // Retried, because the common failure is transient (a 503 mid-deploy), and
13984
+ // bounded hard: this runs inline, and an unbounded wait would wedge the stream.
14336
13985
  async reportUnrunnableDispatch(workspaceId, payload) {
14337
13986
  if (!this.deviceApi) return false;
14338
13987
  let lastErr;
@@ -14372,9 +14021,8 @@ var CompanionSupervisor = class {
14372
14021
  );
14373
14022
  return false;
14374
14023
  }
14375
- // CT1680: a runner is gone without having settled its turn. Same bounded
14376
- // retry as the unrunnable report above, on the device credential, which is
14377
- // the one the launcher still holds whatever became of the runner.
14024
+ // A runner is gone without having settled its turn. Same bounded retry, on the
14025
+ // device credential, which the launcher holds whatever became of the runner.
14378
14026
  async reportRunnerGone(report) {
14379
14027
  if (!this.deviceApi) return;
14380
14028
  let lastErr;
@@ -14399,12 +14047,9 @@ var CompanionSupervisor = class {
14399
14047
  "Couldn't tell Cabane a turn's runner stopped; its credential expires on its own."
14400
14048
  );
14401
14049
  }
14402
- // Pull-on-miss for the assignment-change race: a dispatch arrived for an agent
14403
- // we don't yet run. If it's addressed to THIS device, the assignment just
14404
- // changed under us (the poll hasn't caught it) — force a fresh assignments pull
14405
- // and return the now-live runner. Returns undefined when the dispatch isn't for
14406
- // this device, or the agent still isn't ours after pulling (a genuine
14407
- // not-ours dispatch) — the caller then settles and ignores it as before.
14050
+ // Pull-on-miss: a dispatch addressed to THIS device for an agent we don't run
14051
+ // yet means the assignment changed under us. Undefined when it isn't for this
14052
+ // device, or the agent still isn't ours after the pull.
14408
14053
  async recoverRacedAgent(wr, payload) {
14409
14054
  if (!this.deviceId || payload.deviceId !== this.deviceId) return void 0;
14410
14055
  if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
@@ -14445,9 +14090,8 @@ var CompanionSupervisor = class {
14445
14090
  };
14446
14091
  const tail = this.runDispatch(
14447
14092
  wr,
14448
- // No frame delivered this one — it came off `/runs` — so there is no
14449
- // dispatch id to dedupe on. The `recoveredTurns` set above is this
14450
- // path's own guard.
14093
+ // Off `/runs`, not a frame, so no dispatch id to dedupe on;
14094
+ // `recoveredTurns` is this path's guard.
14451
14095
  "",
14452
14096
  payload,
14453
14097
  agent,
@@ -14469,9 +14113,7 @@ var CompanionSupervisor = class {
14469
14113
  this.recovering = false;
14470
14114
  }
14471
14115
  }
14472
- // One turn: the SJ516 F7 / CT26 skip-vs-resume dedupe, the dispatch, then
14473
- // the console summary. Mirrors the old per-workspace `handleEvent`, now
14474
- // scoped to one agent.
14116
+ // One turn: the skip-vs-resume dedupe, the dispatch, then the console summary.
14475
14117
  async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId) {
14476
14118
  const workspaceId = wr.workspaceId;
14477
14119
  if (dispatchId && hasDispatched(workspaceId, dispatchId)) {
@@ -14556,7 +14198,7 @@ var CompanionSupervisor = class {
14556
14198
  );
14557
14199
  }
14558
14200
  }
14559
- // ---- per-agent outbox drain scheduler (CT25) ----
14201
+ // ---- per-agent outbox drain scheduler ----
14560
14202
  makeDrain(api, agentId) {
14561
14203
  let drainTimer;
14562
14204
  let drainDelay = DRAIN_BASE_MS;
@@ -14625,19 +14267,16 @@ var CompanionSupervisor = class {
14625
14267
  saveConfig(next);
14626
14268
  configureLogger(this.log, next);
14627
14269
  }
14628
- // ---- harnesses (CT586) ----
14629
- // Probe all three harnesses, cache the signals + versions, and push the derived
14630
- // per-harness snapshot to the hub (which flips the Companion's Harnesses surface
14631
- // over SSE). Fail-soft by construction — `probeHarnessSignals` never throws — but
14632
- // wrapped anyway so a harness refresh can never take a heartbeat down. Called on
14633
- // the heartbeat cadence (fresh presence for the manifest + UI) and on demand.
14270
+ // ---- harnesses ----
14271
+ // Probe all three harnesses, cache signals and versions, and push the snapshot
14272
+ // to the hub. `probeHarnessSignals` never throws, but this is wrapped anyway so
14273
+ // a refresh can never take a heartbeat down.
14634
14274
  async refreshHarnessStatuses(presence = {}) {
14635
14275
  try {
14636
14276
  const signals = await probeHarnessSignals(this.config, {
14637
14277
  probePresence: this.probePresence,
14638
14278
  presence,
14639
- // CT1379: the bundled executables are resolved on the same cadence as the
14640
- // CLI probes, through the supervisor's seam so a test pins the host fact.
14279
+ // Through the supervisor's seam, so a test pins the host fact.
14641
14280
  resolveBundled: this.resolveBundled
14642
14281
  });
14643
14282
  this.harnessSignals = signals;
@@ -14660,11 +14299,10 @@ var CompanionSupervisor = class {
14660
14299
  async recheckHarnesses() {
14661
14300
  await this.refreshHarnessStatuses();
14662
14301
  }
14663
- // CT1085 §1 step 3: beat NOW and wait for it to land. `start` calls this
14664
- // between bringing the runtime up and asking the person anything, so the
14665
- // browser's connect step is already showing what this machine has ("your
14666
- // terminal is asking") rather than sitting blank while the terminal blocks on
14667
- // an answer. Coalesces with an in-flight beat rather than stacking a second.
14302
+ // Beat NOW and wait for it to land. `start` calls this before asking the
14303
+ // person anything, so the browser's connect step already shows what this
14304
+ // machine has rather than sitting blank while the terminal waits on an answer.
14305
+ // Coalesces with an in-flight beat.
14668
14306
  async heartbeatNow() {
14669
14307
  if (this.inFlightHeartbeat) {
14670
14308
  await this.inFlightHeartbeat;
@@ -14673,35 +14311,25 @@ var CompanionSupervisor = class {
14673
14311
  this.kickHeartbeat();
14674
14312
  if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
14675
14313
  }
14676
- // CT1085: the config as it stands right now — after any `enableHarness` write.
14677
- // The control socket's connect handler needs it to run the shake-out check
14678
- // against the URL/flag that was just persisted, not the one this process booted
14679
- // with.
14314
+ // The config as it stands now, after any `enableHarness` write: the control
14315
+ // socket's shake-out check needs what was just persisted, not the boot config.
14680
14316
  currentConfig() {
14681
14317
  return this.config;
14682
14318
  }
14683
- // Friendly enable for the config-driven harnesses — flip the flag the app owns in
14684
- // `~/.cabane/config.json`, no hand-edited JSON. It never installs a binary and
14685
- // never drives a login (BYO — Decided).
14319
+ // Enable a config-driven harness by flipping the flag the app owns in
14320
+ // `~/.cabane/config.json`. Never installs a binary or drives a login (BYO).
14686
14321
  //
14687
- // CT1379: this records INTENT; it does not by itself expose anything. A harness
14688
- // is advertised only when the user asked for it AND the SDK's own bundled binary
14689
- // resolves, so a successful enable on a companion whose optional dependency
14690
- // never installed leaves the device advertising nothing — correctly, and with
14691
- // the Harnesses surface saying why. Read every "expose" below as "record the
14692
- // half of the answer the user owns".
14693
- // - claude-code (CT1082): set `claudeCode.enabled`, after checking `claude` is
14694
- // on PATH. This is the callable the terminal offer ("We found Claude Code.
14695
- // Connect it? [Y/n]") and the web pairing flow both wire to.
14696
- // - codex: set `codex.enabled` after checking `codex` is on PATH (the CLI +
14697
- // `codex login` remain the user's).
14698
- // - opencode: set `opencode.serverUrl` — but ONLY after health-probing the URL,
14699
- // so we never advertise a serve that isn't there. An unreachable URL is a
14700
- // typed failure the UI shows in place, not a silent bad write.
14701
- // On success, persist, re-probe, and beat immediately, so a runtime that IS
14702
- // launchable starts taking turns at once rather than on the next tick — and one
14703
- // that isn't reports its issue just as promptly. Returns a typed outcome (never
14704
- // throws for a reachable/valid input).
14322
+ // This records INTENT, the user's half: a harness is advertised only when also
14323
+ // its bundled binary resolves, so an enable on a companion whose optional
14324
+ // dependency never installed advertises nothing, and the Harnesses surface
14325
+ // says why.
14326
+ // - claude-code: `claudeCode.enabled`, after a presence check. The terminal
14327
+ // offer and the web pairing flow both call this.
14328
+ // - codex: `codex.enabled`, after a presence check (login stays the user's).
14329
+ // - opencode: `opencode.serverUrl`, ONLY after health-probing the URL; an
14330
+ // unreachable one is a typed failure shown in place.
14331
+ // On success: persist, re-probe and beat at once, so the answer is true in
14332
+ // Cabane promptly either way. Never throws for a valid input.
14705
14333
  async enableHarness(input) {
14706
14334
  let next;
14707
14335
  let checkedPresence = null;
@@ -14752,9 +14380,8 @@ var CompanionSupervisor = class {
14752
14380
  return { ok: true };
14753
14381
  }
14754
14382
  // Re-create every running agent's Dispatcher from the CURRENT config, keeping
14755
- // the runner (its api/outbox/chains/SSE) intact. Used after a runtime-enabling
14756
- // config change so constructor-captured options — per-agent cwd/prepareHook and
14757
- // the adapter registry — pick up their new values without an app restart.
14383
+ // the runner (its api, outbox, chains) intact, so constructor-captured options
14384
+ // pick up a config change without a restart.
14758
14385
  rebuildDispatchers() {
14759
14386
  for (const wr of this.workspaces.values()) {
14760
14387
  for (const runner of wr.agents.values()) {
@@ -14918,8 +14545,7 @@ async function createCompanionRuntime(opts = {}) {
14918
14545
  }));
14919
14546
  await warnAboutHarnessReadiness(cfg, {
14920
14547
  probeClaude: async () => claudeCode,
14921
- // CT1085: the CLI's onboarding script owns the terminal and says this in
14922
- // its own words (the `!` block, or the per-harness offer), so it hands us a
14548
+ // The CLI's onboarding script says this in its own words, so it hands a
14923
14549
  // sink that logs instead. Every other caller keeps the stderr line.
14924
14550
  ...opts.onReadinessWarning ? { warn: opts.onReadinessWarning } : {}
14925
14551
  });
@@ -14956,11 +14582,10 @@ async function createCompanionRuntime(opts = {}) {
14956
14582
  const clearRuntimeOnExit = () => clearRuntimeStateIfOurs(instanceId);
14957
14583
  process.on("exit", clearRuntimeOnExit);
14958
14584
  const hub = new CompanionStateHub({
14959
- // CT29: one device, one base URL — the cabane instance this device is paired
14960
- // with. The dashboard's connection line shows it.
14585
+ // The dashboard's connection line shows it.
14961
14586
  baseUrl: cfg.baseUrl,
14962
14587
  companionVersion: COMPANION_VERSION,
14963
- // SJ516 F4: surfaced on `/api/status` so `stop`/`status` can confirm the
14588
+ // Surfaced on `/api/status` so `stop`/`status` can confirm the
14964
14589
  // process behind the marker pid is this companion (not a recycled pid).
14965
14590
  instanceId
14966
14591
  });
@@ -15013,7 +14638,7 @@ async function createCompanionRuntime(opts = {}) {
15013
14638
  socket: control.path,
15014
14639
  ...dashboard ? { url: dashboard.url, port: dashboard.port } : {},
15015
14640
  startedAt,
15016
- // SJ495: the daemon launcher sets this env on the detached child, so the
14641
+ // The daemon launcher sets this env on the detached child, so the
15017
14642
  // marker records whether this companion is backgrounded (foreground start
15018
14643
  // leaves it unset → false).
15019
14644
  daemon: process.env.CABANE_COMPANION_DAEMON === "1",