@cabane/companion 0.6.144 → 0.6.146

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
@@ -3719,24 +3569,18 @@ function summarizeToolInput(input) {
3719
3569
  return "";
3720
3570
  }
3721
3571
  var TOOL_IO_MAX_CHARS = 1e4;
3722
- function summarizeToolIo(input) {
3572
+ function renderToolIo(input) {
3723
3573
  if (input === void 0 || input === null) return "";
3724
- if (typeof input === "string") {
3725
- const trimmed = input.trim();
3726
- return trimmed.length > 0 ? truncate(trimmed, TOOL_IO_MAX_CHARS) : "";
3727
- }
3574
+ if (typeof input === "string") return input.trim();
3728
3575
  try {
3729
- return truncate(JSON.stringify(input), TOOL_IO_MAX_CHARS);
3576
+ return JSON.stringify(input) ?? "";
3730
3577
  } catch {
3731
3578
  return "";
3732
3579
  }
3733
3580
  }
3734
- function summarizeToolResult(content) {
3581
+ function renderToolResult(content) {
3735
3582
  if (content === void 0 || content === null) return "";
3736
- if (typeof content === "string") {
3737
- const trimmed = content.trim();
3738
- return trimmed.length > 0 ? truncate(trimmed, TOOL_IO_MAX_CHARS) : "";
3739
- }
3583
+ if (typeof content === "string") return content.trim();
3740
3584
  if (Array.isArray(content)) {
3741
3585
  const parts = [];
3742
3586
  let sawContentEntry = false;
@@ -3751,12 +3595,12 @@ function summarizeToolResult(content) {
3751
3595
  }
3752
3596
  }
3753
3597
  const joined = parts.join("\n").trim();
3754
- if (joined.length > 0) return truncate(joined, TOOL_IO_MAX_CHARS);
3598
+ if (joined.length > 0) return joined;
3755
3599
  if (sawContentEntry || content.length === 0) return "";
3756
3600
  }
3757
3601
  if (typeof content !== "object") return "";
3758
3602
  if (!Array.isArray(content) && Object.keys(content).length === 0) return "";
3759
- return summarizeToolIo(content);
3603
+ return renderToolIo(content);
3760
3604
  }
3761
3605
  function truncate(s, max) {
3762
3606
  if (s.length <= max) return s;
@@ -3773,15 +3617,19 @@ function sinkEmitter(sink, _onError) {
3773
3617
  return;
3774
3618
  case "tool":
3775
3619
  if (sink.onToolActivity) {
3620
+ const input = renderToolIo(event.input);
3621
+ const result = event.result === void 0 ? null : renderToolResult(event.result);
3776
3622
  await sink.onToolActivity({
3777
3623
  toolUseId: event.id,
3778
3624
  toolName: event.name,
3779
3625
  summary: event.summary,
3780
- // SJ529: render the raw payloads for the tool log here — the single
3781
- // truncation site (CT255 consumer concern).
3782
- input: summarizeToolIo(event.input),
3783
- result: event.result === void 0 ? "" : summarizeToolResult(event.result),
3784
- // CT496: carry the originating MCP server (null when absent / a host tool).
3626
+ input: truncate(input, TOOL_IO_MAX_CHARS),
3627
+ result: result === null ? "" : truncate(result, TOOL_IO_MAX_CHARS),
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.
3630
+ ...event.input === void 0 ? {} : { inputChars: input.length },
3631
+ ...result === null ? {} : { resultChars: result.length },
3632
+ // The originating MCP server (null when absent / a host tool).
3785
3633
  mcpServer: event.mcpServer ?? null,
3786
3634
  phase: event.phase
3787
3635
  });
@@ -3841,6 +3689,8 @@ var TurnPump = class {
3841
3689
  summary: evt.summary,
3842
3690
  input: evt.input,
3843
3691
  result: evt.result,
3692
+ ...evt.inputChars !== void 0 ? { inputChars: evt.inputChars } : {},
3693
+ ...evt.resultChars !== void 0 ? { resultChars: evt.resultChars } : {},
3844
3694
  mcpServer: evt.mcpServer,
3845
3695
  phase: evt.phase,
3846
3696
  seq
@@ -3873,22 +3723,18 @@ var TurnPump = class {
3873
3723
  // The turn's final reply text, captured for the dashboard feed / logging.
3874
3724
  finalReplyBody = null;
3875
3725
  emittedFinalSource = "none";
3876
- // CT1383: provider tool id → the persisted identity of its CURRENT call. The
3877
- // provider's id is only a lookup key: it is unique within one adapter
3878
- // invocation at best (Codex restarts at `item_0` on every invocation, and a
3879
- // resumed turn keeps its turn id), so the row and the outbox entry are keyed
3880
- // on what the pump mints — a host id for the host's `(turn_id, tool_use_id)`
3881
- // upsert, and a seq for the outbox's `(turnId, seq)` file. Both are minted
3882
- // together; minting only the id would let two calls overwrite each other's
3883
- // 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.
3884
3732
  //
3885
- // In memory on purpose: it dies with the process, and so does the runtime.
3886
- // After a crash no in-flight `done` awaits a pre-crash `start` — the resumed
3887
- // 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.
3888
3735
  toolCallByProviderId = /* @__PURE__ */ new Map();
3889
- // The callback consumer the classification (via `sinkEmitter`) — and the
3890
- // frozen committer test — drive. Each callback applies the choreography and
3891
- // 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.
3892
3738
  sink;
3893
3739
  // Whether a message has landed under this span — first words, at least. A
3894
3740
  // span that spoke settles; one that never did is a pass.
@@ -3980,7 +3826,7 @@ var TURN_CONTROL_FIELD_DESCRIPTIONS = {
3980
3826
  wake_me: {
3981
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.",
3982
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.",
3983
- 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.`
3984
3830
  },
3985
3831
  withdraw_ask: {
3986
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.",
@@ -3993,7 +3839,7 @@ var MEANINGS = {
3993
3839
  ask: "put a structured question to a person",
3994
3840
  cancel_wake: "cancel your own armed wake in this conversation",
3995
3841
  skip_turn: "end without posting a reply",
3996
- wake_me: "schedule your own return",
3842
+ wake_me: "schedule your own return; when it fires, say first what you came back to check",
3997
3843
  withdraw_ask: "withdraw an open ask in this conversation with a reason"
3998
3844
  };
3999
3845
  function turnControlPromptGuidance(prefix) {
@@ -4214,52 +4060,41 @@ function buildClaudeCodeOptions(req, augment) {
4214
4060
  // pinned `model` arrives as a bare SDK id — the host already resolved the
4215
4061
  // namespaced catalog id to it.
4216
4062
  ...model ? { model } : {},
4217
- // SJ530: surface thinking summaries — default adaptive+summarized, and fill in
4218
- // `display:'summarized'` on a configured adaptive/enabled block that omits one.
4219
- // 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']`).
4220
4066
  thinking: withThinkingSummaries(readThinking(config.runtimeOptions)),
4221
4067
  ...config.effort ? { effort: config.effort } : {},
4222
4068
  // Never merge arbitrary user `~/.claude` config; without `strictMcpConfig` the
4223
4069
  // user's existing `cabane` MCP registration could shadow ours.
4224
4070
  settingSources: [],
4225
4071
  strictMcpConfig: true,
4226
- // 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).
4227
4073
  ...devControlsAutoMemory ? {} : { settings: { autoMemoryEnabled: false } },
4228
4074
  mcpServers,
4229
4075
  ...cwd ? { cwd } : {},
4230
- // Extra env (a companion prepare hook's tokens/ports; the in-app's debug flags)
4231
- // merged OVER the inherited environment.
4076
+ // Extra env (a companion prepare hook's tokens/ports) merged over the inherited environment.
4232
4077
  //
4233
- // CT1503: and the background-tasks kill switch LAST, so nothing in
4234
- // `process.env` or `req.local.env` can turn it back on. Claude Code converts
4235
- // a foreground command that hits its Bash ceiling (600 000 ms) into a
4236
- // background task instead of erroring; the agent then reads a phantom task
4237
- // id inside a live turn. A later turn that resumed such a session was then
4238
- // seen getting every tool call aborted with kind `background` — which the CLI
4239
- // renders as "The user doesn't want to take this action right now", a
4240
- // cancellation wearing a refusal's words. The automatic conversion is
4241
- // REPRODUCED and is what this flag fixes; that the stopped task is what
4242
- // poisoned the resumed session is an INFERENCE from one incident, consistent
4243
- // with the transcript but never reproduced, and this change does not rest on
4244
- // it. `Dl()` reads this variable on the Bash tool's own call
4245
- // path and sets `canAutoBackground: false` and `turnAbortBackgrounds: false`
4246
- // 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.
4247
4087
  //
4248
- // The cost is acknowledged and deliberate: it ALSO removes deliberate
4249
- // `run_in_background` (the parameter leaves the tool schema). That is the
4250
- // only switch this harness offers, and there is no operator opt-out here on
4251
- // purpose — an escape hatch would contradict the guarantee. It is a targeted
4252
- // mitigation for the automatic conversion, NOT a cleanup mechanism: step 0
4253
- // measured that this flag changes no process-survival outcome on any turn-end
4254
- // 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.
4255
4092
  //
4256
- // Note `env` REPLACES the subprocess environment rather than merging (the
4257
- // SDK's own doc on `Options.env`), which is why `process.env` is spread here
4258
- // and why this key is now always present — previously it appeared only when
4259
- // `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.
4260
4095
  //
4261
- // CT1698: `secretEnv` joins here, and only here — the values the MCP
4262
- // 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.
4263
4098
  env: {
4264
4099
  ...process.env,
4265
4100
  ...req.local.env,
@@ -4750,8 +4585,7 @@ function makeRequest(overrides2 = {}) {
4750
4585
  systemPrompt: "system",
4751
4586
  prompt: "hi there",
4752
4587
  content: [{ type: "text", text: "hi there" }],
4753
- // CT302: model is required — the adapter rejects a null model, so the
4754
- // conformance fixture carries a real bare SDK id.
4588
+ // The adapter rejects a null model, so the fixture carries a real bare SDK id.
4755
4589
  config: { model: "claude-opus-4-8" },
4756
4590
  policy: CABANE_POLICY,
4757
4591
  session: null,
@@ -4864,7 +4698,7 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4864
4698
  phase: "start",
4865
4699
  summary: "sleep.md",
4866
4700
  input: { path: "sleep.md" },
4867
- // CT496: derived off the raw `mcp__cabane__` name.
4701
+ // Derived from the raw `mcp__cabane__` name.
4868
4702
  mcpServer: "cabane"
4869
4703
  },
4870
4704
  {
@@ -4882,9 +4716,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4882
4716
  ]
4883
4717
  },
4884
4718
  {
4885
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result;
4886
- // the adapter stops before the closing reply — no final text, no `result`
4887
- // 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).
4888
4722
  name: "cancel mid-stream",
4889
4723
  request: makeRequest(),
4890
4724
  nativeStream: [
@@ -4936,12 +4770,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4936
4770
  ]
4937
4771
  },
4938
4772
  {
4939
- // CT1144: the empty-runtime anomaly's exact captured trace — `system/init`
4940
- // then a successful `result`, with nothing in between. Distinct from
4941
- // `empty-final` below: THAT turn did work (a tool ran) and just didn't speak;
4942
- // this one produced nothing at all. The adapter's job is unchanged either way
4943
- // — report faithfully what the runtime said — so it must still emit
4944
- // `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
4945
4776
  // content-bearing event is a failed turn.
4946
4777
  name: "zero-content success (empty runtime result)",
4947
4778
  request: makeRequest(),
@@ -4949,9 +4780,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
4949
4780
  expected: [sessionEvent("s1"), { type: "result", ok: true }]
4950
4781
  },
4951
4782
  {
4952
- // CT1220: on resume, a completed background task can emit a notification and
4953
- // its own zero-token result before the prompt's frames. That result is not the
4954
- // 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.
4955
4786
  name: "task-notification result does not end a resumed turn",
4956
4787
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
4957
4788
  nativeStream: [
@@ -5027,8 +4858,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5027
4858
  ]
5028
4859
  },
5029
4860
  {
5030
- // CT1220: the filter is deliberately narrow. An origin-less zero-token result
5031
- // 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.
5032
4863
  name: "origin-less result still ends a resumed turn",
5033
4864
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5034
4865
  nativeStream: [
@@ -5099,11 +4930,10 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5099
4930
  ]
5100
4931
  },
5101
4932
  {
5102
- // CT558/CT592/CT731: a subscription cap. The SDK emits a rejected
5103
- // `rate_limit_event` with a named subscription window; the terminal error result
5104
- // then classifies as the structured `usage_capped` reason. The held provider
5105
- // line independently classifies to the same kind, so it lands as a runtime
5106
- // 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.
5107
4937
  name: "subscription cap \u2192 usage_capped",
5108
4938
  request: makeRequest(),
5109
4939
  nativeStream: [
@@ -5122,9 +4952,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5122
4952
  ]
5123
4953
  },
5124
4954
  {
5125
- // CT1336: a failed turn can still contain the agent's own partial narration.
5126
- // No text classification means no authorship rewrite, even though the
5127
- // 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.
5128
4957
  name: "partial narration before subscription cap stays progress",
5129
4958
  request: makeRequest(),
5130
4959
  nativeStream: [
@@ -5140,8 +4969,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5140
4969
  ]
5141
4970
  },
5142
4971
  {
5143
- // CT1336: even recognized runtime prose stays authored when its kind
5144
- // disagrees with the structured terminal failure.
4972
+ // Recognized runtime prose stays authored when its kind disagrees with the structured terminal
4973
+ // failure.
5145
4974
  name: "cap notice before server failure stays progress",
5146
4975
  request: makeRequest(),
5147
4976
  nativeStream: [
@@ -5163,10 +4992,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5163
4992
  ]
5164
4993
  },
5165
4994
  {
5166
- // CT1336 review: `classifyErrorText` intentionally searches arbitrary error
5167
- // blobs for status tokens. Assistant narration needs the stricter whole-
5168
- // notice classifier, or an incidental count is de-authored when the turn
5169
- // 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.
5170
4998
  name: "incidental 5xx-shaped narration before server failure stays progress",
5171
4999
  request: makeRequest(),
5172
5000
  nativeStream: [
@@ -5188,9 +5016,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5188
5016
  ]
5189
5017
  },
5190
5018
  {
5191
- // CT558/CT592: a cap signalled only by the result's
5192
- // `terminal_reason:'blocking_limit'` (no discrete rate-limit event) still
5193
- // 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`.
5194
5021
  name: "blocking_limit terminal reason \u2192 usage_capped",
5195
5022
  request: makeRequest(),
5196
5023
  nativeStream: [
@@ -5204,8 +5031,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5204
5031
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "usage_capped" }]
5205
5032
  },
5206
5033
  {
5207
- // CT558: an expired login surfaces as an `auth_status` error frame; the failing
5208
- // result classifies as `auth_expired`.
5034
+ // An expired login surfaces as an `auth_status` error frame; the failing result classifies as
5035
+ // `auth_expired`.
5209
5036
  name: "expired login \u2192 auth_expired",
5210
5037
  request: makeRequest(),
5211
5038
  nativeStream: [
@@ -5219,10 +5046,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5219
5046
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "auth_expired" }]
5220
5047
  },
5221
5048
  {
5222
- // CT592: a transient provider outage surfaced in the terminal result text (no
5223
- // structured event/enum) — a 529 overload — classifies as `server_error` via
5224
- // the text fallback. It must NOT read as the cap and must not stamp the
5225
- // 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`.
5226
5052
  name: "provider overload text \u2192 server_error",
5227
5053
  request: makeRequest(),
5228
5054
  nativeStream: [
@@ -5235,11 +5061,9 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5235
5061
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "server_error" }]
5236
5062
  },
5237
5063
  {
5238
- // CT601: the resolved model is captured off `system/init.model` and attached
5239
- // to the terminal `result` — even on a plain resume (this fixture resumes, so
5240
- // no `session` event fires, proving the model doesn't ride the session event).
5241
- // The configured request model is irrelevant here; what's asserted is that the
5242
- // 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.
5243
5067
  name: "resolved model captured from system/init",
5244
5068
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5245
5069
  nativeStream: [init("s1", "claude-opus-4-8"), assistantText("Done."), resultSuccess("s1")],
@@ -5249,9 +5073,8 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5249
5073
  ]
5250
5074
  },
5251
5075
  {
5252
- // Session resume echo: the stored state matches the current cwd, so we resume
5253
- // its SDK id; `system/init` echoes that same id, so NO session event fires
5254
- // (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.
5255
5078
  name: "session resume (no re-emit on echo)",
5256
5079
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
5257
5080
  nativeStream: [init("s1"), assistantText("Back again."), resultSuccess("s1")],
@@ -5261,16 +5084,14 @@ var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
5261
5084
  ]
5262
5085
  },
5263
5086
  {
5264
- // SJ527 cwd-mismatch degrade: the stored state was created under a DIFFERENT
5265
- // cwd, so the adapter refuses to resume and starts fresh — `system/init`
5266
- // 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.
5267
5089
  name: "session cwd-mismatch degrades to fresh",
5268
5090
  request: makeRequest({ session: encodeSession({ sdkSessionId: "old", cwd: "/env/gone" }) }),
5269
5091
  nativeStream: [init("fresh1"), assistantText("Fresh start."), resultSuccess("fresh1")],
5270
5092
  expected: [
5271
- // CT315: the degrade is flagged on the session event so the host relays it
5272
- // on settle and the server rewinds the catch-up mark (recovering the
5273
- // 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).
5274
5095
  sessionEvent("fresh1", CWD, true),
5275
5096
  { type: "text", body: "Fresh start.", terminal: true },
5276
5097
  { type: "result", ok: true }
@@ -5913,8 +5734,6 @@ function createOpencodeAdapter(deps = {}) {
5913
5734
  const transport = deps.transport ?? (deps.serverUrl ? createHttpOpencodeTransport({ baseUrl: deps.serverUrl }) : null);
5914
5735
  return {
5915
5736
  name: "opencode",
5916
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
5917
- // argument went with the named tool surface it selected against.
5918
5737
  promptAddendum: () => OPENCODE_ADDENDUM,
5919
5738
  dialectSchema: opencodeDialectSchema,
5920
5739
  async *runTurn(request, signal, hooks) {
@@ -6077,15 +5896,12 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6077
5896
  ]
6078
5897
  },
6079
5898
  {
6080
- // CT301: a STREAMING provider's tool input arrives late. opencode re-emits the
6081
- // tool part as a full snapshot as the args parse: DeepSeek sends `input: {}` on
6082
- // the early `pending`/`running` frame and the full args only on a later snapshot
6083
- // (live-captured against opencode 1.17: `pending {}` → `running {full}` →
6084
- // `completed {full}`). The `start` event fires immediately off the first (empty)
6085
- // frame — that's the live card — but the `done` event (and the DB row it
6086
- // upserts) must carry the LATEST non-empty input + its summary, not the
6087
- // first-seen `{}`. This is the case the pre-CT301 suite structurally couldn't
6088
- // 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.
6089
5905
  name: "streaming tool input \u2014 late args refresh the done event (CT301)",
6090
5906
  request: makeRequest2(),
6091
5907
  nativeStream: [
@@ -6123,9 +5939,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6123
5939
  ]
6124
5940
  },
6125
5941
  {
6126
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
6127
- // adapter stops before the closing reply — no final text, no `result` event.
6128
- // 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).
6129
5944
  name: "cancel mid-stream",
6130
5945
  request: makeRequest2(),
6131
5946
  nativeStream: [
@@ -6165,10 +5980,9 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6165
5980
  ]
6166
5981
  },
6167
5982
  {
6168
- // CT558/CT592: a provider 429 throttle. The wrapped `RateLimitError` is
6169
- // unwrapped by `readSessionError` then text-classified — a bare "rate limit
6170
- // (429)" with no cap noun is the throttle (`rate_limited`), distinct from the
6171
- // 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.
6172
5986
  name: "provider 429 \u2192 rate_limited (throttle, no reset)",
6173
5987
  request: makeRequest2(),
6174
5988
  nativeStream: [
@@ -6184,9 +5998,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6184
5998
  ]
6185
5999
  },
6186
6000
  {
6187
- // CT592: a subscription cap surfaced as text ("usage limit reached") → the cap
6188
- // (`usage_capped`). opencode carries no reset hint, so it's reset-less (the
6189
- // 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").
6190
6003
  name: "usage cap text \u2192 usage_capped (no reset)",
6191
6004
  request: makeRequest2(),
6192
6005
  nativeStream: [
@@ -6202,9 +6015,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6202
6015
  ]
6203
6016
  },
6204
6017
  {
6205
- // CT592: a transient provider outage (5xx / overload) surfaced as text → the
6206
- // honest `server_error`, never the cap. This is the outage class that used to
6207
- // fall through to an opaque `unknown`.
6018
+ // A transient provider outage (5xx / overload) surfaced as text → `server_error`, never the cap.
6208
6019
  name: "provider overload text \u2192 server_error",
6209
6020
  request: makeRequest2(),
6210
6021
  nativeStream: [
@@ -6222,7 +6033,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6222
6033
  ]
6223
6034
  },
6224
6035
  {
6225
- // CT558: an expired/invalid credential classifies as `auth_expired`.
6036
+ // An expired or invalid credential classifies as `auth_expired`.
6226
6037
  name: "auth failure \u2192 auth_expired",
6227
6038
  request: makeRequest2(),
6228
6039
  nativeStream: [
@@ -6238,8 +6049,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6238
6049
  ]
6239
6050
  },
6240
6051
  {
6241
- // CT1351: an unclassified failure remains as informative as a classified one:
6242
- // 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.
6243
6054
  name: "unclassified failure keeps a runtime notice and raw reason",
6244
6055
  request: makeRequest2(),
6245
6056
  nativeStream: [errored("something local broke")],
@@ -6260,8 +6071,8 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6260
6071
  ]
6261
6072
  },
6262
6073
  {
6263
- // CT1351: empty, whitespace-only, and bare status-token failures still set
6264
- // 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.
6265
6076
  name: "empty failure text emits no runtime notice",
6266
6077
  request: makeRequest2(),
6267
6078
  nativeStream: [errored("")],
@@ -6293,12 +6104,10 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6293
6104
  ]
6294
6105
  },
6295
6106
  {
6296
- // CT1144: the same zero-content success the claude-code suite pins, in
6297
- // opencode's vocabulary — the session goes idle having emitted no part at
6298
- // all. No opencode sample of the anomaly has been seen in the wild; the
6299
- // fixture exists because the host's classification is runtime-neutral, so a
6300
- // runtime that CAN produce this envelope must be shown producing it in the
6301
- // 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.
6302
6111
  name: "zero-content success (empty runtime result)",
6303
6112
  request: makeRequest2(),
6304
6113
  nativeStream: [idle()],
@@ -6355,10 +6164,9 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6355
6164
  ]
6356
6165
  },
6357
6166
  {
6358
- // CT298: the stored state matches the current directory, so the adapter resumes
6359
- // its id and the transport reports `created:false` on the same id — NO session
6360
- // event fires (the id didn't change). This is the resume-echo case CT294
6361
- // 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.
6362
6170
  name: "session resume (no re-emit on echo)",
6363
6171
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
6364
6172
  nativeStream: [textPart("p1", "Back again."), idle()],
@@ -6368,12 +6176,10 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6368
6176
  ]
6369
6177
  },
6370
6178
  {
6371
- // CT298: on a resumed turn the transport mints NO client message id (supplying
6372
- // one no-ops the turn), so the decoder can't be handed a `skipMessageId` up
6373
- // front. It learns the user-message id from the stream — the `message.updated`
6374
- // user frame precedes that message's echoed part — and drops the echo, so the
6375
- // injected per-turn context still can't leak into Steps. Only the assistant
6376
- // 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.
6377
6183
  name: "session resume \u2014 prompt echo filtered via stream-derived user id",
6378
6184
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
6379
6185
  nativeStream: [
@@ -6389,17 +6195,15 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
6389
6195
  ]
6390
6196
  },
6391
6197
  {
6392
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was
6393
- // created under a DIFFERENT directory, so the adapter refuses to resume and
6394
- // starts fresh — the transport creates a new session, and the emitted session
6395
- // 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.
6396
6201
  name: "session directory-mismatch degrades to fresh",
6397
6202
  request: makeRequest2({ session: encodeSession2({ sessionId: "old", directory: "/env/gone" }) }),
6398
6203
  nativeStream: [textPart("p1", "Fresh start."), idle()],
6399
6204
  expected: [
6400
- // CT315: the degrade is flagged on the session event (parity with
6401
- // claude-code) so the host relays it on settle and the server rewinds the
6402
- // 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.
6403
6207
  sessionEvent2(NEW_SESSION_ID, DIR, true),
6404
6208
  { type: "text", body: "Fresh start.", terminal: true },
6405
6209
  { type: "result", ok: true }
@@ -6417,11 +6221,9 @@ var CODEX_ADDENDUM = [
6417
6221
  "absent without attempting discovery and invocation, and if a call fails, report the recorded",
6418
6222
  "error.",
6419
6223
  turnControlPromptGuidance("mcp__cabane_companion__"),
6420
- // CT1552: every ancillary tool is written the way Codex's own tool list shows
6421
- // it — qualified. The bare `read_binary` / `upload` / … this list used to mix
6422
- // in sat two lines under the instruction to invoke the exact qualified names,
6423
- // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
6424
- // 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.
6425
6227
  "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
6426
6228
  "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
6427
6229
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
@@ -6665,10 +6467,9 @@ async function* decodeCodexStream(events, ctx) {
6665
6467
  threadId,
6666
6468
  cwd: ctx.cwd ?? "",
6667
6469
  promptFingerprint: ctx.promptFingerprint ?? null,
6668
- // CT1645: the baseline this turn INHERITED, carried through unchanged.
6669
- // This early write exists so a turn that dies mid-stream still leaves
6670
- // the thread id recorded; it cannot yet know what this turn will spend.
6671
- // 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.
6672
6473
  usageBaseline: ctx.usageBaseline ?? null
6673
6474
  });
6674
6475
  if (threadId !== ctx.resumedThreadId || state !== ctx.storedState) {
@@ -6952,9 +6753,8 @@ function buildConfig(req, baseInstructionsFile) {
6952
6753
  return {
6953
6754
  mcp_servers,
6954
6755
  experimental_use_rmcp_client: true,
6955
- // CT871: the no-host-access prompt replacement — Cabane's composed prompt
6956
- // becomes Codex's base instructions, and Codex's own environment preamble goes
6957
- // 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.
6958
6758
  ...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
6959
6759
  ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
6960
6760
  };
@@ -7051,16 +6851,11 @@ function createSdkCodexTransport(opts = {}) {
7051
6851
  const codexOptions = {
7052
6852
  ...opts.apiKey ? { apiKey: opts.apiKey } : {},
7053
6853
  ...opts.codexPathOverride ? { codexPathOverride: opts.codexPathOverride } : {},
7054
- // The `--config` overrides (MCP servers + rmcp flag). CT1501: rendered to
7055
- // finished `--config` strings by US and passed through `configOverrides`,
7056
- // the SDK's raw passthrough — NOT through `config`, which would flatten
7057
- // them itself. A user MCP server named `my.server` cannot go in an
7058
- // override's dotted path at all (the CLI splits the path on `.` and
7059
- // ignores TOML quoting), so it has to ride inside an inline-table value,
7060
- // and the SDK's flattener cannot be steered into producing one. This is
7061
- // also what makes the PUBLISHED companion correct, where the vendored
7062
- // patch never reached — and why that patch is gone. See
7063
- // `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`.
7064
6859
  configOverrides: buildConfigOverrides(spec.config),
7065
6860
  ...spec.secretEnv ? { env: codexProcessEnv(spec.secretEnv) } : {}
7066
6861
  };
@@ -7078,8 +6873,6 @@ function createCodexAdapter(deps = {}) {
7078
6873
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
7079
6874
  return {
7080
6875
  name: "codex",
7081
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
7082
- // argument went with the named tool surface it selected against.
7083
6876
  promptAddendum: () => CODEX_ADDENDUM,
7084
6877
  dialectSchema: codexDialectSchema,
7085
6878
  async *runTurn(request, signal, hooks) {
@@ -7171,17 +6964,14 @@ function createCodexAdapter(deps = {}) {
7171
6964
  cwd: req.local.cwd,
7172
6965
  resumedThreadId: resumeThreadId,
7173
6966
  degraded,
7174
- // CT1075: which prompt the thread carries once this turn lands. If we sent
7175
- // one, it's that one — a later copy is what the model reads, so a changed
7176
- // prompt supersedes the older copy still sitting above it. If we withheld
7177
- // it, the thread still carries whatever it did before. Read off the spec
7178
- // rather than recomputed, so the recorded fact can't disagree with what
7179
- // 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.
7180
6970
  promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
7181
6971
  storedState: req.session,
7182
6972
  usageBaseline: threadUsageBaseline,
7183
- // CT601: Codex reports no model in-stream, so record what the thread was
7184
- // 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.
7185
6975
  resolvedModel: spec.model,
7186
6976
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
7187
6977
  });
@@ -7230,11 +7020,9 @@ function makeRequest3(overrides2 = {}) {
7230
7020
  systemPrompt: PROMPT,
7231
7021
  prompt: "hi there",
7232
7022
  content: [{ type: "text", text: "hi there" }],
7233
- // CT601: the resolved model + reasoning effort ride the terminal `result` off
7234
- // the run spec's model. The behavioral fixtures below don't assert them, so the
7235
- // default is the model-less ("let Codex choose") token → no `resolvedModel`
7236
- // emitted → their expected results stay unchanged; a dedicated capture fixture
7237
- // 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.
7238
7026
  config: { model: null },
7239
7027
  policy: COMPANION_POLICY2,
7240
7028
  session: null,
@@ -7286,13 +7074,12 @@ var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint =
7286
7074
  });
7287
7075
  var CODEX_CONFORMANCE_FIXTURES = [
7288
7076
  {
7289
- // A plain text reply: the held agent message seals as the terminal final at
7290
- // `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`.
7291
7079
  //
7292
- // CT1283: the counters NEST — `cached_input_tokens` is a slice of
7293
- // `input_tokens`, `reasoning_output_tokens` a slice of `output_tokens` — so
7294
- // input is 100 (not 120) and output is 50 (not 60), with the cache read broken
7295
- // 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.
7296
7083
  name: "clean turn",
7297
7084
  request: makeRequest3(),
7298
7085
  nativeStream: [
@@ -7309,10 +7096,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7309
7096
  expected: [
7310
7097
  sessionEvent3(NEW_THREAD_ID),
7311
7098
  { type: "text", body: "Hello there.", terminal: true },
7312
- // CT1645: the closing session write — the thread's ending totals, recorded
7313
- // as the baseline the NEXT turn subtracts. This is a fresh thread, so the
7314
- // `result` usage below is the frame verbatim (nothing to subtract yet);
7315
- // 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.
7316
7101
  sessionEvent3(NEW_THREAD_ID, DIR2, false, fingerprintPrompt(PROMPT), {
7317
7102
  inputTokens: 100,
7318
7103
  outputTokens: 50,
@@ -7384,8 +7169,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7384
7169
  ]
7385
7170
  },
7386
7171
  {
7387
- // CT1211: recorded MCP success shape. Codex exposes the text compatibility
7388
- // 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.
7389
7174
  name: "MCP success prefers native structured content",
7390
7175
  request: makeRequest3(),
7391
7176
  nativeStream: [
@@ -7435,8 +7220,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7435
7220
  ]
7436
7221
  },
7437
7222
  {
7438
- // CT1211 incident shape: MCP `isError` keeps the failed phase, while the raw
7439
- // 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.
7440
7225
  name: "failed MCP call preserves structured receipt while remaining error phase",
7441
7226
  request: makeRequest3(),
7442
7227
  nativeStream: [
@@ -7488,8 +7273,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7488
7273
  ]
7489
7274
  },
7490
7275
  {
7491
- // A transport/tool failure has no MCP result payload and still uses the real
7492
- // 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`.
7493
7278
  name: "failed MCP call without result keeps error message fallback",
7494
7279
  request: makeRequest3(),
7495
7280
  nativeStream: [
@@ -7529,8 +7314,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7529
7314
  ]
7530
7315
  },
7531
7316
  {
7532
- // CT1147 stays intact when a connector includes structured metadata beside an
7533
- // 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.
7534
7319
  name: "image-only MCP result remains empty despite structured metadata",
7535
7320
  request: makeRequest3(),
7536
7321
  nativeStream: [
@@ -7623,12 +7408,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
7623
7408
  ]
7624
7409
  },
7625
7410
  {
7626
- // CT715: web_search LIFECYCLE. Unlike the other three tool kinds, a `web_search`
7627
- // item carries NO `status` field — completion is signaled by the frame TYPE
7628
- // (`item.started` → `item.completed`). The `start` fires off the first frame
7629
- // (empty query, no output card); the `done` must resolve off `item.completed`
7630
- // and carry the populated query the completed frame filled in. Before CT715 the
7631
- // 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".
7632
7415
  name: "web_search lifecycle \u2014 completes off frame type, populated query on done",
7633
7416
  request: makeRequest3(),
7634
7417
  nativeStream: [
@@ -7659,9 +7442,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7659
7442
  ]
7660
7443
  },
7661
7444
  {
7662
- // CT715: a non-text web action (`action.type: "other"`) legitimately completes
7663
- // with an EMPTY query — that's Codex's own data, not our bug. It must still
7664
- // 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`.
7665
7447
  name: "web_search lifecycle \u2014 empty-query completion still resolves to done",
7666
7448
  request: makeRequest3(),
7667
7449
  nativeStream: [
@@ -7692,9 +7474,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7692
7474
  ]
7693
7475
  },
7694
7476
  {
7695
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
7696
- // adapter stops before the closing reply — no final text, no `result` event.
7697
- // 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).
7698
7479
  name: "cancel mid-stream",
7699
7480
  request: makeRequest3(),
7700
7481
  nativeStream: [
@@ -7775,11 +7556,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7775
7556
  ]
7776
7557
  },
7777
7558
  {
7778
- // CT592: Codex used to classify NOTHING — every `turn.failed` became a raw
7779
- // `error:<text>` `unknown`. It now routes the failure text through the shared
7780
- // classifier (Codex is text-only), so a provider 429 / outage / auth / cap
7781
- // failure splits into the honest taxonomy instead of all reading `unknown`.
7782
- // 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:
7783
7561
  name: "turn.failed 429 \u2192 rate_limited (throttle)",
7784
7562
  request: makeRequest3(),
7785
7563
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
@@ -7826,8 +7604,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7826
7604
  ]
7827
7605
  },
7828
7606
  {
7829
- // CT592: a Codex failure text the classifier can't place still keeps today's
7830
- // 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).
7831
7609
  name: "turn.failed unclassified \u2192 raw error reason (unchanged)",
7832
7610
  request: makeRequest3(),
7833
7611
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
@@ -7851,8 +7629,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7851
7629
  ]
7852
7630
  },
7853
7631
  {
7854
- // CT1351: empty, whitespace-only, and bare status-token failures still set
7855
- // 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.
7856
7634
  name: "empty failure text emits no runtime notice",
7857
7635
  request: makeRequest3(),
7858
7636
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
@@ -7881,11 +7659,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7881
7659
  ]
7882
7660
  },
7883
7661
  {
7884
- // CT481: fail LOUD on unknown model metadata. An unknown `--model` makes Codex
7885
- // emit an `error` item ("… Defaulting to fallback metadata …") and then go
7886
- // haywire (the spurious tool call scripted after it MUST never be emitted). The
7887
- // adapter ends the turn the moment the signal lands, with a machine reason — so
7888
- // 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.
7889
7665
  name: "unknown model metadata \u2192 fail loud (guard)",
7890
7666
  request: makeRequest3(),
7891
7667
  nativeStream: [
@@ -7931,12 +7707,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
7931
7707
  ]
7932
7708
  },
7933
7709
  {
7934
- // CT1144: the same zero-content success the claude-code suite pins, in
7935
- // Codex's vocabulary — the thread starts, the turn completes, and nothing is
7936
- // emitted in between. No Codex sample of the anomaly has been seen in the
7937
- // wild; the fixture exists because the host's classification is runtime-
7938
- // neutral, so a runtime that CAN produce this envelope must be shown
7939
- // 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.
7940
7714
  name: "zero-content success (empty runtime result)",
7941
7715
  request: makeRequest3(),
7942
7716
  nativeStream: [threadStarted(NEW_THREAD_ID), turnCompleted()],
@@ -8005,11 +7779,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
8005
7779
  ]
8006
7780
  },
8007
7781
  {
8008
- // CT601: Codex reports no model in-stream, so the resolved model + reasoning
8009
- // effort come from what the thread was STARTED with (the run spec) and ride the
8010
- // terminal `result`. A real model + a `runtimeOptions.codex.modelReasoningEffort`
8011
- // are asserted through to `resolvedModel` (bare, `openai/`-stripped) and
8012
- // `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`.
8013
7786
  name: "resolved model + reasoning effort captured from run spec",
8014
7787
  request: makeRequest3({
8015
7788
  config: {
@@ -8030,15 +7803,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8030
7803
  ]
8031
7804
  },
8032
7805
  {
8033
- // Session resume: the stored state matches the current directory, so the adapter
8034
- // resumes its thread id and `thread.started` echoes the SAME id — NO session
8035
- // event fires (the id didn't change AND the state is unchanged). This is what
8036
- // 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.
8037
7808
  //
8038
- // CT1075: the stored state also says the thread already holds the composed
8039
- // prompt, so this turn withholds the preamble and the state it would write is
8040
- // byte-identical to the stored one. The silence is now proof of BOTH: an id that
8041
- // 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.
8042
7812
  name: "session resume (no re-emit on echo)",
8043
7813
  request: makeRequest3({
8044
7814
  session: encodeSession3({
@@ -8055,16 +7825,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8055
7825
  ]
8056
7826
  },
8057
7827
  {
8058
- // CT1075: a resumed thread the platform's state does NOT vouch for — a state
8059
- // written before CT1075 existed, so it carries no `promptFingerprint` field. The
8060
- // adapter reads the absence as "we can't say which prompt this thread holds",
8061
- // sends the full prompt exactly as it did pre-CT1075, and re-emits the state with
8062
- // the fingerprint recorded — on the SAME thread id, which pre-CT1075 emitted
8063
- // nothing at all. That re-emit is the upgrade path: without it a long-lived
8064
- // 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.
8065
7832
  //
8066
- // This is the fixture that fails if the static half is withheld on a thread we
8067
- // have no evidence about.
7833
+ // This fixture fails if the standing half is withheld on a thread we have no evidence about.
8068
7834
  name: "session resume with a pre-CT1075 state re-emits with the prompt fingerprint",
8069
7835
  request: makeRequest3({
8070
7836
  session: JSON.stringify({ threadId: "th1", cwd: DIR2 })
@@ -8077,14 +7843,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8077
7843
  ]
8078
7844
  },
8079
7845
  {
8080
- // CT1075 (review finding): the thread carries a DIFFERENT prompt than the one
8081
- // this turn composed — a charter edit, a renamed agent, a redeployed prompt
8082
- // body. The stored fingerprint doesn't match, so the new prompt is sent exactly
8083
- // 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.
8084
7849
  //
8085
- // This is the fixture that fails if a stale prompt is treated as good enough. A
8086
- // boolean flag would have passed the resume fixture above and silently withheld
8087
- // 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.
8088
7852
  name: "session resume with a stale prompt fingerprint re-sends the prompt",
8089
7853
  request: makeRequest3({
8090
7854
  session: encodeSession3({
@@ -8102,15 +7866,12 @@ var CODEX_CONFORMANCE_FIXTURES = [
8102
7866
  ]
8103
7867
  },
8104
7868
  {
8105
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was created
8106
- // under a DIFFERENT directory, so the adapter refuses to resume and starts
8107
- // fresh — `thread.started` reports a NEW id, and the emitted session event
8108
- // records the CURRENT directory with `degraded: true` so the host rewinds the
8109
- // 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.
8110
7872
  //
8111
- // CT1075: the refused resume means the new thread is EMPTY, so the full prompt
8112
- // goes in even though the stored state vouched for a copy — of a thread we're
8113
- // 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.
8114
7875
  name: "session directory-mismatch degrades to fresh",
8115
7876
  request: makeRequest3({
8116
7877
  session: encodeSession3({
@@ -8159,7 +7920,7 @@ var ConnectorHealthStore = class {
8159
7920
  lastError: outcome.errorReason ?? null
8160
7921
  });
8161
7922
  }
8162
- // 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
8163
7924
  // no observation. Read by the dispatcher at idle-timeout fire: an idle reap
8164
7925
  // while this runtime is definitively `limited` (a present, future `limitedUntil`
8165
7926
  // recorded by a sibling/earlier turn's classified cap settle) is the account cap
@@ -8463,8 +8224,7 @@ function createTurnContainment(turnId, deps = {}) {
8463
8224
  `--unit=${unit}`,
8464
8225
  `--property=TimeoutStopSec=${CONTAINMENT_STOP_GRACE_SEC}s`,
8465
8226
  // Bind the scope's life to the companion's own unit, so stopping or
8466
- // restarting the companion still takes its in-flight turns with it —
8467
- // which is what happens today, and what the scope would otherwise undo.
8227
+ // restarting the companion still takes its in-flight turns with it.
8468
8228
  // Omitted outside a unit, where there is nothing to bind to.
8469
8229
  ...ownUnit ? [`--property=PartOf=${ownUnit}`] : [],
8470
8230
  "--"
@@ -8784,7 +8544,7 @@ var mcpHttpServerSchema = z15.object({
8784
8544
  type: z15.literal("http"),
8785
8545
  url: z15.string().url(),
8786
8546
  headers: z15.record(z15.string(), z15.string()).optional(),
8787
- // 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
8788
8548
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8789
8549
  // header whose `${VAR}` it resolved.
8790
8550
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -8793,7 +8553,7 @@ var mcpSseServerSchema = z15.object({
8793
8553
  type: z15.literal("sse"),
8794
8554
  url: z15.string().url(),
8795
8555
  headers: z15.record(z15.string(), z15.string()).optional(),
8796
- // 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
8797
8557
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8798
8558
  // header whose `${VAR}` it resolved.
8799
8559
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -8806,9 +8566,8 @@ var thinkingConfigSchema = z15.discriminatedUnion("type", [
8806
8566
  ]);
8807
8567
  var effortSchema = z15.enum(["low", "medium", "high", "xhigh", "max"]);
8808
8568
  var runConfigSchema = z15.object({
8809
- // CT788: the host-access binary replaced the `assistant`/`coding`/`custom` mode
8810
- // trio + its custom tool lists — `true` grants the host filesystem/shell, absent
8811
- // 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
8812
8571
  // `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
8813
8572
  // stripped, so an older companion riding a newer server never rejects the config).
8814
8573
  hostAccess: z15.boolean().optional(),
@@ -8835,7 +8594,7 @@ var RUNNER_ENV = {
8835
8594
  dispatch: "CABANE_DISPATCH",
8836
8595
  // The turn id the launcher resolved, and whether this is a replay of a turn
8837
8596
  // an earlier process started. The seq floor for a replay is derived inside
8838
- // the turn from the outbox and the server, as it always was.
8597
+ // the turn from the outbox and the server.
8839
8598
  turnId: "CABANE_TURN_ID",
8840
8599
  resumed: "CABANE_RESUMED",
8841
8600
  // Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
@@ -8871,10 +8630,10 @@ var runnerSpecSchema = z16.object({
8871
8630
  // Whether the harness starts under the OS sandbox. The launcher sends 'os'
8872
8631
  // only when this host can make one.
8873
8632
  sandbox: z16.enum(["os", "none"]),
8874
- // CT1684: the device's sandbox policy, validated by the launcher. Absent
8633
+ // The device's sandbox policy, validated by the launcher. Absent
8875
8634
  // when the sandbox is off.
8876
8635
  sandboxPolicy: sandboxPolicySchema.optional(),
8877
- // CT1691: every companion store the launcher's latest walk found, denied
8636
+ // Every companion store the launcher's latest walk found, denied
8878
8637
  // to the harness. Absent when the sandbox is off.
8879
8638
  companionStores: z16.array(z16.string()).optional(),
8880
8639
  logLevel: z16.string(),
@@ -8883,15 +8642,15 @@ var runnerSpecSchema = z16.object({
8883
8642
  // True when this (conversation, agent) pair was already told its session
8884
8643
  // could not be saved: the notice is once per pair, not once per process.
8885
8644
  sessionWriteNoticeClaimed: z16.boolean(),
8886
- // CT1680: the runner's identity, minted with its credential. The start
8645
+ // The runner's identity, minted with its credential. The start
8887
8646
  // PATCH carries it so the lease knows which runner holds the span.
8888
8647
  runnerId: z16.string(),
8889
- // CT1681: the launcher keeps the runtime's session files beside the attempt
8648
+ // The launcher keeps the runtime's session files beside the attempt
8890
8649
  // and restores them into this runner's home, so the runner names the attempt
8891
8650
  // each context was composed for and waits for the launcher to say it's in
8892
8651
  // place. Absent: nothing to wait for (a runner on the launcher's own disk).
8893
8652
  attemptState: z16.boolean().optional(),
8894
- // CT1681: MCP `${NAME}` secrets come from the launcher, by name, instead of
8653
+ // MCP `${NAME}` secrets come from the launcher, by name, instead of
8895
8654
  // this machine's secrets.json. Absent: the runner reads the device's file.
8896
8655
  secretsFromLauncher: z16.boolean().optional()
8897
8656
  });
@@ -9520,7 +9279,7 @@ function createTurnControlMcpServer(acts, skipState) {
9520
9279
  return performAct(acts, {
9521
9280
  kind: "ask",
9522
9281
  callId: randomUUID2(),
9523
- // CT1350: the swap rides the ask, so the server commits both halves
9282
+ // The swap rides the ask, so the server commits both halves
9524
9283
  // in one transaction — the person never sees neither or both.
9525
9284
  ...args.replaces ? { replaces: args.replaces } : {},
9526
9285
  ask: hasArray ? {
@@ -9571,7 +9330,7 @@ function createTurnControlMcpServer(acts, skipState) {
9571
9330
  },
9572
9331
  { annotations: ACT_TOOL, alwaysLoad: true }
9573
9332
  ),
9574
- // CT1350: the standalone withdrawal. Not annotated destructive — an ask
9333
+ // The standalone withdrawal. Not annotated destructive — an ask
9575
9334
  // is a question, not data, and a harness that gated this on a live
9576
9335
  // operator would hang a turn nobody is watching (the `cancel_wake`
9577
9336
  // reasoning). Not idempotent either: a second call with the same id is
@@ -9590,7 +9349,7 @@ function createTurnControlMcpServer(acts, skipState) {
9590
9349
  }),
9591
9350
  { annotations: ACT_TOOL, alwaysLoad: true }
9592
9351
  ),
9593
- // CT990: the inverse. No arguments — it can only ever stand down the
9352
+ // The inverse. No arguments — it can only ever stand down the
9594
9353
  // caller's own wake in the conversation it's holding a turn in.
9595
9354
  tool(
9596
9355
  CANCEL_WAKE_TOOL,
@@ -9633,7 +9392,7 @@ function buildCompanionTurnRequest(params) {
9633
9392
  // adapter's `ResolvedMcpServers`.
9634
9393
  ...Object.keys(params.mcpServers).length > 0 ? { mcpServers: params.mcpServers } : {},
9635
9394
  ...params.secretEnv && Object.keys(params.secretEnv).length > 0 ? { secretEnv: params.secretEnv } : {},
9636
- // CT289: the auto-memory escape hatch, when the operator set it.
9395
+ // The auto-memory escape hatch, when the operator set it.
9637
9396
  ...params.claudeCode ? { claudeCode: params.claudeCode } : {}
9638
9397
  },
9639
9398
  // Host-injected turn-control tools, under their own namespace.
@@ -9887,7 +9646,7 @@ var TranscriptWriter = class {
9887
9646
  }
9888
9647
  this.line({ type: "_meta", ...meta });
9889
9648
  }
9890
- // Append a raw SDK message verbatim.
9649
+ // Append one event verbatim.
9891
9650
  write(msg) {
9892
9651
  this.line(msg);
9893
9652
  }
@@ -9895,7 +9654,7 @@ var TranscriptWriter = class {
9895
9654
  close(outcome) {
9896
9655
  this.line({ type: "_outcome", ...outcome });
9897
9656
  }
9898
- // CT1145: keep an independent copy of an empty successful envelope after the
9657
+ // Keep an independent copy of an empty successful envelope after the
9899
9658
  // footer has landed. The ordinary 200-file FIFO keeps rotating as before;
9900
9659
  // this bucket retains the newest 50 anomalies regardless of ordinary traffic.
9901
9660
  // Best-effort like every other transcript operation: observability can never
@@ -9992,6 +9751,8 @@ var TurnCommitter = class {
9992
9751
  summary,
9993
9752
  input,
9994
9753
  result,
9754
+ inputChars,
9755
+ resultChars,
9995
9756
  mcpServer,
9996
9757
  phase,
9997
9758
  seq
@@ -10007,6 +9768,8 @@ var TurnCommitter = class {
10007
9768
  summary,
10008
9769
  input,
10009
9770
  result,
9771
+ ...inputChars !== void 0 ? { inputChars } : {},
9772
+ ...resultChars !== void 0 ? { resultChars } : {},
10010
9773
  mcpServer,
10011
9774
  phase,
10012
9775
  seq,
@@ -10048,12 +9811,9 @@ var TurnCommitter = class {
10048
9811
  // Renders each `TurnEvent` onto the pump's callback sink (tool-log truncation),
10049
9812
  // routing a throwing commit to `onError`.
10050
9813
  emit;
10051
- // CT261: route one `TurnEvent` from the adapter. `text` / `tool` / `thinking`
10052
- // flow through the pump (rendered + committed); `session` / `result` are the
10053
- // dispatcher's concern and ignored here. A throwing commit (a flaky/4xx HTTP
10054
- // POST) is routed to the error hook and swallowed — a failed commit is
10055
- // warn-only and must never fail the turn (the role the classification's
10056
- // `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.
10057
9817
  async ingestEvent(event) {
10058
9818
  if (event.type === "session" || event.type === "result") return;
10059
9819
  try {
@@ -10232,18 +9992,15 @@ var TurnExecution = class {
10232
9992
  turnLog;
10233
9993
  abortController = new AbortController();
10234
9994
  outcome = initialOutcome();
10235
- // CT1503: this turn's shell containment, once the claude-code adapter is built.
10236
- // Null before that and on a turn that never reached adapter construction — a
10237
- // startup failure has nothing to reap, and reaping is a no-op rather than a
10238
- // 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.
10239
9997
  containment = null;
10240
9998
  seqCounter = 0;
10241
- // CT11: per-turn monotonic counter mirroring the in-process dispatcher's.
10242
- //
10243
- // 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.
10244
10001
  nextSeq = () => ++this.seqCounter;
10245
- // CT1380: a REPLAY, told to us by the supervisor — not derivable here, since
10246
- // `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.
10247
10004
  resumed;
10248
10005
  resumedFromSeq;
10249
10006
  // Whether the span this resumed run continues already has first words —
@@ -10251,8 +10008,8 @@ var TurnExecution = class {
10251
10008
  resumedSpoke = false;
10252
10009
  turnContext;
10253
10010
  resolvedMcpServers;
10254
- // CT1698: the harness's secret environment for this turn. Only ever handed
10255
- // 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`.
10256
10013
  secretEnv = {};
10257
10014
  effectiveCwd;
10258
10015
  hookEnv;
@@ -10266,19 +10023,15 @@ var TurnExecution = class {
10266
10023
  turnControlIntentFetched = false;
10267
10024
  applyRecordedTurnControlIntent = async () => {
10268
10025
  };
10269
- // CT1292: the committer is built before the lease watchdog arms, so the
10270
- // failed-commit hint reaches it through this slot. Safe as a no-op until the
10271
- // watchdog replaces it — the committer only fires it from inside the adapter
10272
- // 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.
10273
10029
  noteCommitFailed = () => {
10274
10030
  };
10275
10031
  disarmWatchdogs = () => {
10276
10032
  };
10277
- // CT1503: reap this turn's shell containment. Idempotent (the containment drops
10278
- // what it has already stopped) and total — a reap that fails is reported and
10279
- // swallowed, because a turn that produced a good answer must not be recorded as
10280
- // failed on account of a leftover process, and the failure is visible in the log
10281
- // 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.
10282
10035
  async reapContainment() {
10283
10036
  const containment = this.containment;
10284
10037
  if (!containment) return;
@@ -10304,20 +10057,18 @@ var TurnExecution = class {
10304
10057
  );
10305
10058
  }
10306
10059
  }
10307
- // Codo's stack review, blocking finding #2: set the moment `acquireLease`'s
10308
- // PATCH returns — the lease is granted and this is the pair's running span,
10309
- // 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
10310
10062
  // participant flag.
10311
10063
  admitted = false;
10312
- // Rooms: one recompose per turn when the lease refuses a stale context.
10064
+ // One recompose per turn when the lease refuses a stale context.
10313
10065
  recomposedForLease = false;
10314
- // CT1677: the execution source the CURRENT context was adopted as. Reassigned
10315
- // when a `dispatch_context_stale` recompose adopts a replacement, so a piece of
10316
- // work — preparation, the committer, a notice — captures it when that work
10317
- // begins and never reads it again: earlier work keeps its source. Undefined
10318
- // 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.
10319
10070
  executionSourceId;
10320
- // CT1677: one fresh fetch per turn when adoption is refused.
10071
+ // One fresh fetch per turn when adoption is refused.
10321
10072
  refetchedForAdoption = false;
10322
10073
  concluded(reason, errorReason) {
10323
10074
  return new TurnConcluded(reason, errorReason);
@@ -10385,21 +10136,18 @@ var TurnExecution = class {
10385
10136
  this.supervisor.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
10386
10137
  return { ok: false, durationMs, reason, runtime: this.turnContext?.runtime };
10387
10138
  }
10388
- // The post-admission analogue of `concludeBeforeRun` (Codo's stack review,
10389
- // blocking finding #2): this turn WAS admitted — it holds the conversation's
10390
- // lease — so the clear must NAME the turn and DECLARE its terminal outcome.
10391
- // A bare pre-run clear here would reset the participant flag while the span
10392
- // stayed running until the lost sweep. `outcome` follows the settle wire's
10393
- // rule: a failure names its reason; without one the turn settled — the
10394
- // runtime-unavailable exit posts its user-facing `final` before concluding,
10395
- // 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`).
10396
10144
  async concludeAdmittedRun(reason, errorReason) {
10397
10145
  const { payload, turnLog, startedAt, turnId } = this;
10398
10146
  const body = {
10399
10147
  activeRunStartedAt: null,
10400
10148
  turnId,
10401
- // CT1379: `runtimeIncomplete` is a real failure that deliberately carries
10402
- // 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.
10403
10151
  outcome: errorReason || this.outcome.runtimeIncomplete ? "failed" : "settled"
10404
10152
  };
10405
10153
  if (errorReason) body.errorReason = errorReason.slice(0, 200);
@@ -10493,11 +10241,10 @@ var TurnExecution = class {
10493
10241
  throw this.concluded(reason, reason);
10494
10242
  }
10495
10243
  }
10496
- // CT1677: register this context's issuance as the execution source of the
10497
- // work about to run on it. A context with no issuance has nothing to adopt,
10498
- // and that ends the turn exactly as a failed adoption does: this companion
10499
- // never commits a row whose provenance it cannot name. Only a refusal —
10500
- // 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.
10501
10248
  async adoptSource(context) {
10502
10249
  const { payload, turnId, turnLog } = this;
10503
10250
  if (!context.issuanceId) {
@@ -10546,9 +10293,8 @@ var TurnExecution = class {
10546
10293
  secretEnv,
10547
10294
  missing
10548
10295
  } = resolveMcpSecrets(
10549
- // CT262: the user MCP DEFINITIONS (placeholder form) come from the turn
10550
- // context now, not a separate `getAgentSelf` run-config fetch. CT1698: and
10551
- // 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`.
10552
10298
  this.turnContext.mcpServers ?? {},
10553
10299
  secretStore,
10554
10300
  this.turnContext.secretEnv
@@ -10661,15 +10407,12 @@ var TurnExecution = class {
10661
10407
  agentUsername: this.opts.agentUsername,
10662
10408
  runtime: this.turnContext.runtime,
10663
10409
  hostAccess: this.turnContext.policy.hostFs,
10664
- // CT317/CT319: the trigger message's referenced-entry paths — what the
10665
- // tasker prepare hook keys its per-task env off. Defaults to `[]` for
10666
- // 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.
10667
10412
  triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
10668
10413
  title: this.turnContext.conversation.title,
10669
- // CT943: the dispatching message's text — where an `env:` directive
10670
- // rides. The server has always sent the trigger body on the turn
10671
- // context (for the live feed); this is the first thing to read it as
10672
- // 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.
10673
10416
  messageBody: this.turnContext.message.body
10674
10417
  },
10675
10418
  { turnId, deviceId: this.opts.deviceId },
@@ -10715,37 +10458,30 @@ var TurnExecution = class {
10715
10458
  payload.agentId,
10716
10459
  {
10717
10460
  activeRunStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
10718
- // CT1679: a runner leases under its launcher's boot id — the one the
10719
- // 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.
10720
10463
  bootId: this.opts.inRunner?.bootId ?? bootId,
10721
- // CT1680: the runner holding the span, beside the device's boot id.
10722
10464
  ...this.opts.inRunner ? { runnerId: this.opts.inRunner.runnerId } : {},
10723
- // CT1380: the durable record that this turn resumed, and from where.
10465
+ // The durable record that this turn resumed, and from where.
10724
10466
  ...this.resumedFromSeq !== void 0 ? { resumedFromSeq: this.resumedFromSeq } : {},
10725
- // CT1454: the issuance the context THIS attempt runs on came from. Read
10726
- // off `this.turnContext`, which is the fetch that composed this request —
10727
- // so a retransmitted start echoes the same id and reuses the same record,
10728
- // and a resumed attempt echoes its own, newer one. The post-admission
10729
- // fresh-session recompose (`freshRequest`) deliberately does not touch it:
10730
- // it adopts only the prompt, never the run-config, so its issuance must
10731
- // 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.
10732
10471
  ...this.turnContext.issuanceId ? { issuanceId: this.turnContext.issuanceId } : {},
10733
- // CT33: hand the server this turn's id so the new-run chokepoint's
10734
- // `closeAbandonedTurns` sweep excludes it. The prepare hook may have
10735
- // already emitted a "preparing" activity row for this turn above (it
10736
- // runs before this flip), which would otherwise make the sweep mistake
10737
- // 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.
10738
10474
  turnId,
10739
10475
  // The newest message this turn's read covered: the lease records it,
10740
10476
  // and settle moves the cursor to it. Omitted when the server sent
10741
10477
  // none (an older API), so the cursor stays where it was.
10742
10478
  ...this.turnContext.readThrough !== void 0 ? { readThrough: this.turnContext.readThrough } : {},
10743
10479
  ...this.turnContext.readRevision !== void 0 ? { readRevision: this.turnContext.readRevision } : {},
10744
- // Rooms: the attempt this request's session belongs to (null = fresh).
10480
+ // The attempt this request's session belongs to (null = fresh).
10745
10481
  ...this.turnContext.attemptId !== void 0 ? { attemptId: this.turnContext.attemptId } : {},
10746
- // CT1543: the frame this start acts on. The turn-context fetch already
10747
- // checked it, but that was before an assembly this turn awaited — the
10748
- // 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.
10749
10485
  ...payload.episode !== void 0 ? { dispatchMessageId: payload.messageId, dispatchEpisode: payload.episode } : {}
10750
10486
  }
10751
10487
  );
@@ -10785,21 +10521,18 @@ var TurnExecution = class {
10785
10521
  this.request = buildCompanionTurnRequest({
10786
10522
  turnContext: this.turnContext,
10787
10523
  baseUrl: this.opts.baseUrl,
10788
- // ACC8: always present; `fetchTurnContext` refused a context without one.
10524
+ // Always present: `fetchContext` refuses a context without one.
10789
10525
  turnToken: this.turnContext.turnToken,
10790
- // SJ524: the hook-resolved cwd overrides the static local cwd.
10791
10526
  ...this.effectiveCwd ? { cwd: this.effectiveCwd } : {},
10792
- // CT1103: the prepare-hook env plus the authoritative host-access token.
10793
10527
  env: this.turnEnv,
10794
10528
  mcpServers: this.resolvedMcpServers,
10795
10529
  secretEnv: this.secretEnv,
10796
10530
  turnControlServer,
10797
- // CT238: this turn's conversation, forwarded as the active-conversation
10798
- // 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.
10799
10533
  activeConversationId: payload.conversationId,
10800
- // Workspace coordinate retained in the compatible TurnRequest envelope.
10801
10534
  workspaceId,
10802
- // CT289: the operator's auto-memory escape hatch (machine-local), when set.
10535
+ // The operator's machine-local auto-memory escape hatch, when set.
10803
10536
  ...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
10804
10537
  });
10805
10538
  }
@@ -10836,8 +10569,8 @@ var TurnExecution = class {
10836
10569
  if (this.opts.claudeCodeAvailable?.() ?? true) {
10837
10570
  this.containment = createTurnContainment(this.turnId, {
10838
10571
  log: (msg, meta) => turnLog.debug(meta ?? {}, msg),
10839
- // CT1679: a group outside the runner's own is reported, so a launcher
10840
- // 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.
10841
10574
  ...this.opts.inRunner?.onProcessGroup ? { onProcessGroup: this.opts.inRunner.onProcessGroup } : {}
10842
10575
  });
10843
10576
  const containment = this.containment;
@@ -10858,8 +10591,8 @@ var TurnExecution = class {
10858
10591
  const serverUrl = this.opts.opencodeServerUrl;
10859
10592
  adapters.push(
10860
10593
  createOpencodeAdapter(
10861
- // CT1679: the transport's per-server turn mutex is per process; in a
10862
- // 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.
10863
10596
  this.opts.inRunner ? {
10864
10597
  transport: this.opts.inRunner.opencodeTransport(serverUrl),
10865
10598
  onWarn
@@ -10906,7 +10639,7 @@ var TurnExecution = class {
10906
10639
  kind: "runtime_notice",
10907
10640
  turnId,
10908
10641
  parentMessageId: payload.messageId,
10909
- // CT1677: the notice is written by this attempt's source, like its speech.
10642
+ // Written by this attempt's source, like its speech.
10910
10643
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {}
10911
10644
  });
10912
10645
  } catch (postErr) {
@@ -10920,16 +10653,13 @@ var TurnExecution = class {
10920
10653
  throw this.concluded(`runtime_unavailable:${err.runtime}`);
10921
10654
  }
10922
10655
  }
10923
- // The adapter refused to resume this pair's stored session and is starting a
10924
- // fresh one. The request it holds was composed for a session that had the
10925
- // conversation so far — the read is what landed since the cursor, the
10926
- // agent's own words left out because the session held them. A fresh session
10927
- // holds none of it, so fetch the context again as a first turn on an empty
10928
- // session (the whole conversation, own words included, the first-turn
10929
- // material) and hand the adapter that to run on — before it speaks or acts.
10930
- // The lease already granted stands; the read reaches at least its mark. A
10931
- // fetch that fails returns null and the adapter falls back to the request
10932
- // 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.
10933
10663
  async freshRequest(reason) {
10934
10664
  const { payload, turnId, turnLog } = this;
10935
10665
  try {
@@ -10940,9 +10670,8 @@ var TurnExecution = class {
10940
10670
  false,
10941
10671
  true,
10942
10672
  void 0,
10943
- // CT1657: the turn's abort. This recompose happens mid-run with the
10944
- // adapter waiting on it, so a Stop here must end the wait rather than
10945
- // 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.
10946
10675
  this.abortController.signal
10947
10676
  );
10948
10677
  turnLog.debug(
@@ -10994,19 +10723,17 @@ var TurnExecution = class {
10994
10723
  conversationId: payload.conversationId,
10995
10724
  agentId: payload.agentId,
10996
10725
  turnId,
10997
- // CT113: the message this turn answers — passed back on every commit so
10998
- // 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.
10999
10728
  parentMessageId: payload.messageId,
11000
10729
  signal: abortController.signal,
11001
10730
  log: turnLog,
11002
10731
  nextSeq: this.nextSeq,
11003
10732
  spoke: this.resumedSpoke,
11004
- // 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.
11005
10734
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {},
11006
- // CT1292: a commit that didn't land may mean this turn's lease is gone.
11007
- // `noteCommitFailed` is a field slot the lease watchdog fills when it
11008
- // arms (it starts as a no-op) — the committer only fires it from inside
11009
- // 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`.
11010
10737
  onCommitFailed: (err) => this.noteCommitFailed(err)
11011
10738
  });
11012
10739
  const usesHttpTurnControl = turnRuntime === "codex" || turnRuntime === "opencode";
@@ -11110,9 +10837,9 @@ var TurnExecution = class {
11110
10837
  workspaceId,
11111
10838
  payload.conversationId,
11112
10839
  payload.agentId,
11113
- // CT1680: named for its turn, which a runner credential
11114
- // requires once its start is admitted; the server then binds
11115
- // 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.
11116
10843
  { agentSessionId: event.state, turnId }
11117
10844
  );
11118
10845
  } catch (err) {
@@ -11217,9 +10944,7 @@ var TurnExecution = class {
11217
10944
  }
11218
10945
  const body = {
11219
10946
  activeRunStartedAt: null,
11220
- // CT277: the server keys the turn-end metadata UPDATE (ended_at + tokens)
11221
- // off this turn id, so the settle PATCH must carry it — the start PATCH
11222
- // 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.
11223
10948
  turnId
11224
10949
  };
11225
10950
  if (o.turnUsage) {
@@ -11387,11 +11112,11 @@ var Dispatcher = class {
11387
11112
  this.aborts = opts.aborts ?? /* @__PURE__ */ new Map();
11388
11113
  }
11389
11114
  opts;
11390
- // CT1288: keyed (conversation|agent) → per-TURN controllers. Stop is
11391
- // 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.
11392
11117
  aborts;
11393
- // CT1109: pairs already told "your session couldn't be saved" — the notice is
11394
- // 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.
11395
11120
  sessionWriteNotified = /* @__PURE__ */ new Set();
11396
11121
  notifyStart(info) {
11397
11122
  try {
@@ -11408,11 +11133,6 @@ var Dispatcher = class {
11408
11133
  // The supervisor supplies a stable turnId on both fresh and resumed dispatch.
11409
11134
  // resumed is separate: only replay seeds sequence/speech recovery from the
11410
11135
  // prior span. A direct caller omitting turnId gets a fresh id.
11411
- //
11412
- // CT1261: a short `handle` creates a `TurnExecution` and runs it. The
11413
- // supervisor seam hands the execution exactly the three cross-turn duties —
11414
- // the abort registry, the session-write notice latch, the observer — and
11415
- // nothing else.
11416
11136
  async handle(payload, opts = {}) {
11417
11137
  const key = runKey(payload.conversationId, payload.agentId);
11418
11138
  const supervisor = {
@@ -11458,8 +11178,8 @@ var Dispatcher = class {
11458
11178
  }
11459
11179
  return new TurnExecution(this.opts, supervisor, payload, opts).run();
11460
11180
  }
11461
- // SJ383: cancel a specific (conversation, agent) run if one is in flight in
11462
- // 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.
11463
11183
  cancel(conversationId, agentId) {
11464
11184
  const key = runKey(conversationId, agentId);
11465
11185
  const pair = this.aborts.get(key);
@@ -13156,7 +12876,7 @@ var Outbox = class {
13156
12876
  }
13157
12877
  workspaceId;
13158
12878
  log;
13159
- // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
12879
+ // Resolved lazily (per call) so tests that swap HOME between
13160
12880
  // cases route writes at the right tmpdir.
13161
12881
  dir() {
13162
12882
  return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
@@ -13222,7 +12942,7 @@ var Outbox = class {
13222
12942
  );
13223
12943
  return entries;
13224
12944
  }
13225
- // 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
13226
12946
  // half of a resumed turn's seq floor the SERVER CANNOT SEE. A commit that
13227
12947
  // failed transiently before the crash is sitting in this directory with its
13228
12948
  // seq already spent, and the restart's drain is fire-and-forget
@@ -13446,24 +13166,20 @@ var CompanionSupervisor = class {
13446
13166
  config;
13447
13167
  log;
13448
13168
  hub;
13449
- // CT1379: the bundled-executable resolver — the execution-presence half of both
13450
- // routing gates. Synchronous and cheap (a module resolve plus a stat), so unlike
13451
- // the CLI probes it needs no boot-frozen fallback: a caller that asks before the
13452
- // 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.
13453
13172
  resolveBundled;
13454
- // CT1379: the last bundled status LOGGED per harness, so a missing binary is
13455
- // reported at startup and on each healthy↔missing transition rather than on
13456
- // 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.
13457
13175
  loggedBundledStatus = /* @__PURE__ */ new Map();
13458
- // CT571: the probed per-harness versions, reported on each heartbeat manifest.
13459
- // CT586: no longer boot-frozen — the per-heartbeat harness re-probe refreshes
13460
- // 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.
13461
13178
  harnessVersions;
13462
- // CT586: the latest harness detection signals (presence + reachability), probed
13463
- // on the heartbeat cadence + on demand. Feeds BOTH the heartbeat manifest's
13464
- // live presence and the Companion's Harnesses surface off ONE source, so the UI
13465
- // can never disagree with what the manifest advertises. Null until the first
13466
- // 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.
13467
13183
  harnessSignals = null;
13468
13184
  // Fresh per attempt, never the cached heartbeat signal: installing a harness
13469
13185
  // between attempts must be observed immediately.
@@ -13476,8 +13192,8 @@ var CompanionSupervisor = class {
13476
13192
  unrunnableRetryDelaysMs;
13477
13193
  deviceApi = null;
13478
13194
  deviceSub = null;
13479
- // ACC8: whether the device stream is open, which is what every workspace's
13480
- // link on the dashboard reports.
13195
+ // Whether the device stream is open, which is what every workspace's link on
13196
+ // the dashboard reports.
13481
13197
  deviceConnected = false;
13482
13198
  recoveryChecked = false;
13483
13199
  recovering = false;
@@ -13489,8 +13205,7 @@ var CompanionSupervisor = class {
13489
13205
  pollTimer = null;
13490
13206
  refreshing = false;
13491
13207
  stopped = false;
13492
- // CT484: latch so the companion/server version-skew warning is logged once, not
13493
- // on every 30s heartbeat.
13208
+ // The version-skew warning is logged once, not every heartbeat.
13494
13209
  versionSkewWarned = false;
13495
13210
  // This device's id, captured from the heartbeat / assignments response. The SSE
13496
13211
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
@@ -13500,12 +13215,12 @@ var CompanionSupervisor = class {
13500
13215
  // The in-flight assignments pull, exposed so a caller that needs fresh data
13501
13216
  // (pull-on-miss) can `await` the running pull rather than racing past it.
13502
13217
  inFlightRefresh = null;
13503
- // CT566: device-global connector-health memory — every dispatcher records its
13504
- // turn's classified outcome here (per runtime); the heartbeat echoes it so the
13505
- // 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.
13506
13221
  connectorHealth = new ConnectorHealthStore();
13507
- // ACC10: which dispatches this process is running, so the copy of a frame that
13508
- // 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.
13509
13224
  claims = new DispatchClaims();
13510
13225
  constructor(opts) {
13511
13226
  this.config = opts.config;
@@ -13526,10 +13241,10 @@ var CompanionSupervisor = class {
13526
13241
  }
13527
13242
  this.latchBootBundledState();
13528
13243
  }
13529
- // CT1681: what a runner container said it can run, probed once at start;
13530
- // 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.
13531
13246
  runnerImage = null;
13532
- // CT1681: the companion every container mounts — this one's own install.
13247
+ // The companion every container mounts: this one's own install.
13533
13248
  runnerCompanion() {
13534
13249
  return companionSource({
13535
13250
  configured: this.config.runners?.docker?.companionDir,
@@ -13641,10 +13356,7 @@ var CompanionSupervisor = class {
13641
13356
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
13642
13357
  this.pollTimer.unref?.();
13643
13358
  }
13644
- // The set of SSE subscriber `finished` promises — start.ts awaits these to
13645
- // keep the foreground process alive. With the poll loop running there's always
13646
- // at least the loops keeping the event loop alive; the SSE promises let a
13647
- // clean teardown resolve.
13359
+ // start.ts awaits these; they resolve on a clean teardown.
13648
13360
  finishedPromises() {
13649
13361
  return [...this.deviceSub ? [this.deviceSub.finished] : []];
13650
13362
  }
@@ -13672,39 +13384,26 @@ var CompanionSupervisor = class {
13672
13384
  bootId,
13673
13385
  version: COMPANION_VERSION,
13674
13386
  exposedSecretNames: store.names(),
13675
- // Report each runtime only when this device can actually run it:
13676
- // CT1379 claude-code and codex when the SDK's own bundled binary is there
13677
- // and the user asked for the harness, CT270 opencode when the operator
13678
- // configured an `opencode serve`.
13387
+ // Each runtime only when this device can actually run it.
13679
13388
  manifest: buildCompanionManifest({
13680
- // CT1082/CT1379: connected AND launchable. Execution presence is the
13681
- // live bundled-binary resolution (refreshed on this beat), consent is
13682
- // the user's `claudeCode.enabled`. Claude Code rides neither alone.
13389
+ // Consented (`claudeCode.enabled`) AND launchable (the live bundled
13390
+ // binary). Neither alone.
13683
13391
  claudeCode: this.claudeCodeOffered(),
13684
13392
  opencode: !!this.config.opencode?.serverUrl,
13685
- // CT481/CT1379: advertise codex when the operator enabled it AND its
13686
- // vendored binary is there. The enable flag alone used to be enough,
13687
- // which is how a device with a half-installed 331 MB platform package
13688
- // 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.
13689
13395
  codex: this.codexOffered(),
13690
- // CT1379: and say WHY when an asked-for harness is absent from the list
13691
- // above. Diagnostic only — the server never routes on it — and always
13396
+ // WHY an asked-for harness is absent above. Diagnostic only, and always
13692
13397
  // sent, so an empty array clears a previously reported issue.
13693
13398
  runtimeIssues: this.runtimeIssues(),
13694
- // CT571/CT586: each runtime's `version` from the latest harness probe
13695
- // (fail-soft to null). Informational only — the server matches on name.
13399
+ // Informational only; the server matches on name.
13696
13400
  versions: this.harnessVersions,
13697
- // CT1681: shown on the device page.
13401
+ // Shown on the device page.
13698
13402
  ...runnersEnabled(this.config) ? { runner: this.runnerManifest() } : {}
13699
13403
  }),
13700
- // CT566: echo the last classified credential state per runtime, when the
13701
- // companion has seen any. Omitted (undefined) until the first observed
13702
- // failure/heal, so a fresh companion's beat is unchanged and the server's
13703
- // 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.
13704
13406
  ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
13705
- // CT584: include enumerated models only when the probe SUCCEEDED (non-null).
13706
- // A null (probe failed / no opencode) omits the field, and the server then
13707
- // leaves this device's stored availability untouched.
13708
13407
  ...opencodeModels !== null ? { models: opencodeModels } : {}
13709
13408
  });
13710
13409
  this.hub.setDevice({ deviceId: res.deviceId });
@@ -13719,11 +13418,8 @@ var CompanionSupervisor = class {
13719
13418
  );
13720
13419
  }
13721
13420
  }
13722
- // CT484: belt-and-suspenders beside shipping the companion in lockstep inside the
13723
- // artifact — if the server reports a build version that differs from this
13724
- // companion's, warn loudly (once). This is exactly the skew that bit the M1 walk:
13725
- // an npm-pinned companion lagging a from-develop server. The elimination (bundled
13726
- // 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.
13727
13423
  checkVersionSkew(serverVersion) {
13728
13424
  if (this.versionSkewWarned) return;
13729
13425
  if (!serverVersion || serverVersion === COMPANION_VERSION) return;
@@ -13920,10 +13616,9 @@ var CompanionSupervisor = class {
13920
13616
  `Stopped running ${runner.displayName} (unassigned)`
13921
13617
  );
13922
13618
  }
13923
- // CT1379: can this machine LAUNCH the harness — is the SDK's own bundled
13924
- // executable there? Live (the per-beat re-resolve), falling back to resolving
13925
- // right now before the first refresh lands. Execution presence ONLY; consent is
13926
- // 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.
13927
13622
  bundledStatus(runtime) {
13928
13623
  const cached2 = runtime === "codex" ? this.harnessSignals?.codexBundled : this.harnessSignals?.claudeBundled;
13929
13624
  return cached2 ?? this.resolveBundled(runtime).status;
@@ -13934,23 +13629,19 @@ var CompanionSupervisor = class {
13934
13629
  bundledResolution(runtime) {
13935
13630
  return this.resolveBundled(runtime);
13936
13631
  }
13937
- // CT1082/CT1379: does this device OFFER claude-code — connected by its user, and
13938
- // able to start the binary a turn runs? The ONE signal both the heartbeat
13939
- // manifest and the dispatcher's adapter registry read, so what the device
13940
- // advertises and what it can select can't disagree (the CT833 invariant, with
13941
- // 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.
13942
13636
  claudeCodeOffered() {
13943
13637
  return isClaudeCodeConnected(this.config) && this.bundledStatus("claude-code") === "present";
13944
13638
  }
13945
- // CT1379: and the same question for codex, which until now was the enable flag
13946
- // alone — so an enabled device with no vendored binary advertised a runtime it
13947
- // 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.
13948
13640
  codexOffered() {
13949
13641
  return isCodexEnabled(this.config) && this.bundledStatus("codex") === "present";
13950
13642
  }
13951
- // CT1379: the manifest's diagnostic half, derived from the same two facts as the
13952
- // two predicates above so an issue and an advertisement can never both be made
13953
- // 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.
13954
13645
  runtimeIssues() {
13955
13646
  return runtimeIssuesFor({
13956
13647
  claudeCode: {
@@ -13960,26 +13651,16 @@ var CompanionSupervisor = class {
13960
13651
  codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") }
13961
13652
  });
13962
13653
  }
13963
- // CT1379: a missing binary is reported ONCE, and again only when the answer
13964
- // CHANGES — with the runtime, the platform/architecture and the resolution
13965
- // result, which is enough to find the occurrence in `companion.log` without a
13966
- // repro and carries nothing that could be a credential or a vendor's output.
13967
- //
13968
- // Two things this deliberately does NOT do (Codo's review, blocking finding #2):
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.
13969
13657
  //
13970
- // - It does not re-announce the state the device booted in. `warnAboutHarnessReadiness`
13971
- // already said that on stderr at startup, in the specified words, at the one
13972
- // moment a person is watching a terminal — and the `--daemon` launcher runs
13973
- // that preflight in the FOREGROUND for exactly that reason, because the
13974
- // child's output goes to a file. The boot state is latched in the
13975
- // constructor, so the first refresh reports nothing new; it lands here as a
13976
- // debug record instead, present for diagnosis without competing for
13977
- // attention with the warning the operator already read.
13978
- // - It does not report a transition in generic prose. A binary that goes
13979
- // missing at 3am is the case the operator will actually meet, and a line
13980
- // that says only "binary is missing" leaves them with no repair and no
13981
- // restart instruction. Transitions carry the specified copy verbatim — the
13982
- // 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.
13983
13664
  logBundledTransitions() {
13984
13665
  for (const runtime of ["claude-code", "codex"]) {
13985
13666
  const intended = runtime === "codex" ? isCodexEnabled(this.config) : isClaudeCodeConnected(this.config);
@@ -14002,9 +13683,8 @@ var CompanionSupervisor = class {
14002
13683
  this.log.debug(meta, "companion: bundled harness binary is missing (harness not in use)");
14003
13684
  }
14004
13685
  }
14005
- // CT1379: the boot state, latched so the first refresh reports no news, and
14006
- // recorded once as evidence. Runs in the constructor — the resolve is a module
14007
- // 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.
14008
13688
  latchBootBundledState() {
14009
13689
  for (const runtime of ["claude-code", "codex"]) {
14010
13690
  const resolution = this.resolveBundled(runtime);
@@ -14022,35 +13702,34 @@ var CompanionSupervisor = class {
14022
13702
  );
14023
13703
  }
14024
13704
  }
14025
- // CT1679: the locks every runner on this device shares, held here.
13705
+ // The locks every runner on this device shares.
14026
13706
  runnerLocks = new DeviceLocks();
14027
- // CT1679: `runners.sandbox`, checked against this host once. A host that
14028
- // 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.
14029
13709
  sandboxUsable;
14030
13710
  sandboxHost = {
14031
13711
  osSandboxUsable: () => this.sandboxUsable ??= osSandboxUsable(this.log)
14032
13712
  };
14033
- // CT1684: `runners.policy`, parsed once. An invalid one is kept as the
14034
- // 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.
14035
13715
  parsedSandboxPolicy;
14036
13716
  sandboxPolicy() {
14037
13717
  this.parsedSandboxPolicy ??= parseSandboxPolicy(this.config.runners?.policy);
14038
13718
  return this.parsedSandboxPolicy;
14039
13719
  }
14040
- // CT1691: the walk for other companions' stores, run off the turn path.
13720
+ // The walk for other companions' stores, run off the turn path.
14041
13721
  storeWatch;
14042
13722
  companionStores() {
14043
13723
  this.storeWatch ??= companionStoreWatch(this.log);
14044
13724
  return this.storeWatch;
14045
13725
  }
14046
- // CT1693: the sandbox one agent's turns run under — its `agents` row, then
14047
- // the device's. Resolved from the current config every time it's asked, so
14048
- // 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.
14049
13729
  runnerSandboxFor(row) {
14050
13730
  return runnerSandboxFor(this.config, row, this.sandboxHost);
14051
13731
  }
14052
- // CT1693: what the heartbeat says about this device's runners, resolved
14053
- // against the agents it runs right now.
13732
+ // Resolved against the agents this device runs right now.
14054
13733
  runnerManifest() {
14055
13734
  const agents = [...this.workspaces.values()].flatMap(
14056
13735
  (wr) => [...wr.agents.values()].map((agent) => ({
@@ -14064,7 +13743,7 @@ var CompanionSupervisor = class {
14064
13743
  ...runnerSandboxReport(this.config, agents, this.sandboxHost)
14065
13744
  };
14066
13745
  }
14067
- // CT1679: one provider for the device, built on first use.
13746
+ // One provider for the device, built on first use.
14068
13747
  runnerProvider() {
14069
13748
  this.providerForRunners ??= this.dockerRunners() ? this.createDockerRunners() : createLocalProcessProvider();
14070
13749
  return this.providerForRunners;
@@ -14106,8 +13785,7 @@ var CompanionSupervisor = class {
14106
13785
  log: this.log
14107
13786
  });
14108
13787
  }
14109
- // CT1681: the runner command for this device's provider: the image's own
14110
- // companion in a container, this process's otherwise.
13788
+ // The image's own companion in a container, this process's otherwise.
14111
13789
  runnerCommandFor() {
14112
13790
  if (this.runnerCommand) return this.runnerCommand;
14113
13791
  return this.dockerRunners() ? ["cabane-companion", "run-turn"] : selfRunnerCommand();
@@ -14139,36 +13817,20 @@ var CompanionSupervisor = class {
14139
13817
  aborts: ctx.aborts,
14140
13818
  runConfig: ctx.runConfig,
14141
13819
  log: this.log.child({ agentName: ctx.agentDisplayName ?? ctx.agentUsername }),
14142
- // CT833: register the claude-code adapter only when this device actually
14143
- // offers claude-code — read per turn (not captured here), so a harness
14144
- // installed or connected after boot works on the next turn exactly as it
14145
- // appears on the next beat. CT1082: "offers" means connected as well as
14146
- // runnable, so a disconnected harness can't be selected either.
14147
- // CT1379: and the same for the bundled binary — the predicate is shared with
14148
- // the heartbeat above, so a runtime advertised by one and refused by the
14149
- // 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.
14150
13824
  claudeCodeAvailable: () => this.claudeCodeOffered(),
14151
- // CT270: the opencode server URL (operator-configured), when this device
14152
- // offers the opencode runtime. Threaded so an opencode turn selects the
14153
- // opencode adapter; unset leaves the device claude-code-only.
14154
13825
  ...this.config.opencode?.serverUrl ? { opencodeServerUrl: this.config.opencode.serverUrl } : {},
14155
- // CT481: register the codex adapter when this device offers codex; a miss
14156
- // leaves an `openai/…` turn to fail loudly (no silent claude-code fallback).
14157
- // CT1379: read PER TURN, like claude-code above, and off the same predicate
14158
- // the heartbeat uses — it was captured here at dispatcher-construction time
14159
- // from the enable flag alone, so a device could register an adapter for a
14160
- // binary it did not have and keep it registered after the binary came back.
14161
13826
  codexAvailable: () => this.codexOffered(),
14162
- // CT556: per-turn timeout watchdog windows, from the companion's own env
14163
- // (`AGENT_IDLE_TIMEOUT_MS` / `AGENT_TOTAL_TIMEOUT_MS`). Unset → the
14164
- // dispatcher's baked-in defaults (CT1503: 15 min idle / 6h total). An
14165
- // override set here still wins, and is deliberately not validated against
14166
- // 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.
14167
13830
  ...positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") !== void 0 ? { idleTimeoutMs: positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") } : {},
14168
13831
  ...positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") !== void 0 ? { totalTimeoutMs: positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") } : {},
14169
13832
  observer: this.hub.observerFor(ctx.workspaceId, ctx.agentId, ctx.workspaceSlug),
14170
13833
  transcriptDir: transcriptsDir(),
14171
- // CT566: the shared connector-health memory the turn records into on settle.
14172
13834
  connectorHealth: this.connectorHealth,
14173
13835
  ...runnersEnabled(this.config) ? {
14174
13836
  runner: {
@@ -14192,11 +13854,10 @@ var CompanionSupervisor = class {
14192
13854
  this.workspaces.delete(workspaceId);
14193
13855
  this.hub.removeWorkspace(workspaceId);
14194
13856
  }
14195
- // ---- SSE event routing (per workspace, BY agentId) ----
14196
- // ACC10: the device stream's frames — dispatch and cancel, addressed to this
14197
- // device and authenticated as it. ACC8: the only stream this companion opens.
14198
- // It keeps no cursor: it replays what is still offered on every connect
14199
- // 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`.
14200
13861
  async handleDeviceMessage(ev) {
14201
13862
  const wire = this.parseFrame(ev);
14202
13863
  if (!wire) return;
@@ -14232,9 +13893,9 @@ var CompanionSupervisor = class {
14232
13893
  return null;
14233
13894
  }
14234
13895
  }
14235
- // ACC10: the dedupe gate. A reconnect replays every dispatch still offered,
14236
- // including one whose turn is already running here; the first copy runs the
14237
- // 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.
14238
13899
  //
14239
13900
  // A cancel needs no gate: `dispatcher.cancel` on an already-aborted run is a
14240
13901
  // no-op that reports `false`, so a second copy is inert.
@@ -14250,8 +13911,6 @@ var CompanionSupervisor = class {
14250
13911
  if (claim) this.claims.release(wire.id);
14251
13912
  }
14252
13913
  }
14253
- // Everything below here is the routing as it was: it takes a frame and knows
14254
- // nothing about which stream carried it.
14255
13914
  async routeFrame(wr, wire) {
14256
13915
  if (wire.type === "device:cancel_requested") {
14257
13916
  const payload2 = {
@@ -14317,17 +13976,12 @@ var CompanionSupervisor = class {
14317
13976
  await tail;
14318
13977
  if (wr.chains.get(chainKey) === tail) wr.chains.delete(chainKey);
14319
13978
  }
14320
- // CT1146: tell the server this device cannot run a dispatch it was addressed to.
14321
- //
13979
+ // Tell the server this device cannot run a dispatch it was addressed to.
14322
13980
  // Returns whether the report LANDED. The server checks the recorded dispatch
14323
- // destination even if the agent's assignment has moved. Reporting promptly
14324
- // explains the roster miss; the thirty-minute unanswered-offer deadline is
14325
- // only the backstop when no report lands.
13981
+ // destination even if the assignment has moved.
14326
13982
  //
14327
- // Bounded retry, because the common failure is transient (a 503 mid-deploy, a
14328
- // dropped socket) and one more attempt a second later usually lands. Bounded
14329
- // hard — this runs inline on the event loop, and an unbounded wait here would
14330
- // 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.
14331
13985
  async reportUnrunnableDispatch(workspaceId, payload) {
14332
13986
  if (!this.deviceApi) return false;
14333
13987
  let lastErr;
@@ -14367,9 +14021,8 @@ var CompanionSupervisor = class {
14367
14021
  );
14368
14022
  return false;
14369
14023
  }
14370
- // CT1680: a runner is gone without having settled its turn. Same bounded
14371
- // retry as the unrunnable report above, on the device credential, which is
14372
- // 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.
14373
14026
  async reportRunnerGone(report) {
14374
14027
  if (!this.deviceApi) return;
14375
14028
  let lastErr;
@@ -14394,12 +14047,9 @@ var CompanionSupervisor = class {
14394
14047
  "Couldn't tell Cabane a turn's runner stopped; its credential expires on its own."
14395
14048
  );
14396
14049
  }
14397
- // Pull-on-miss for the assignment-change race: a dispatch arrived for an agent
14398
- // we don't yet run. If it's addressed to THIS device, the assignment just
14399
- // changed under us (the poll hasn't caught it) — force a fresh assignments pull
14400
- // and return the now-live runner. Returns undefined when the dispatch isn't for
14401
- // this device, or the agent still isn't ours after pulling (a genuine
14402
- // 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.
14403
14053
  async recoverRacedAgent(wr, payload) {
14404
14054
  if (!this.deviceId || payload.deviceId !== this.deviceId) return void 0;
14405
14055
  if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
@@ -14440,9 +14090,8 @@ var CompanionSupervisor = class {
14440
14090
  };
14441
14091
  const tail = this.runDispatch(
14442
14092
  wr,
14443
- // No frame delivered this one — it came off `/runs` — so there is no
14444
- // dispatch id to dedupe on. The `recoveredTurns` set above is this
14445
- // path's own guard.
14093
+ // Off `/runs`, not a frame, so no dispatch id to dedupe on;
14094
+ // `recoveredTurns` is this path's guard.
14446
14095
  "",
14447
14096
  payload,
14448
14097
  agent,
@@ -14464,9 +14113,7 @@ var CompanionSupervisor = class {
14464
14113
  this.recovering = false;
14465
14114
  }
14466
14115
  }
14467
- // One turn: the SJ516 F7 / CT26 skip-vs-resume dedupe, the dispatch, then
14468
- // the console summary. Mirrors the old per-workspace `handleEvent`, now
14469
- // scoped to one agent.
14116
+ // One turn: the skip-vs-resume dedupe, the dispatch, then the console summary.
14470
14117
  async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId) {
14471
14118
  const workspaceId = wr.workspaceId;
14472
14119
  if (dispatchId && hasDispatched(workspaceId, dispatchId)) {
@@ -14551,7 +14198,7 @@ var CompanionSupervisor = class {
14551
14198
  );
14552
14199
  }
14553
14200
  }
14554
- // ---- per-agent outbox drain scheduler (CT25) ----
14201
+ // ---- per-agent outbox drain scheduler ----
14555
14202
  makeDrain(api, agentId) {
14556
14203
  let drainTimer;
14557
14204
  let drainDelay = DRAIN_BASE_MS;
@@ -14620,19 +14267,16 @@ var CompanionSupervisor = class {
14620
14267
  saveConfig(next);
14621
14268
  configureLogger(this.log, next);
14622
14269
  }
14623
- // ---- harnesses (CT586) ----
14624
- // Probe all three harnesses, cache the signals + versions, and push the derived
14625
- // per-harness snapshot to the hub (which flips the Companion's Harnesses surface
14626
- // over SSE). Fail-soft by construction — `probeHarnessSignals` never throws — but
14627
- // wrapped anyway so a harness refresh can never take a heartbeat down. Called on
14628
- // 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.
14629
14274
  async refreshHarnessStatuses(presence = {}) {
14630
14275
  try {
14631
14276
  const signals = await probeHarnessSignals(this.config, {
14632
14277
  probePresence: this.probePresence,
14633
14278
  presence,
14634
- // CT1379: the bundled executables are resolved on the same cadence as the
14635
- // 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.
14636
14280
  resolveBundled: this.resolveBundled
14637
14281
  });
14638
14282
  this.harnessSignals = signals;
@@ -14655,11 +14299,10 @@ var CompanionSupervisor = class {
14655
14299
  async recheckHarnesses() {
14656
14300
  await this.refreshHarnessStatuses();
14657
14301
  }
14658
- // CT1085 §1 step 3: beat NOW and wait for it to land. `start` calls this
14659
- // between bringing the runtime up and asking the person anything, so the
14660
- // browser's connect step is already showing what this machine has ("your
14661
- // terminal is asking") rather than sitting blank while the terminal blocks on
14662
- // 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.
14663
14306
  async heartbeatNow() {
14664
14307
  if (this.inFlightHeartbeat) {
14665
14308
  await this.inFlightHeartbeat;
@@ -14668,35 +14311,25 @@ var CompanionSupervisor = class {
14668
14311
  this.kickHeartbeat();
14669
14312
  if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
14670
14313
  }
14671
- // CT1085: the config as it stands right now — after any `enableHarness` write.
14672
- // The control socket's connect handler needs it to run the shake-out check
14673
- // against the URL/flag that was just persisted, not the one this process booted
14674
- // 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.
14675
14316
  currentConfig() {
14676
14317
  return this.config;
14677
14318
  }
14678
- // Friendly enable for the config-driven harnesses — flip the flag the app owns in
14679
- // `~/.cabane/config.json`, no hand-edited JSON. It never installs a binary and
14680
- // 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).
14681
14321
  //
14682
- // CT1379: this records INTENT; it does not by itself expose anything. A harness
14683
- // is advertised only when the user asked for it AND the SDK's own bundled binary
14684
- // resolves, so a successful enable on a companion whose optional dependency
14685
- // never installed leaves the device advertising nothing — correctly, and with
14686
- // the Harnesses surface saying why. Read every "expose" below as "record the
14687
- // half of the answer the user owns".
14688
- // - claude-code (CT1082): set `claudeCode.enabled`, after checking `claude` is
14689
- // on PATH. This is the callable the terminal offer ("We found Claude Code.
14690
- // Connect it? [Y/n]") and the web pairing flow both wire to.
14691
- // - codex: set `codex.enabled` after checking `codex` is on PATH (the CLI +
14692
- // `codex login` remain the user's).
14693
- // - opencode: set `opencode.serverUrl` — but ONLY after health-probing the URL,
14694
- // so we never advertise a serve that isn't there. An unreachable URL is a
14695
- // typed failure the UI shows in place, not a silent bad write.
14696
- // On success, persist, re-probe, and beat immediately, so a runtime that IS
14697
- // launchable starts taking turns at once rather than on the next tick — and one
14698
- // that isn't reports its issue just as promptly. Returns a typed outcome (never
14699
- // 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.
14700
14333
  async enableHarness(input) {
14701
14334
  let next;
14702
14335
  let checkedPresence = null;
@@ -14747,9 +14380,8 @@ var CompanionSupervisor = class {
14747
14380
  return { ok: true };
14748
14381
  }
14749
14382
  // Re-create every running agent's Dispatcher from the CURRENT config, keeping
14750
- // the runner (its api/outbox/chains/SSE) intact. Used after a runtime-enabling
14751
- // config change so constructor-captured options — per-agent cwd/prepareHook and
14752
- // 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.
14753
14385
  rebuildDispatchers() {
14754
14386
  for (const wr of this.workspaces.values()) {
14755
14387
  for (const runner of wr.agents.values()) {
@@ -14913,8 +14545,7 @@ async function createCompanionRuntime(opts = {}) {
14913
14545
  }));
14914
14546
  await warnAboutHarnessReadiness(cfg, {
14915
14547
  probeClaude: async () => claudeCode,
14916
- // CT1085: the CLI's onboarding script owns the terminal and says this in
14917
- // 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
14918
14549
  // sink that logs instead. Every other caller keeps the stderr line.
14919
14550
  ...opts.onReadinessWarning ? { warn: opts.onReadinessWarning } : {}
14920
14551
  });
@@ -14951,11 +14582,10 @@ async function createCompanionRuntime(opts = {}) {
14951
14582
  const clearRuntimeOnExit = () => clearRuntimeStateIfOurs(instanceId);
14952
14583
  process.on("exit", clearRuntimeOnExit);
14953
14584
  const hub = new CompanionStateHub({
14954
- // CT29: one device, one base URL — the cabane instance this device is paired
14955
- // with. The dashboard's connection line shows it.
14585
+ // The dashboard's connection line shows it.
14956
14586
  baseUrl: cfg.baseUrl,
14957
14587
  companionVersion: COMPANION_VERSION,
14958
- // SJ516 F4: surfaced on `/api/status` so `stop`/`status` can confirm the
14588
+ // Surfaced on `/api/status` so `stop`/`status` can confirm the
14959
14589
  // process behind the marker pid is this companion (not a recycled pid).
14960
14590
  instanceId
14961
14591
  });
@@ -15008,7 +14638,7 @@ async function createCompanionRuntime(opts = {}) {
15008
14638
  socket: control.path,
15009
14639
  ...dashboard ? { url: dashboard.url, port: dashboard.port } : {},
15010
14640
  startedAt,
15011
- // 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
15012
14642
  // marker records whether this companion is backgrounded (foreground start
15013
14643
  // leaves it unset → false).
15014
14644
  daemon: process.env.CABANE_COMPANION_DAEMON === "1",