@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/cli.js CHANGED
@@ -110,18 +110,15 @@ var init_prepare_hook = __esm({
110
110
  // default is generous; a hook that hangs past this is killed and the turn
111
111
  // fails with a clear timeout message rather than pinning the companion.
112
112
  timeoutMs: z.number().int().positive().optional(),
113
- // CT943: re-invoke the hook on turns where a prepared result already exists,
114
- // in VALIDATE mode — the cached result is handed back as `prepared` and the
115
- // hook's stdout is ignored, so the cwd provably cannot move. Its only power
116
- // is to exit non-zero and fail the turn.
113
+ // Re-invoke the hook when a prepared result already exists, in VALIDATE
114
+ // mode: the cached result is handed back as `prepared` and stdout is
115
+ // ignored, so the cwd cannot move. Its only power is to exit non-zero and
116
+ // fail the turn. The hook resolves once per (conversation, agent), so a
117
+ // later message asking for a different environment is otherwise silently
118
+ // ignored; this lets a hook refuse it out loud.
117
119
  //
118
- // Why it exists: the hook runs exactly once per (conversation, agent), so a
119
- // later message asking for a different environment can't be honoured. Without
120
- // this, it is silently ignored — the host knows and says nothing, which is
121
- // the worst available outcome. An opt-in hook can now refuse it out loud.
122
- //
123
- // Default OFF, and deliberately: an operator hook that provisions
124
- // unconditionally must not start running on every turn because it upgraded.
120
+ // Default OFF: a hook that provisions unconditionally must not start
121
+ // running on every turn because the companion upgraded.
125
122
  validateCached: z.boolean().optional(),
126
123
  // Independent operator-owned reporter. Receives sanitized failure metadata
127
124
  // on stdin; never re-enters this hook and cannot change the turn outcome.
@@ -160,16 +157,15 @@ var init_prepare_hook = __esm({
160
157
  CABANE_AGENT_USERNAME: input.agentUsername,
161
158
  CABANE_RUNTIME: input.runtime ?? "",
162
159
  CABANE_HOST_ACCESS: input.hostAccess ? "1" : "0",
163
- // CT319: the conversation anchor is gone. These are kept as DEPRECATED
164
- // back-compat constants so an old user prepare script that still reads
165
- // them doesn't crash; a hook should key off CABANE_TRIGGER_ENTRY_PATHS.
160
+ // DEPRECATED constants, kept so an old prepare script that reads them
161
+ // doesn't crash; a hook should key off CABANE_TRIGGER_ENTRY_PATHS.
166
162
  CABANE_ANCHOR_KIND: "workspace",
167
163
  CABANE_ANCHOR_PATH: "",
168
- // CT317: newline-joined — workspace paths never contain a newline, so a
164
+ // Newline-joined — workspace paths never contain a newline, so a
169
165
  // hook splits on `\n` unambiguously. Empty string when none.
170
166
  CABANE_TRIGGER_ENTRY_PATHS: input.triggerEntryPaths.join("\n"),
171
167
  CABANE_CONVERSATION_TITLE: input.title ?? "",
172
- // CT943: the dispatching message's text, and — in validate mode only —
168
+ // The dispatching message's text, and — in validate mode only —
173
169
  // the prepared result the hook is being asked to check this dispatch
174
170
  // against. Both empty/absent on an ordinary first run.
175
171
  CABANE_TRIGGER_MESSAGE: input.messageBody ?? "",
@@ -458,18 +454,17 @@ var init_config = __esm({
458
454
  localAgentConfigSchema = z2.object({
459
455
  cwd: z2.string().optional(),
460
456
  prepareHook: prepareHookSchema.optional(),
461
- // CT289: the Claude Code auto-memory escape hatch. Auto-memory is forced OFF
462
- // by default on every companion (memory belongs in the Cabane workspace, and a
463
- // shared companion would otherwise pool one cwd-keyed memory dir across users).
464
- // On a companion you run yourself, set `claudeCode: { autoMemory: true }` to hand
465
- // auto-memory back to your own `~/.claude/settings.json` — Cabane then stops
457
+ // The Claude Code auto-memory escape hatch. Auto-memory is forced OFF by
458
+ // default (memory belongs in the Cabane workspace, and a shared companion
459
+ // would pool one cwd-keyed memory dir across users). On a companion you run
460
+ // yourself, `claudeCode: { autoMemory: true }` hands auto-memory back to your
461
+ // own `~/.claude/settings.json` — Cabane then stops
466
462
  // injecting the off switch and your normal Claude Code memory workflow applies
467
463
  // (in coding mode, where the checkout's project settings are read).
468
464
  claudeCode: z2.object({ autoMemory: z2.boolean().optional() }).strict().optional(),
469
- // CT1693: this agent's exception to `runners.sandbox` — an ops agent that
470
- // runs with the machine on a sandboxed device, or one sandboxed agent on a
471
- // device that isn't. Absent is "inherit"; see `runnerSandboxFor`. Only on a
472
- // row keyed by the agent's id (`agentsConfigSchema`).
465
+ // This agent's exception to `runners.sandbox`: an ops agent that runs with
466
+ // the machine on a sandboxed device, or one sandboxed agent on a device that
467
+ // isn't. Absent is "inherit". Only on a row keyed by the agent's id.
473
468
  sandbox: z2.enum(["os", "none"]).optional()
474
469
  }).strict();
475
470
  AGENT_ID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -485,9 +480,8 @@ var init_config = __esm({
485
480
  }
486
481
  });
487
482
  companionConfigSchema = z2.object({
488
- // The cabane instance this device is paired with. SJ515: https-enforced
489
- // (loopback exempt) so a hand-edited config can't smuggle a plaintext-http
490
- // base URL onto the MITM-able channel the device token + prompt ride.
483
+ // https-enforced (loopback exempt), so a hand-edited config can't put the
484
+ // device token and prompt on a plaintext channel.
491
485
  baseUrl: z2.string().url().refine(isAllowedBaseUrl, {
492
486
  message: "baseUrl must be https (loopback http is allowed for local dev only)"
493
487
  }),
@@ -505,91 +499,59 @@ var init_config = __esm({
505
499
  // agentId / username / `slug/username`. Hand-added by the operator; the companion
506
500
  // never writes this (it only persists credentials + the device, elsewhere).
507
501
  agents: agentsConfigSchema.optional(),
508
- // CT1095: the DEVICE-LEVEL prepare hook — one entry that applies to every agent
509
- // assigned to this device, unless that agent's own `agents` row names a hook of
510
- // its own.
511
- //
512
- // Until now `prepareHook` existed only under `agents[<id>]`, which made the
513
- // config a second silent roster: a device whose provisioning is genuinely
514
- // per-device (ours resolves a directory from the conversation and agent ids the
515
- // companion already hands the hook) still had to list every agent by id, and an
516
- // agent nobody remembered to add got no hook at all — no error, no log, just a
517
- // turn that started in the operator's `$HOME`. That is how @dale woke in
518
- // `/home/joshed` on 2026-08-17 with a perfectly correct dispatch.
519
- //
520
- // Absent → nothing changes for anyone, which is why this can land inert: a
521
- // device with no device-level entry behaves exactly as it did before. A hook
522
- // that fails still fails the dispatch loudly, as now.
502
+ // The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
503
+ // `agents` row names one. Per-agent hooks alone make the config a second
504
+ // silent roster: an agent nobody listed gets no hook and no error, and starts
505
+ // in the operator's `$HOME` (an agent woke in `/home/joshed` this way on
506
+ // 2026-08-17).
523
507
  prepareHook: prepareHookSchema.optional(),
524
- // CT1085 §9: `dashboardPort` and `autoOpen` are READ AND IGNORED by the CLI —
525
- // it binds no port and opens no browser. They stay in the schema (rather than
526
- // becoming a strict-mode parse error on an existing config file) and still mean
527
- // what they say to the Electron shell, the one front-end that renders a
528
- // dashboard. `logLevel` is unaffected: pino level, live-editable.
508
+ // `dashboardPort` and `autoOpen` are IGNORED by the CLI (no port, no browser)
509
+ // but stay in the schema so an existing file still parses; the Electron shell
510
+ // reads them. `logLevel` is pino's level, live-editable.
529
511
  dashboardPort: z2.number().int().min(1).max(65535).optional(),
530
512
  autoOpen: z2.boolean().optional(),
531
513
  logLevel: z2.enum(["warn", "info", "debug"]).optional(),
532
514
  logFormat: z2.enum(["human", "json"]).optional(),
533
- // CT270: the opencode runtime, when the operator runs one on this machine. The
534
- // operator installs opencode, starts `opencode serve` (auth via opencode's own
535
- // `/connect` — Cabane never sees provider keys), and points the companion at it
536
- // here. Setting this makes the device advertise the `opencode` runtime on its
537
- // heartbeat manifest (so the server offers DeepSeek/opencode models here and
538
- // routes those turns to this device) AND registers the opencode adapter in the
539
- // dispatcher. Absent → the device is claude-code-only, exactly as before.
515
+ // An `opencode serve` the operator runs here (auth via opencode's own
516
+ // `/connect`; Cabane never sees provider keys). Set, the device advertises the
517
+ // `opencode` runtime and registers its adapter.
540
518
  opencode: z2.object({
541
519
  serverUrl: z2.string().url()
542
520
  }).strict().optional(),
543
- // CT1082: the Claude Code runtime, when the user connected it on this machine.
544
- // The change this task exists for: Claude Code used to be the one harness a
545
- // device exposed with no configuration — a `claude --version` exit-0 was taken as
546
- // consent — and it is now the third config-driven harness, opted into exactly
547
- // like codex. CT1379: the binary that must be there is the Agent SDK's own
548
- // bundled executable — the file a turn actually launches — not the user's
549
- // `claude` on PATH, which the SDK never runs. The manifest advertises
550
- // claude-code only when this block says so AND that binary resolves.
521
+ // Whether the user connected Claude Code on this machine: consent, opted into
522
+ // like codex. The manifest advertises claude-code only when this says so AND
523
+ // the Agent SDK's bundled executable resolves (not `claude` on PATH, which the
524
+ // SDK never runs).
551
525
  //
552
- // The block's PRESENCE is also the migration marker (see `migrateConnectedHarnesses`).
553
- // Absent means the config predates this task — a device whose user was never
554
- // asked — and the migration grandfathers it on first start. So every config
555
- // written from here on carries the block explicitly, including a freshly-paired
556
- // one, which starts at `enabled: false`: a new device is connected to nothing
557
- // until its user says otherwise.
526
+ // The block's PRESENCE is also the migration marker
527
+ // (`migrateConnectedHarnesses`): absent means a config whose user was never
528
+ // asked. So every config written carries it, a fresh pair at `enabled: false`.
558
529
  //
559
- // Not to be confused with the per-agent `agents.<key>.claudeCode.autoMemory`
560
- // above — that's one agent's memory switch, this is the device's connected set.
530
+ // Not the per-agent `agents.<key>.claudeCode.autoMemory`, one agent's memory
531
+ // switch.
561
532
  claudeCode: z2.object({
562
533
  enabled: z2.boolean().optional()
563
534
  }).strict().optional(),
564
- // CT481: the codex runtime, when the operator runs Codex on this machine. Unlike
565
- // opencode (a long-lived `opencode serve` addressed by URL), Codex is a local CLI
566
- // the `@openai/codex-sdk` spawns per turn — so the config is just an opt-in flag,
567
- // not a server address. The operator installs Codex + logs in (`codex login`, or
568
- // `CODEX_API_KEY` — Cabane never sees the key) and sets `{ "codex": { "enabled":
569
- // true } }`. Presence of the block enables it (an explicit `enabled: false`
570
- // keeps the block but turns it off). Absent → the device doesn't offer codex.
571
- // CT1379: enabling is now half the answer, not all of it — the manifest and the
572
- // adapter registry additionally require the SDK's vendored `codex` binary to
573
- // resolve, because the enable flag alone advertised a runtime a device with a
574
- // half-installed platform package could not start.
535
+ // Codex is a local CLI the SDK spawns per turn, so this is an opt-in flag, not
536
+ // an address. The operator logs in (`codex login` or `CODEX_API_KEY`; Cabane
537
+ // never sees the key). The block's presence enables it; `enabled: false` turns
538
+ // it off. Half the answer: advertising also requires the SDK's vendored
539
+ // `codex` binary to resolve.
575
540
  codex: z2.object({
576
541
  enabled: z2.boolean().optional()
577
542
  }).strict().optional(),
578
- // CT1679: run each turn in a child runner process instead of in this one.
579
- // Absent, or `enabled: false`, is the in-process path, untouched. `sandbox`
580
- // wraps the runner in an OS sandbox where the host supports one; `'none'`
581
- // (the default) runs it as a plain child beside this process, a supported
582
- // configuration in its own right rather than a degraded one.
583
- // CT1684: `policy` is what the OS sandbox lets a turn reach, read and hold
584
- // (`runner/sandbox-policy.ts`). It is validated there rather than here, so a
585
- // bad policy refuses the turns it governs, with the field named, instead of
586
- // keeping the companion from starting at all.
543
+ // Run each turn in a child runner process. Absent or `enabled: false` is the
544
+ // in-process path. `sandbox: 'os'` wraps the runner in an OS sandbox where the
545
+ // host supports one; `'none'` (the default) is a plain child, supported in its
546
+ // own right. `policy` is what the sandbox lets a turn reach, read and hold,
547
+ // validated in `runner/sandbox-policy.ts` so a bad policy refuses the turns it
548
+ // governs, field named, instead of stopping the companion starting.
587
549
  runners: z2.object({
588
550
  enabled: z2.boolean().optional(),
589
551
  provider: z2.enum(["local-process", "docker"]).optional(),
590
552
  sandbox: z2.enum(["os", "none"]).optional(),
591
- // CT1681: the `docker` provider — a container per conversation and agent,
592
- // from one image, on a network whose only way out is an allow-listed proxy.
553
+ // A container per conversation and agent, from one image, on a network
554
+ // whose only way out is an allow-listed proxy.
593
555
  docker: z2.object({
594
556
  // The base image a container runs. Default: the one this companion
595
557
  // builds from its own Dockerfile on first use. One named here must
@@ -1133,7 +1095,7 @@ var init_api_error_shape = __esm({
1133
1095
  "dispatch_not_admitted",
1134
1096
  "turn_already_ended",
1135
1097
  "turn_belongs_elsewhere",
1136
- // Rooms: the context this start was composed from named an attempt the
1098
+ // The context this start was composed from named an attempt the
1137
1099
  // lease no longer picks (midnight rolled it, a degrade retired it). Nothing
1138
1100
  // was granted; the turn recomposes its context and starts again, once.
1139
1101
  "dispatch_context_stale"
@@ -1207,10 +1169,8 @@ var init_api = __esm({
1207
1169
  this.opts = opts;
1208
1170
  }
1209
1171
  opts;
1210
- // CT25: re-entrancy guard for `drainOutbox`. The supervisor kicks a drain
1211
- // from several places (startup, SSE reconnect, the backoff timer); without
1212
- // this guard two overlapping drains could each pick up the same queued entry
1213
- // and double-send it (the server dedupes, but the wasted POSTs aren't free).
1172
+ // Re-entrancy guard for `drainOutbox`: drains are kicked from several places,
1173
+ // and two overlapping ones would double-send the same entry.
1214
1174
  draining = false;
1215
1175
  lastDeliveryWarning = 0;
1216
1176
  get base() {
@@ -1272,19 +1232,13 @@ var init_api = __esm({
1272
1232
  }
1273
1233
  }
1274
1234
  }
1275
- // CT25: send-or-enqueue for a turn-row commit. Tries the immediate
1276
- // (retry-budgeted) send first so the happy path keeps its current latency,
1277
- // then on a *retryable* failure persists the row to the on-disk outbox
1278
- // instead of dropping it. Classification is deliberate:
1279
- // - success → done.
1280
- // - no outbox configured (one-shot CLI / tests) → rethrow, exactly the
1281
- // pre-CT25 behaviour.
1282
- // - cancel (the turn was stopped; `signal` aborted or an AbortError) →
1283
- // rethrow without queuing: the user asked us to stop, and the held text
1284
- // was already dropped — we must not resurrect it from the queue.
1285
- // - terminal 4xx (a deterministic rejection a replay can't fix) → rethrow,
1286
- // so the caller's error path runs as it does today (e.g. the committer
1287
- // re-arms its empty-final fallback).
1235
+ // Send-or-enqueue for a turn-row commit: the immediate (retry-budgeted) send
1236
+ // first, then on a *retryable* failure persist to the outbox.
1237
+ // - no outbox configured (one-shot CLI / tests) → rethrow.
1238
+ // - cancel (`signal` aborted or an AbortError) → rethrow without queuing:
1239
+ // the user asked us to stop, and the text must not resurrect from the queue.
1240
+ // - terminal 4xx (a replay can't fix it) → rethrow, so the caller's error
1241
+ // path runs.
1288
1242
  // - retryable (transport / 5xx — commit outcome may be unknown) → queue
1289
1243
  // and return. This acknowledges local delivery responsibility, not server
1290
1244
  // persistence. Replays retain the endpoint's identity: messages/thinking/
@@ -1309,13 +1263,10 @@ var init_api = __esm({
1309
1263
  );
1310
1264
  }
1311
1265
  }
1312
- // CT25: flush the outbox FIFO. Removes an entry once the server acknowledges
1313
- // it (2xx) or rejects it terminally (any 4xx — a deleted conversation 404, a
1314
- // forbidden, etc. — which a replay can never satisfy, so we discard with a
1315
- // log rather than spin forever). A retryable failure (transport / 5xx) means
1316
- // the API is still down: stop the cycle and leave that entry and the rest
1317
- // queued for the next drain. Returns a summary the supervisor's backoff
1318
- // scheduler reads. Re-entrancy-guarded so overlapping kicks don't double-send.
1266
+ // Flush the outbox FIFO. An entry goes once the server acknowledges it (2xx)
1267
+ // or rejects it terminally (any 4xx, which a replay can never satisfy: discard
1268
+ // with a log rather than spin). A retryable failure means the API is still
1269
+ // down: stop and leave that entry and the rest for the next drain.
1319
1270
  async drainOutbox() {
1320
1271
  const outbox = this.opts.outbox;
1321
1272
  if (!outbox) return { remaining: 0, progressed: false };
@@ -1363,40 +1314,24 @@ var init_api = __esm({
1363
1314
  }
1364
1315
  return { remaining: outbox.size(), progressed };
1365
1316
  }
1366
- // CT25: how many commits are currently queued for this workspace — the
1367
- // supervisor's drain scheduler reads it to decide whether to keep polling.
1317
+ // How many commits are queued; the drain scheduler reads it to decide whether
1318
+ // to keep polling.
1368
1319
  outboxSize() {
1369
1320
  return this.opts.outbox?.size() ?? 0;
1370
1321
  }
1371
- // CT262: the ONE turn-context fetch. Collapses the companion's old four-fetch
1372
- // choreography (getConversation + getMessage + getAgentSelf + getParticipantAgent)
1373
- // into a single call: the server composes the whole server portion of the
1374
- // `TurnRequest` — systemPrompt, per-turn prompt + vision content, effective
1375
- // run-config, `HostPolicy`, prior session, the user MCP definitions to resolve
1376
- // locally, plus the anchor/title + trigger-message summary the host needs.
1377
- // Agent-PAT authed; the workspace is implied by the PAT.
1378
- // CT714: `turnId` is the host-minted id for THIS turn, passed so the server can
1379
- // bind the minted turn token to it — the turn-control surface then rejects a
1380
- // token whose turn has ended. The dispatcher mints it before this call and
1381
- // reuses the same value on its active-run PATCH, so the token's turn id and the
1382
- // pair's `active_turn_id` agree.
1383
- // CT1380: `resumed` says this dispatch is a REPLAY of an interrupted turn, so
1384
- // the server should hand back `turnSeqFloor` — the turn's committed seq
1385
- // high-water mark. Only the supervisor knows (it minted the id or recovered
1386
- // it), so it is passed down rather than inferred here. It gates the COST of
1387
- // the aggregate, never the tenancy scoping, which the route applies
1388
- // unconditionally.
1389
- // `fresh` asks for the context a session that holds NOTHING needs: the read
1390
- // from the start of the conversation, the agent's own words included, and
1391
- // the first-turn material — what the adapter runs on when it refused to
1392
- // resume the stored session and is starting a fresh one.
1393
- // CT1543: `episode` is the dispatch event's own, echoed back so the server can
1394
- // tell this frame from a later wake on the same message. Omitted when the
1395
- // event didn't carry one, which is the only thing an older event can mean.
1396
- // CT1657: `signal` is the turn's own abort. It matters here more than on any
1397
- // other read, because this call now waits up to ~8s across five attempts: a
1398
- // Stop landing in that window used to leave the whole schedule running, and
1399
- // the person watched a turn they had already stopped keep trying.
1322
+ // The ONE turn-context fetch: the server composes the whole server portion of
1323
+ // the `TurnRequest`. Agent-PAT authed; the workspace is implied by the PAT.
1324
+ // - `turnId`: the host-minted id the server binds the turn token to, so the
1325
+ // turn-control surface rejects a token whose turn has ended.
1326
+ // - `resumed`: a REPLAY of an interrupted turn, so the server returns
1327
+ // `turnSeqFloor`. Only the supervisor knows. It gates the cost of the
1328
+ // aggregate, never the tenancy scoping.
1329
+ // - `fresh`: the context a session holding NOTHING needs (the whole read,
1330
+ // own words included, plus first-turn material), for an adapter that
1331
+ // refused to resume the stored session.
1332
+ // - `episode`: the dispatch event's own, so the server can tell this frame
1333
+ // from a later wake on the same message. Omitted by an older event.
1334
+ // - `signal`: the turn's abort, so a Stop cuts the ~8s retry schedule short.
1400
1335
  getTurnContext(conversationId, messageId2, turnId, resumed, fresh, episode, signal) {
1401
1336
  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))}` : "");
1402
1337
  return this.request("GET", `/api/agent/turn-context?${q}`, void 0, {
@@ -1404,9 +1339,8 @@ var init_api = __esm({
1404
1339
  ...signal ? { signal } : {}
1405
1340
  });
1406
1341
  }
1407
- // CT1380: the local half of a resumed turn's seq floor (see
1408
- // `Outbox.maxSeqForTurn`). 0 when no outbox is configured — a one-shot CLI or
1409
- // test has no durable queue, so nothing can be hiding in it.
1342
+ // The local half of a resumed turn's seq floor (`Outbox.maxSeqForTurn`). 0
1343
+ // with no outbox: nothing can be hiding in a queue that doesn't exist.
1410
1344
  outboxMaxSeqForTurn(turnId) {
1411
1345
  return this.opts.outbox?.maxSeqForTurn(turnId) ?? 0;
1412
1346
  }
@@ -1422,13 +1356,11 @@ var init_api = __esm({
1422
1356
  (e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
1423
1357
  );
1424
1358
  }
1425
- // CT1677: ADOPT the issuance a turn context carried as the execution source of
1426
- // the work about to run on it. Made once per context, before preparation emits
1427
- // or queues anything, so every row that work commits can name a source the
1428
- // server has durably recorded and will never reap. Retried like any write: the
1429
- // server's adoption is idempotent. A 409 `execution_source_unavailable` is a
1430
- // refusal — nothing to adopt, the reaper got there first — and the caller
1431
- // fetches a fresh context rather than working on this one.
1359
+ // ADOPT a turn context's issuance as the execution source of its work, once,
1360
+ // before preparation emits or queues anything, so every row can name a source
1361
+ // the server durably recorded and will never reap. Adoption is idempotent, so
1362
+ // retried. A 409 `execution_source_unavailable` means the reaper got there
1363
+ // first; the caller fetches a fresh context.
1432
1364
  adoptExecutionSource(issuanceId, body, signal) {
1433
1365
  return this.request(
1434
1366
  "POST",
@@ -1437,10 +1369,10 @@ var init_api = __esm({
1437
1369
  signal ? { retry: true, signal } : { retry: true }
1438
1370
  );
1439
1371
  }
1440
- // CT1680: the launcher mints the runner credential for one dispatch, on its
1441
- // PAT and from its device, and hands it to the runner in place of both. A
1442
- // retry is safe: the server returns the same runner id and retires the
1443
- // credential the lost response carried.
1372
+ // The launcher mints the runner credential for one dispatch, on its PAT and
1373
+ // from its device, and hands it to the runner in place of both. A retry is
1374
+ // safe: the server returns the same runner id and retires the credential the
1375
+ // lost response carried.
1444
1376
  mintRunnerCredential(body) {
1445
1377
  return this.request("POST", "/api/agent/runner-credentials", body, { retry: true });
1446
1378
  }
@@ -1460,7 +1392,7 @@ var init_api = __esm({
1460
1392
  // invocation. The server records the effect and receipt in one transaction;
1461
1393
  // an ok:false response is a recorded refusal for the tool to report.
1462
1394
  // Retries retain the caller's callId and recover the same receipt, even
1463
- // when the turn settled between attempts (CT1447): the route consults the
1395
+ // when the turn settled between attempts: the route consults the
1464
1396
  // receipt before it refuses an ended turn, so a 4xx proves no attempt wrote.
1465
1397
  // The caller's signal bounds how long the model waits for the tool result.
1466
1398
  turnAct(workspaceId, turnId, act, signal) {
@@ -1471,36 +1403,23 @@ var init_api = __esm({
1471
1403
  signal ? { retry: true, signal } : { retry: true }
1472
1404
  );
1473
1405
  }
1474
- // CT1292: RENEW THE TURN'S LEASE — ask the server whether this turn is still
1475
- // running. The dispatcher calls this on a cadence for the life of the SDK loop,
1476
- // and out of cadence the moment a commit is refused, so a loop whose turn was
1477
- // terminalized underneath it stops instead of running to the six-hour cap.
1406
+ // RENEW THE TURN'S LEASE: is this turn still running? Called on a cadence for
1407
+ // the life of the SDK loop and the moment a commit is refused, so a loop whose
1408
+ // turn was terminalized underneath it stops.
1478
1409
  //
1479
- // Single-shot and NEVER retried, on purpose. A renewal is a question about
1480
- // right now, not a durable write: a failed one has no answer worth replaying,
1481
- // and the next tick asks again in a few seconds anyway. Retrying would only
1482
- // stretch one tick over the next one's slot.
1410
+ // Single-shot, NEVER retried: a failed renewal has no answer worth replaying
1411
+ // and the next tick asks again; retrying stretches one tick over the next.
1483
1412
  //
1484
- // ALL FAILURE IS `unknown`, and this is the safety property the whole feature
1485
- // rests on. The only thing that may stop a running loop is the server SAYING
1486
- // the turn is over; a 500, a dropped connection, an expired token or a body we
1487
- // cannot parse says nothing about the turn, and reading any of them as death
1488
- // would kill healthy turns every time the API hiccups — strictly worse than the
1489
- // gap this closes. So the failure direction is fixed here, at the boundary,
1490
- // rather than left to each caller to remember.
1413
+ // ALL FAILURE IS `unknown`. Only the server SAYING the turn is over may stop a
1414
+ // running loop; a 500, a dropped connection, an expired token or an
1415
+ // unparseable body says nothing about the turn, and reading it as death would
1416
+ // kill healthy turns on every API hiccup. Fixed here, at the boundary, so no
1417
+ // caller has to remember.
1491
1418
  //
1492
- // AND IT IS DEADLINED (Codo's blocking finding #1). `fetch` has no default
1493
- // timeout, so a request that neither resolves nor rejects — a black-holed
1494
- // connection, a half-open socket surviving a network change — would hang this
1495
- // promise forever. That is not merely a slow probe: the caller single-flights
1496
- // on it, so one stalled request latches the check permanently, every later
1497
- // cadence tick returns immediately, and the loop this exists to stop runs to
1498
- // the six-hour cap even after connectivity comes back — the exact defect the
1499
- // task is about, reintroduced through the mechanism meant to close it.
1500
- //
1501
- // The deadline turns that into an ordinary `unknown`: the request rejects, the
1502
- // in-flight latch clears, and the NEXT tick asks again. Well under the renewal
1503
- // cadence so a stalled probe is finished with before its successor is due.
1419
+ // DEADLINED, because `fetch` has no default timeout: a black-holed request
1420
+ // would hang forever, and the caller single-flights on it, so one stall would
1421
+ // latch the check and let the loop run to the cap. The deadline makes it an
1422
+ // ordinary `unknown` and the next tick asks again.
1504
1423
  async checkTurnLease(workspaceId, conversationId, agentId, turnId) {
1505
1424
  const q = `turnId=${encodeURIComponent(turnId)}`;
1506
1425
  try {
@@ -1509,11 +1428,7 @@ var init_api = __esm({
1509
1428
  `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/turn-lease?${q}`,
1510
1429
  void 0,
1511
1430
  {
1512
- // CT1657: the one GET that opts OUT of the default retry. The reasoning
1513
- // is right above — a failed probe has no answer worth replaying and the
1514
- // next tick asks again in seconds, so retrying inside this one would
1515
- // stretch a tick over its successor's slot, which is what the deadline
1516
- // exists to prevent.
1431
+ // The one GET that opts OUT of the default retry; see above.
1517
1432
  retry: false,
1518
1433
  signal: AbortSignal.timeout(this.opts.leaseCheckTimeoutMs ?? LEASE_CHECK_TIMEOUT_MS)
1519
1434
  }
@@ -1547,9 +1462,8 @@ var init_api = __esm({
1547
1462
  }
1548
1463
  return this.request("PATCH", path, body);
1549
1464
  }
1550
- // CT93: send-or-enqueue for an active-run flag write, with last-writer-wins
1551
- // across the (conversation, agent) pair. Mirrors `durableCommit`, with two
1552
- // deliberate differences:
1465
+ // Send-or-enqueue for an active-run flag write, last-writer-wins per
1466
+ // (conversation, agent) pair. Like `durableCommit`, except:
1553
1467
  // - No abort signal is threaded. The clear is the turn's teardown and must
1554
1468
  // land even when the turn was *cancelled* (the abort signal is set) — a
1555
1469
  // cancelled turn still has to drop its "working" indicator.
@@ -1603,9 +1517,6 @@ var init_api = __esm({
1603
1517
  );
1604
1518
  }
1605
1519
  }
1606
- // CT29: per-device liveness moved off the per-workspace agent PAT and onto the
1607
- // device token — see `DeviceApi.heartbeat`. There is no agent-PAT heartbeat
1608
- // anymore.
1609
1520
  // Commit the agent's first/last words (message) or a runtime_notice through
1610
1521
  // the public messages endpoint. Intermediate working text uses reportText.
1611
1522
  // Attribution comes from the agent credential; a new speech row can offer
@@ -1689,36 +1600,26 @@ var init_host_policy = __esm({
1689
1600
  "packages/agent-runtime/src/host-policy.ts"() {
1690
1601
  "use strict";
1691
1602
  hostPolicySchema = z5.object({
1692
- // Host filesystem + shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the
1693
- // notebook read/write pair, git-worktree tools, and the `REPL` host code-exec
1694
- // tool. Off under the locked assistant surface (today's `DISALLOWED_TOOLS`);
1695
- // on under `coding` mode.
1603
+ // Host filesystem and shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the notebook pair,
1604
+ // git-worktree tools and `REPL`. Off on the locked assistant surface; on in the coding posture.
1696
1605
  hostFs: z5.boolean(),
1697
- // Public web (today's `WebSearch` / `WebFetch`, the `DEFAULT_WEB_TOOLS`). Public
1698
- // web, not host reach — granted by default today, but expressible as a grant.
1606
+ // Public web (`WebSearch` / `WebFetch`). Not host reach; a grant like the rest.
1699
1607
  web: z5.boolean(),
1700
- // Browser automation (the Playwright MCP surface). Varies by host: a companion has
1701
- // it, the house executor does not (CT230).
1608
+ // Browser automation (the Playwright MCP surface). Varies by host.
1702
1609
  browser: z5.boolean(),
1703
- // User-configured MCP servers permitted. False for the house executor
1704
- // (CT227: Cabane agents run no user MCP servers), true for a personal companion.
1610
+ // User-configured MCP servers permitted.
1705
1611
  userMcp: z5.boolean(),
1706
- // Subagent / sub-dispatch (`Task` / `Agent`). A per-host GRANT (see the CT261
1707
- // amendment above): `false` on the locked assistant/house surface (banned via
1708
- // `DISALLOWED_TOOLS`), `true` under coding / custom where the fan-out is left to
1709
- // the coding allow-hook (which reaches sub-agent tool calls, CT680) or the user
1710
- // allowlist. The subagent completes within the turn, so
1711
- // it's not the turn-model invariant `scheduling` is.
1612
+ // Subagent dispatch (`Task` / `Agent`) — a grant: `false` on the locked surface, `true` in the
1613
+ // coding posture, where the coding allow-hook reaches sub-agent tool calls. A subagent completes
1614
+ // within the turn, so it's not an invariant like `scheduling`.
1712
1615
  subagents: z5.boolean(),
1713
1616
  // ── Hard platform invariants — always denied, never granted ────────────────
1714
- // Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*`
1715
- // families, `Monitor`, `Workflow`, …). A turn is one query that resolves when
1716
- // `result` fires; a scheduled callback fires after the reply window has closed
1717
- // and strands the agent (the CT155/CT156 rule).
1617
+ // Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*` families,
1618
+ // `Monitor`, `Workflow`, …). A turn is one query that resolves on `result`; a scheduled callback
1619
+ // fires after the reply window has closed and strands the agent.
1718
1620
  scheduling: z5.literal("never"),
1719
- // Human-facing UI prompts (`AskUserQuestion`). A Cabane conversation has no
1720
- // handler to answer a structured prompt, so the call hangs the turn
1721
- // (`UNSUPPORTED_TOOLS`). The agent asks in its reply instead.
1621
+ // Human-facing UI prompts (`AskUserQuestion`). Nothing in a Cabane conversation can answer a
1622
+ // structured prompt, so the call hangs the turn. The agent asks in its reply instead.
1722
1623
  uiPrompts: z5.literal("never")
1723
1624
  });
1724
1625
  }
@@ -1731,59 +1632,40 @@ var init_turn_event = __esm({
1731
1632
  "packages/agent-runtime/src/turn-event.ts"() {
1732
1633
  "use strict";
1733
1634
  turnEventSchema = z6.discriminatedUnion("type", [
1734
- // The runtime's opaque session state, emitted when the adapter learns it (e.g.
1735
- // the SDK `system/init` frame). The platform stores `state` verbatim per
1736
- // (conversation, agent) and hands it back on the next turn; only the adapter
1737
- // knows what it means. Today's companion captures the raw SDK session id here; a
1738
- // future adapter may encode more (e.g. `{sdkSessionId, cwd}`) — still one
1739
- // opaque string to the platform.
1635
+ // The runtime's opaque session state, emitted when the adapter learns it (e.g. the SDK
1636
+ // `system/init` frame). The platform stores `state` verbatim per (conversation, agent) and
1637
+ // hands it back next turn; only the adapter knows what it means.
1740
1638
  //
1741
- // CT315: `degraded: true` means the adapter started this FRESH session despite
1742
- // a stored one it was handed — a cwd/directory mismatch (the SJ527 guard) or
1743
- // legacy/foreign state it refused to resume. It is NOT set for a genuine
1744
- // first turn (`no_session`). The signal matters because the server built this
1745
- // turn's catch-up manifest assuming the prior session carried everything up to
1746
- // the high-water mark (CT298's two-channel invariant); a degrade breaks that,
1747
- // so the mark now over-reaches an empty session. The host relays `degraded`
1748
- // on settle and the server rewinds the mark, so the NEXT turn rebuilds a full
1749
- // catch-up (this failing turn is unavoidably lossy — the degrade is only known
1750
- // on the companion, after the server committed the manifest). Runtime-neutral: a
1751
- // plain boolean, not a runtime-specific reason string (that stays in the
1752
- // adapter's `onWarn` log). Additive + optional — an old receiver ignores it.
1639
+ // `degraded: true`: the adapter started this fresh session despite a stored one (a cwd
1640
+ // mismatch, or legacy/foreign state it refused to resume) — never for a genuine first turn.
1641
+ // The server built this turn's catch-up manifest assuming the prior session held everything up
1642
+ // to the high-water mark; a degrade breaks that. The host relays `degraded` on settle and the
1643
+ // server rewinds the mark, so the next turn rebuilds a full catch-up (this turn is unavoidably
1644
+ // lossy — the degrade is known only after the manifest was committed). A plain boolean; the
1645
+ // runtime-specific reason stays in the adapter's `onWarn` log. Optional.
1753
1646
  z6.object({
1754
1647
  type: z6.literal("session"),
1755
1648
  state: z6.string(),
1756
1649
  degraded: z6.boolean().optional()
1757
1650
  }),
1758
- // One readable thinking summary. Maps `onThinking({ text })`. Transient —
1759
- // surfaced live, never persisted as durable content.
1651
+ // One readable thinking summary. Transient — surfaced live, never persisted as content.
1760
1652
  z6.object({ type: z6.literal("thinking"), text: z6.string() }),
1761
- // Assistant text. Maps `onAssistantText({ text, final })` — `text`→`body`,
1762
- // `final`→`terminal`. `terminal: false` is interim narration (commits as a
1763
- // `progress` row); `terminal: true` is the turn's closing reply (commits as
1764
- // the `final` row).
1653
+ // Assistant text. `terminal: false` is interim narration (commits as `progress`);
1654
+ // `terminal: true` is the turn's closing reply (commits as `final`).
1765
1655
  z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
1766
- // Runtime/provider-authored prose about the turn — a failure's explanation, or
1767
- // a line the harness wrote on a turn it recovered (CT1750). Unlike
1768
- // `text`, this is not the agent's narration or reply: the pump persists it as
1769
- // `runtime_notice`, and the transcript renders it in the shared SystemNote
1770
- // voice. Adapters emit it only from positive runtime evidence; the web never
1771
- // classifies English strings.
1656
+ // Runtime/provider-authored prose about the turn — a failure's explanation, or a line the
1657
+ // harness wrote on a turn it recovered. Not the agent's words: the pump persists it as
1658
+ // `runtime_notice`, drawn in the SystemNote voice. Adapters emit it only from positive runtime
1659
+ // evidence; the web never classifies English strings.
1772
1660
  z6.object({ type: z6.literal("runtime_notice"), body: z6.string() }),
1773
- // A tool-activity transition. Maps `onToolActivity` — `toolUseId`→`id`,
1774
- // `toolName`→`name` (already prefix-stripped: `cabane_read`, not
1775
- // `mcp__cabane__cabane_read`), `summary` is the short card label. `phase`
1776
- // pairs `start` with a later `done`/`error` on the same `id`.
1661
+ // A tool-activity transition. `name` is prefix-stripped (`cabane_read`, not
1662
+ // `mcp__cabane__cabane_read`); `summary` is the short card label; `phase` pairs `start` with a
1663
+ // later `done`/`error` on the same `id`.
1777
1664
  //
1778
- // `input`/`result` are typed `unknown` (the raw tool payloads), widening
1779
- // today's already-truncated `input: string` / `result: string` — see
1780
- // docs/turn-protocol.md: rendering/truncation for the tool log is a
1781
- // pump/consumer concern applied to the value, not baked into the event type.
1782
- // CT496: `mcpServer` is the MCP server the call came from (`cabane`,
1783
- // `playwright`, …), derived off the raw `mcp__<server>__` name before `name`
1784
- // dropped the prefix; null for a host / built-in tool. The client tags Cabane
1785
- // MCP calls (`=== 'cabane'`) with a subtle glyph. Nullable + optional so a
1786
- // pre-CT496 producer that never sets it is unaffected (treated as null).
1665
+ // `input`/`result` are `unknown` (the raw tool payloads): rendering and truncation for the tool
1666
+ // log are a consumer concern (docs/turn-protocol.md). `mcpServer` is the MCP server the call
1667
+ // came from (`cabane`, `playwright`, …), derived before `name` dropped the prefix; null for a
1668
+ // host tool. Optional, read as null when absent.
1787
1669
  z6.object({
1788
1670
  type: z6.literal("tool"),
1789
1671
  id: z6.string(),
@@ -1794,45 +1676,25 @@ var init_turn_event = __esm({
1794
1676
  result: z6.unknown().optional(),
1795
1677
  mcpServer: z6.string().nullable().optional()
1796
1678
  }),
1797
- // The turn's terminal outcome. Maps the `{ ok, reason }` both loops return
1798
- // inline. `ok:false` carries a machine reason (`no_session`, an error code);
1799
- // the host's clear-PATCH surfaces it as `errorReason`.
1800
- //
1801
- // CT277: an optional `usage` — the SDK result's token counts, which were
1802
- // dropped on the floor before. `inputTokens` is the full context the model saw
1803
- // (uncached + cache-read + cache-creation input), so it doubles as the
1804
- // context-window cost; `outputTokens` the generated tokens. Optional ⇒
1805
- // backward-compatible: an old companion / adapter that never sets it, and a
1806
- // receiver that never reads it, are unaffected (the turn's token columns stay
1807
- // null → the UI shows `—`).
1679
+ // The turn's terminal outcome. `ok:false` carries a machine reason (`no_session`, an error
1680
+ // code), surfaced by the host's clear-PATCH as `errorReason`.
1808
1681
  //
1809
- // CT597: two optional cache-token counts — the slice of `inputTokens` that was
1810
- // a cache read vs. a cache creation, when the runtime breaks it out (claude-code
1811
- // does). Additive + optional: a runtime that doesn't report them omits them, and
1812
- // the server just leaves the cache columns null. Captured now because honest
1813
- // costing later prices a cache-read token far below a fresh input token.
1682
+ // `usage`: the SDK result's token counts. `inputTokens` is the full context the model saw
1683
+ // (uncached + cache-read + cache-creation). The cache counts, when the runtime breaks them out,
1684
+ // are a subset of it, kept because a cache-read token costs far less. All optional; absent
1685
+ // leaves the columns null and the UI shows `—`.
1814
1686
  //
1815
- // CT699: `inputTokens` (and the cache slice) is a BILLING quantity — for
1816
- // claude-code/codex it's the runtime's CUMULATIVE total summed across every model
1817
- // request in the agentic turn, so it grows with the tool-call count and is NOT
1818
- // "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
1819
- // read and `contextWindow` the window it is measured against, both as the
1820
- // harness reported them (claude-code's `getContextUsage`, or its final
1821
- // request's input and `modelUsage` window when that call gives nothing). A
1822
- // runtime that can't source them (codex's cumulative-only usage) omits both,
1823
- // and no surface shows a reading for that turn.
1687
+ // `inputTokens` is a billing quantity — for claude-code/codex the cumulative total across every
1688
+ // model request in the turn, growing with tool calls, not "how full is the window".
1689
+ // `contextTokens` is the occupancy read and `contextWindow` its window, as the harness reported
1690
+ // them (claude-code's `getContextUsage`, else its final request's input and `modelUsage`
1691
+ // window). A runtime that can't source them (codex) omits both.
1824
1692
  //
1825
- // CT601: two more optional carry-homes on the terminal result, alongside
1826
- // `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
1827
- // claude-code learns it from the `system/init` frame mid-run (even for a
1828
- // subscription-default token, where the configured model is null), codex records
1829
- // the model the thread was started with. `resolvedConfig` is the resolved
1830
- // per-runtime knobs the harness reported (`{ reasoningEffort }` for codex; none
1831
- // for claude-code today). Both only known after the run streams — so they ride
1832
- // the terminal event home, the host relays them on settle, and the server writes
1833
- // `message_turn_metadata.resolved_model` / `resolved_config`. Optional ⇒
1834
- // backward-compatible: an old adapter/companion omits them, a cancel has no result
1835
- // event at all, and the columns stay null → the UI shows `—`.
1693
+ // `resolvedModel` is the concrete model the runtime ran (claude-code learns it from
1694
+ // `system/init` mid-run, even for a subscription default; codex records the model the thread
1695
+ // started with); `resolvedConfig` the knobs the harness reported (`{ reasoningEffort }` for
1696
+ // codex). Known only after the run streams, so they ride this event home and the host relays
1697
+ // them on settle. A cancel has no result event at all.
1836
1698
  z6.object({
1837
1699
  type: z6.literal("result"),
1838
1700
  ok: z6.boolean(),
@@ -1844,9 +1706,8 @@ var init_turn_event = __esm({
1844
1706
  cacheCreationTokens: z6.number().optional(),
1845
1707
  contextTokens: z6.number().optional(),
1846
1708
  contextWindow: z6.number().optional(),
1847
- // CT1751: the rest of the harness's own context reading, when it gave
1848
- // one: where it will compact, its `/context` breakdown, and every
1849
- // compaction it reported during the turn. Claude Code only.
1709
+ // The rest of the harness's context reading, when it gave one: where it will compact, its
1710
+ // `/context` breakdown, and every compaction it reported. Claude Code only.
1850
1711
  contextCompactAt: z6.number().optional(),
1851
1712
  contextCategories: z6.array(
1852
1713
  z6.object({
@@ -1869,10 +1730,9 @@ var init_turn_event = __esm({
1869
1730
  thinking: z6.string().optional(),
1870
1731
  reasoningEffort: z6.string().optional()
1871
1732
  }).optional(),
1872
- // CT1275: the harness-reported MCP inventory from this turn's init frame.
1873
- // This is deliberately diagnostic-only: names, statuses and a count, never
1874
- // server definitions, credentials or session ids. `initReceived:false`
1875
- // distinguishes a missing init frame from a real empty inventory.
1733
+ // The harness-reported MCP inventory from this turn's init frame. Diagnostic only: names,
1734
+ // statuses and a count — never server definitions, credentials or session ids.
1735
+ // `initReceived:false` tells a missing init frame from a real empty inventory.
1876
1736
  mcpInventory: z6.object({
1877
1737
  initReceived: z6.boolean(),
1878
1738
  servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
@@ -1989,9 +1849,8 @@ function isContentBearingEvent(event) {
1989
1849
  return normalizeCommitText(event.text) !== "";
1990
1850
  case "tool":
1991
1851
  return true;
1992
- // CT1750: a runtime notice is shown, but it is the harness speaking about
1993
- // the turn, not the turn's output — a success whose only output is a CLI
1994
- // line still produced nothing.
1852
+ // A runtime notice is shown, but it is the harness speaking about the turn, not its output — a
1853
+ // success whose only output is a CLI line still produced nothing.
1995
1854
  case "runtime_notice":
1996
1855
  case "session":
1997
1856
  case "result":
@@ -2073,11 +1932,10 @@ var init_turn_request = __esm({
2073
1932
  mcpUrl: z8.string(),
2074
1933
  bearer: z8.string(),
2075
1934
  activeConversationId: z8.string(),
2076
- // Scoped turn-control MCP URL. Codex/opencode mount it as cabane_companion
2077
- // with the bearer and active-conversation header. It supplies ask,
2078
- // withdraw_ask, wake_me, cancel_wake and skip_turn. Claude Code mounts the
2079
- // companion's in-process instance instead. Optional for compatibility;
2080
- // the companion populates it in build-options.ts.
1935
+ // Scoped turn-control MCP URL. Codex and opencode mount it as `cabane_companion` with the bearer
1936
+ // and active-conversation header, for ask, withdraw_ask, wake_me, cancel_wake and skip_turn.
1937
+ // Claude Code mounts the companion's in-process instance instead. Optional; the companion
1938
+ // populates it.
2081
1939
  turnControlUrl: z8.string().optional(),
2082
1940
  // Workspace coordinate retained for envelope compatibility. Current
2083
1941
  // adapters use MCP, whose workspace operations take workspaceId explicitly.
@@ -2090,19 +1948,17 @@ var init_turn_request = __esm({
2090
1948
  cwd: z8.string().optional(),
2091
1949
  env: z8.record(z8.string(), z8.string()).optional(),
2092
1950
  mcpServers: resolvedMcpServersSchema.optional(),
2093
- // CT1698: the values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` →
2094
- // complete header value). The adapter merges them into the harness
2095
- // process's environment and nowhere else; the host never merges them into
2096
- // its own. Absent when no server references one.
1951
+ // The values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` → complete header value). The
1952
+ // adapter merges them into the harness process's environment and nowhere else; the host never
1953
+ // merges them into its own. Absent when no server references one.
2097
1954
  secretEnv: z8.record(z8.string(), z8.string()).optional(),
2098
- // CT289: machine-local claude-code adapter knobs the operator sets on a
2099
- // companion they run themselves — the auto-memory escape hatch. `autoMemory:
2100
- // true` opts back into Claude Code's auto-memory (governed by the operator's
2101
- // own `.claude/settings.json`); absent/false leaves the adapter's force-off
2102
- // default in place (see `buildClaudeCodeOptions`).
1955
+ // Machine-local claude-code knobs for an operator running their own companion — the auto-memory
1956
+ // escape hatch. `autoMemory: true` opts back into Claude Code's auto-memory (governed by the
1957
+ // operator's `.claude/settings.json`); absent/false keeps the adapter's force-off default
1958
+ // (`buildClaudeCodeOptions`).
2103
1959
  claudeCode: z8.object({ autoMemory: z8.boolean().optional() }).optional()
2104
1960
  }),
2105
- // Host-owned injected servers (host-filled) — e.g. the send server.
1961
+ // Host-owned injected servers (host-filled).
2106
1962
  extra: z8.object({
2107
1963
  mcpServers: hostInjectedServersSchema
2108
1964
  })
@@ -2190,8 +2046,8 @@ async function emitToolResults(msg, emit, pending, onError) {
2190
2046
  // result payload off the block.
2191
2047
  input: started?.input,
2192
2048
  result: block.content,
2193
- // CT496: carry the server captured at `start` (the pretty name here has
2194
- // already lost its prefix). Null when the start wasn't seen.
2049
+ // The server captured at `start` (the pretty name here has lost its prefix). Null when the
2050
+ // start wasn't seen.
2195
2051
  mcpServer: started?.mcpServer ?? null
2196
2052
  },
2197
2053
  onError
@@ -2259,24 +2115,18 @@ function summarizeToolInput(input) {
2259
2115
  }
2260
2116
  return "";
2261
2117
  }
2262
- function summarizeToolIo(input) {
2118
+ function renderToolIo(input) {
2263
2119
  if (input === void 0 || input === null) return "";
2264
- if (typeof input === "string") {
2265
- const trimmed = input.trim();
2266
- return trimmed.length > 0 ? truncate(trimmed, TOOL_IO_MAX_CHARS) : "";
2267
- }
2120
+ if (typeof input === "string") return input.trim();
2268
2121
  try {
2269
- return truncate(JSON.stringify(input), TOOL_IO_MAX_CHARS);
2122
+ return JSON.stringify(input) ?? "";
2270
2123
  } catch {
2271
2124
  return "";
2272
2125
  }
2273
2126
  }
2274
- function summarizeToolResult(content) {
2127
+ function renderToolResult(content) {
2275
2128
  if (content === void 0 || content === null) return "";
2276
- if (typeof content === "string") {
2277
- const trimmed = content.trim();
2278
- return trimmed.length > 0 ? truncate(trimmed, TOOL_IO_MAX_CHARS) : "";
2279
- }
2129
+ if (typeof content === "string") return content.trim();
2280
2130
  if (Array.isArray(content)) {
2281
2131
  const parts = [];
2282
2132
  let sawContentEntry = false;
@@ -2291,12 +2141,12 @@ function summarizeToolResult(content) {
2291
2141
  }
2292
2142
  }
2293
2143
  const joined = parts.join("\n").trim();
2294
- if (joined.length > 0) return truncate(joined, TOOL_IO_MAX_CHARS);
2144
+ if (joined.length > 0) return joined;
2295
2145
  if (sawContentEntry || content.length === 0) return "";
2296
2146
  }
2297
2147
  if (typeof content !== "object") return "";
2298
2148
  if (!Array.isArray(content) && Object.keys(content).length === 0) return "";
2299
- return summarizeToolIo(content);
2149
+ return renderToolIo(content);
2300
2150
  }
2301
2151
  function truncate(s, max) {
2302
2152
  if (s.length <= max) return s;
@@ -2320,15 +2170,19 @@ function sinkEmitter(sink, _onError) {
2320
2170
  return;
2321
2171
  case "tool":
2322
2172
  if (sink.onToolActivity) {
2173
+ const input = renderToolIo(event.input);
2174
+ const result = event.result === void 0 ? null : renderToolResult(event.result);
2323
2175
  await sink.onToolActivity({
2324
2176
  toolUseId: event.id,
2325
2177
  toolName: event.name,
2326
2178
  summary: event.summary,
2327
- // SJ529: render the raw payloads for the tool log here — the single
2328
- // truncation site (CT255 consumer concern).
2329
- input: summarizeToolIo(event.input),
2330
- result: event.result === void 0 ? "" : summarizeToolResult(event.result),
2331
- // CT496: carry the originating MCP server (null when absent / a host tool).
2179
+ input: truncate(input, TOOL_IO_MAX_CHARS),
2180
+ result: result === null ? "" : truncate(result, TOOL_IO_MAX_CHARS),
2181
+ // Only when the event carried an input: a completion whose start this process never saw has
2182
+ // none, and a zero would overwrite the size its start recorded.
2183
+ ...event.input === void 0 ? {} : { inputChars: input.length },
2184
+ ...result === null ? {} : { resultChars: result.length },
2185
+ // The originating MCP server (null when absent / a host tool).
2332
2186
  mcpServer: event.mcpServer ?? null,
2333
2187
  phase: event.phase
2334
2188
  });
@@ -2400,6 +2254,8 @@ var init_pump = __esm({
2400
2254
  summary: evt.summary,
2401
2255
  input: evt.input,
2402
2256
  result: evt.result,
2257
+ ...evt.inputChars !== void 0 ? { inputChars: evt.inputChars } : {},
2258
+ ...evt.resultChars !== void 0 ? { resultChars: evt.resultChars } : {},
2403
2259
  mcpServer: evt.mcpServer,
2404
2260
  phase: evt.phase,
2405
2261
  seq
@@ -2432,22 +2288,18 @@ var init_pump = __esm({
2432
2288
  // The turn's final reply text, captured for the dashboard feed / logging.
2433
2289
  finalReplyBody = null;
2434
2290
  emittedFinalSource = "none";
2435
- // CT1383: provider tool id → the persisted identity of its CURRENT call. The
2436
- // provider's id is only a lookup key: it is unique within one adapter
2437
- // invocation at best (Codex restarts at `item_0` on every invocation, and a
2438
- // resumed turn keeps its turn id), so the row and the outbox entry are keyed
2439
- // on what the pump mints — a host id for the host's `(turn_id, tool_use_id)`
2440
- // upsert, and a seq for the outbox's `(turnId, seq)` file. Both are minted
2441
- // together; minting only the id would let two calls overwrite each other's
2442
- // queued body.
2291
+ // Provider tool id → the persisted identity of its current call. The provider's id is only a
2292
+ // lookup key, unique within one adapter invocation at best (Codex restarts at `item_0` every
2293
+ // invocation, and a resumed turn keeps its turn id), so the row and the outbox entry are keyed on
2294
+ // what the pump mints: a host id for the host's `(turn_id, tool_use_id)` upsert, and a seq for the
2295
+ // outbox's `(turnId, seq)` file. Minted together; minting only the id would let two calls
2296
+ // overwrite each other's queued body.
2443
2297
  //
2444
- // In memory on purpose: it dies with the process, and so does the runtime.
2445
- // After a crash no in-flight `done` awaits a pre-crash `start` — the resumed
2446
- // invocation starts its calls afresh — so there is nothing to recover.
2298
+ // In memory on purpose: it dies with the process, and so does the runtime. After a crash no
2299
+ // in-flight `done` awaits a pre-crash `start` — the resumed invocation starts its calls afresh.
2447
2300
  toolCallByProviderId = /* @__PURE__ */ new Map();
2448
- // The callback consumer the classification (via `sinkEmitter`) — and the
2449
- // frozen committer test — drive. Each callback applies the choreography and
2450
- // commits through the injected sink.
2301
+ // The callback consumer the classification (via `sinkEmitter`) and the committer test drive.
2302
+ // Each callback applies the choreography and commits through the injected sink.
2451
2303
  sink;
2452
2304
  // Whether a message has landed under this span — first words, at least. A
2453
2305
  // span that spoke settles; one that never did is a pass.
@@ -2540,7 +2392,7 @@ var init_descriptions = __esm({
2540
2392
  wake_me: {
2541
2393
  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.",
2542
2394
  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.",
2543
- 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").`
2395
+ 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.`
2544
2396
  },
2545
2397
  withdraw_ask: {
2546
2398
  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.",
@@ -2564,7 +2416,7 @@ var init_prompt_guidance = __esm({
2564
2416
  ask: "put a structured question to a person",
2565
2417
  cancel_wake: "cancel your own armed wake in this conversation",
2566
2418
  skip_turn: "end without posting a reply",
2567
- wake_me: "schedule your own return",
2419
+ wake_me: "schedule your own return; when it fires, say first what you came back to check",
2568
2420
  withdraw_ask: "withdraw an open ask in this conversation with a reason"
2569
2421
  };
2570
2422
  }
@@ -2810,52 +2662,41 @@ function buildClaudeCodeOptions(req, augment) {
2810
2662
  // pinned `model` arrives as a bare SDK id — the host already resolved the
2811
2663
  // namespaced catalog id to it.
2812
2664
  ...model ? { model } : {},
2813
- // SJ530: surface thinking summaries — default adaptive+summarized, and fill in
2814
- // `display:'summarized'` on a configured adaptive/enabled block that omits one.
2815
- // Read from the adapter's own dialect slice (`runtimeOptions['claude-code']`).
2665
+ // Surface thinking summaries: default adaptive+summarized, and fill in `display:'summarized'`
2666
+ // on a configured adaptive/enabled block that omits one. Read from the adapter's dialect slice
2667
+ // (`runtimeOptions['claude-code']`).
2816
2668
  thinking: withThinkingSummaries(readThinking(config.runtimeOptions)),
2817
2669
  ...config.effort ? { effort: config.effort } : {},
2818
2670
  // Never merge arbitrary user `~/.claude` config; without `strictMcpConfig` the
2819
2671
  // user's existing `cabane` MCP registration could shadow ours.
2820
2672
  settingSources: [],
2821
2673
  strictMcpConfig: true,
2822
- // CT289: force auto-memory off unless the operator opted back in (see above).
2674
+ // Auto-memory off unless the operator opted back in (see above).
2823
2675
  ...devControlsAutoMemory ? {} : { settings: { autoMemoryEnabled: false } },
2824
2676
  mcpServers,
2825
2677
  ...cwd ? { cwd } : {},
2826
- // Extra env (a companion prepare hook's tokens/ports; the in-app's debug flags)
2827
- // merged OVER the inherited environment.
2678
+ // Extra env (a companion prepare hook's tokens/ports) merged over the inherited environment.
2828
2679
  //
2829
- // CT1503: and the background-tasks kill switch LAST, so nothing in
2830
- // `process.env` or `req.local.env` can turn it back on. Claude Code converts
2831
- // a foreground command that hits its Bash ceiling (600 000 ms) into a
2832
- // background task instead of erroring; the agent then reads a phantom task
2833
- // id inside a live turn. A later turn that resumed such a session was then
2834
- // seen getting every tool call aborted with kind `background` — which the CLI
2835
- // renders as "The user doesn't want to take this action right now", a
2836
- // cancellation wearing a refusal's words. The automatic conversion is
2837
- // REPRODUCED and is what this flag fixes; that the stopped task is what
2838
- // poisoned the resumed session is an INFERENCE from one incident, consistent
2839
- // with the transcript but never reproduced, and this change does not rest on
2840
- // it. `Dl()` reads this variable on the Bash tool's own call
2841
- // path and sets `canAutoBackground: false` and `turnAbortBackgrounds: false`
2842
- // together, so a timed-out command comes back as a readable error instead.
2680
+ // The background-tasks kill switch goes last, so nothing in `process.env` or `req.local.env`
2681
+ // turns it back on. Claude Code converts a foreground command that hits its Bash ceiling
2682
+ // (600 000 ms) into a background task instead of erroring, and the agent then holds a phantom
2683
+ // task id inside a live turn; a resumed session was seen with every tool call aborted as
2684
+ // `background` ("The user doesn't want to take this action right now"). The conversion is
2685
+ // reproduced and fixed by this flag; that it poisoned the resumed session is an inference
2686
+ // from one incident, and this change doesn't rest on it. `Dl()` reads the variable on the
2687
+ // Bash tool's call path and sets `canAutoBackground: false` and `turnAbortBackgrounds: false`,
2688
+ // so a timed-out command returns a readable error.
2843
2689
  //
2844
- // The cost is acknowledged and deliberate: it ALSO removes deliberate
2845
- // `run_in_background` (the parameter leaves the tool schema). That is the
2846
- // only switch this harness offers, and there is no operator opt-out here on
2847
- // purpose — an escape hatch would contradict the guarantee. It is a targeted
2848
- // mitigation for the automatic conversion, NOT a cleanup mechanism: step 0
2849
- // measured that this flag changes no process-survival outcome on any turn-end
2850
- // path. Cleanup is its own change.
2690
+ // The cost is deliberate: it also removes `run_in_background` (the parameter leaves the tool
2691
+ // schema). It is the only switch, and there is no opt-out — an escape hatch would contradict
2692
+ // the guarantee. It changes no process-survival outcome on any turn-end path (measured);
2693
+ // cleanup is a separate concern.
2851
2694
  //
2852
- // Note `env` REPLACES the subprocess environment rather than merging (the
2853
- // SDK's own doc on `Options.env`), which is why `process.env` is spread here
2854
- // and why this key is now always present — previously it appeared only when
2855
- // `req.local.env` was set, and an absent `env` inherits `process.env`.
2695
+ // `env` replaces the subprocess environment rather than merging (`Options.env`), which is why
2696
+ // `process.env` is spread here and the key is always present.
2856
2697
  //
2857
- // CT1698: `secretEnv` joins here, and only here — the values the MCP
2858
- // headers above reference. It is this child's environment, not the host's.
2698
+ // `secretEnv` — the values the MCP headers above reference — joins here and only here: it is
2699
+ // this child's environment, not the host's.
2859
2700
  env: {
2860
2701
  ...process.env,
2861
2702
  ...req.local.env,
@@ -3377,8 +3218,7 @@ function makeRequest(overrides2 = {}) {
3377
3218
  systemPrompt: "system",
3378
3219
  prompt: "hi there",
3379
3220
  content: [{ type: "text", text: "hi there" }],
3380
- // CT302: model is required — the adapter rejects a null model, so the
3381
- // conformance fixture carries a real bare SDK id.
3221
+ // The adapter rejects a null model, so the fixture carries a real bare SDK id.
3382
3222
  config: { model: "claude-opus-4-8" },
3383
3223
  policy: CABANE_POLICY,
3384
3224
  session: null,
@@ -3513,7 +3353,7 @@ var init_conformance2 = __esm({
3513
3353
  phase: "start",
3514
3354
  summary: "sleep.md",
3515
3355
  input: { path: "sleep.md" },
3516
- // CT496: derived off the raw `mcp__cabane__` name.
3356
+ // Derived from the raw `mcp__cabane__` name.
3517
3357
  mcpServer: "cabane"
3518
3358
  },
3519
3359
  {
@@ -3531,9 +3371,9 @@ var init_conformance2 = __esm({
3531
3371
  ]
3532
3372
  },
3533
3373
  {
3534
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result;
3535
- // the adapter stops before the closing reply — no final text, no `result`
3536
- // event. CT1295: the server writes the `kind:'stopped'` marker.
3374
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops
3375
+ // before the closing reply — no final text, no `result` event (the server writes the stopped
3376
+ // marker).
3537
3377
  name: "cancel mid-stream",
3538
3378
  request: makeRequest(),
3539
3379
  nativeStream: [
@@ -3585,12 +3425,9 @@ var init_conformance2 = __esm({
3585
3425
  ]
3586
3426
  },
3587
3427
  {
3588
- // CT1144: the empty-runtime anomaly's exact captured trace — `system/init`
3589
- // then a successful `result`, with nothing in between. Distinct from
3590
- // `empty-final` below: THAT turn did work (a tool ran) and just didn't speak;
3591
- // this one produced nothing at all. The adapter's job is unchanged either way
3592
- // — report faithfully what the runtime said — so it must still emit
3593
- // `result{ok:true}` here; the host is what decides that a success with no
3428
+ // The empty-runtime anomaly's captured trace: `system/init` then a successful `result`, nothing
3429
+ // in between. Distinct from `empty-final` (a tool ran, no speech); this produced nothing. The
3430
+ // adapter reports faithfully — `result{ok:true}` — and the host decides that a success with no
3594
3431
  // content-bearing event is a failed turn.
3595
3432
  name: "zero-content success (empty runtime result)",
3596
3433
  request: makeRequest(),
@@ -3598,9 +3435,9 @@ var init_conformance2 = __esm({
3598
3435
  expected: [sessionEvent("s1"), { type: "result", ok: true }]
3599
3436
  },
3600
3437
  {
3601
- // CT1220: on resume, a completed background task can emit a notification and
3602
- // its own zero-token result before the prompt's frames. That result is not the
3603
- // turn boundary; decoding continues through the prompt's reply and real usage.
3438
+ // On resume, a completed background task can emit a notification and its own zero-token result
3439
+ // before the prompt's frames. That result is not the turn boundary; decoding continues through
3440
+ // the reply and real usage.
3604
3441
  name: "task-notification result does not end a resumed turn",
3605
3442
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
3606
3443
  nativeStream: [
@@ -3676,8 +3513,8 @@ var init_conformance2 = __esm({
3676
3513
  ]
3677
3514
  },
3678
3515
  {
3679
- // CT1220: the filter is deliberately narrow. An origin-less zero-token result
3680
- // keeps today's break-on-first-result behavior, even if more frames follow.
3516
+ // The filter is deliberately narrow: an origin-less zero-token result still ends the turn, even
3517
+ // if more frames follow.
3681
3518
  name: "origin-less result still ends a resumed turn",
3682
3519
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
3683
3520
  nativeStream: [
@@ -3748,11 +3585,10 @@ var init_conformance2 = __esm({
3748
3585
  ]
3749
3586
  },
3750
3587
  {
3751
- // CT558/CT592/CT731: a subscription cap. The SDK emits a rejected
3752
- // `rate_limit_event` with a named subscription window; the terminal error result
3753
- // then classifies as the structured `usage_capped` reason. The held provider
3754
- // line independently classifies to the same kind, so it lands as a runtime
3755
- // notice rather than agent-authored progress.
3588
+ // A subscription cap. The SDK emits a rejected `rate_limit_event` with a named subscription
3589
+ // window; the terminal error result classifies as `usage_capped`. The held provider line
3590
+ // independently classifies to the same kind, so it lands as a runtime notice, not agent
3591
+ // progress.
3756
3592
  name: "subscription cap \u2192 usage_capped",
3757
3593
  request: makeRequest(),
3758
3594
  nativeStream: [
@@ -3771,9 +3607,8 @@ var init_conformance2 = __esm({
3771
3607
  ]
3772
3608
  },
3773
3609
  {
3774
- // CT1336: a failed turn can still contain the agent's own partial narration.
3775
- // No text classification means no authorship rewrite, even though the
3776
- // structured result is the same usage cap.
3610
+ // A failed turn can still contain the agent's own partial narration. No text classification
3611
+ // means no authorship rewrite, even though the structured result is the same usage cap.
3777
3612
  name: "partial narration before subscription cap stays progress",
3778
3613
  request: makeRequest(),
3779
3614
  nativeStream: [
@@ -3789,8 +3624,8 @@ var init_conformance2 = __esm({
3789
3624
  ]
3790
3625
  },
3791
3626
  {
3792
- // CT1336: even recognized runtime prose stays authored when its kind
3793
- // disagrees with the structured terminal failure.
3627
+ // Recognized runtime prose stays authored when its kind disagrees with the structured terminal
3628
+ // failure.
3794
3629
  name: "cap notice before server failure stays progress",
3795
3630
  request: makeRequest(),
3796
3631
  nativeStream: [
@@ -3812,10 +3647,9 @@ var init_conformance2 = __esm({
3812
3647
  ]
3813
3648
  },
3814
3649
  {
3815
- // CT1336 review: `classifyErrorText` intentionally searches arbitrary error
3816
- // blobs for status tokens. Assistant narration needs the stricter whole-
3817
- // notice classifier, or an incidental count is de-authored when the turn
3818
- // later fails with the matching provider kind.
3650
+ // `classifyErrorText` deliberately searches arbitrary error blobs for status tokens. Assistant
3651
+ // narration needs the stricter whole-notice classifier, or an incidental count would be
3652
+ // de-authored when the turn later fails with the matching provider kind.
3819
3653
  name: "incidental 5xx-shaped narration before server failure stays progress",
3820
3654
  request: makeRequest(),
3821
3655
  nativeStream: [
@@ -3837,9 +3671,8 @@ var init_conformance2 = __esm({
3837
3671
  ]
3838
3672
  },
3839
3673
  {
3840
- // CT558/CT592: a cap signalled only by the result's
3841
- // `terminal_reason:'blocking_limit'` (no discrete rate-limit event) still
3842
- // classifies as the cap (`usage_capped`).
3674
+ // A cap signalled only by the result's `terminal_reason:'blocking_limit'` (no rate-limit event)
3675
+ // still classifies as `usage_capped`.
3843
3676
  name: "blocking_limit terminal reason \u2192 usage_capped",
3844
3677
  request: makeRequest(),
3845
3678
  nativeStream: [
@@ -3853,8 +3686,8 @@ var init_conformance2 = __esm({
3853
3686
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "usage_capped" }]
3854
3687
  },
3855
3688
  {
3856
- // CT558: an expired login surfaces as an `auth_status` error frame; the failing
3857
- // result classifies as `auth_expired`.
3689
+ // An expired login surfaces as an `auth_status` error frame; the failing result classifies as
3690
+ // `auth_expired`.
3858
3691
  name: "expired login \u2192 auth_expired",
3859
3692
  request: makeRequest(),
3860
3693
  nativeStream: [
@@ -3868,10 +3701,9 @@ var init_conformance2 = __esm({
3868
3701
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "auth_expired" }]
3869
3702
  },
3870
3703
  {
3871
- // CT592: a transient provider outage surfaced in the terminal result text (no
3872
- // structured event/enum) — a 529 overload — classifies as `server_error` via
3873
- // the text fallback. It must NOT read as the cap and must not stamp the
3874
- // connector `limited` (the whole point of the honest taxonomy).
3704
+ // A transient provider outage in the terminal result text (no structured event or enum) — a 529
3705
+ // overload — classifies as `server_error` via the text fallback. It must not read as the cap or
3706
+ // stamp the connector `limited`.
3875
3707
  name: "provider overload text \u2192 server_error",
3876
3708
  request: makeRequest(),
3877
3709
  nativeStream: [
@@ -3884,11 +3716,9 @@ var init_conformance2 = __esm({
3884
3716
  expected: [sessionEvent("s1"), { type: "result", ok: false, reason: "server_error" }]
3885
3717
  },
3886
3718
  {
3887
- // CT601: the resolved model is captured off `system/init.model` and attached
3888
- // to the terminal `result` — even on a plain resume (this fixture resumes, so
3889
- // no `session` event fires, proving the model doesn't ride the session event).
3890
- // The configured request model is irrelevant here; what's asserted is that the
3891
- // concrete id the HARNESS reported comes home.
3719
+ // The resolved model is captured off `system/init.model` and attached to the terminal `result`
3720
+ // even on a plain resume (this fixture resumes, so no `session` event fires). What's asserted is
3721
+ // that the concrete id the harness reported comes home.
3892
3722
  name: "resolved model captured from system/init",
3893
3723
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
3894
3724
  nativeStream: [init("s1", "claude-opus-4-8"), assistantText("Done."), resultSuccess("s1")],
@@ -3898,9 +3728,8 @@ var init_conformance2 = __esm({
3898
3728
  ]
3899
3729
  },
3900
3730
  {
3901
- // Session resume echo: the stored state matches the current cwd, so we resume
3902
- // its SDK id; `system/init` echoes that same id, so NO session event fires
3903
- // (the id didn't change).
3731
+ // Session resume echo: the stored state matches the cwd, so its SDK id is resumed and
3732
+ // `system/init` echoes the same id — no session event fires.
3904
3733
  name: "session resume (no re-emit on echo)",
3905
3734
  request: makeRequest({ session: encodeSession({ sdkSessionId: "s1", cwd: CWD }) }),
3906
3735
  nativeStream: [init("s1"), assistantText("Back again."), resultSuccess("s1")],
@@ -3910,16 +3739,14 @@ var init_conformance2 = __esm({
3910
3739
  ]
3911
3740
  },
3912
3741
  {
3913
- // SJ527 cwd-mismatch degrade: the stored state was created under a DIFFERENT
3914
- // cwd, so the adapter refuses to resume and starts fresh — `system/init`
3915
- // reports a new id, and the emitted session event records the CURRENT cwd.
3742
+ // Cwd-mismatch degrade: the stored state was created under another cwd, so the adapter starts
3743
+ // fresh — `system/init` reports a new id, and the session event records the current cwd.
3916
3744
  name: "session cwd-mismatch degrades to fresh",
3917
3745
  request: makeRequest({ session: encodeSession({ sdkSessionId: "old", cwd: "/env/gone" }) }),
3918
3746
  nativeStream: [init("fresh1"), assistantText("Fresh start."), resultSuccess("fresh1")],
3919
3747
  expected: [
3920
- // CT315: the degrade is flagged on the session event so the host relays it
3921
- // on settle and the server rewinds the catch-up mark (recovering the
3922
- // pre-mark context the discarded session no longer carries).
3748
+ // The degrade is flagged on the session event so the host relays it on settle and the server
3749
+ // rewinds the catch-up mark (recovering context the discarded session no longer carries).
3923
3750
  sessionEvent("fresh1", CWD, true),
3924
3751
  { type: "text", body: "Fresh start.", terminal: true },
3925
3752
  { type: "result", ok: true }
@@ -4622,8 +4449,6 @@ function createOpencodeAdapter(deps = {}) {
4622
4449
  const transport = deps.transport ?? (deps.serverUrl ? createHttpOpencodeTransport({ baseUrl: deps.serverUrl }) : null);
4623
4450
  return {
4624
4451
  name: "opencode",
4625
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
4626
- // argument went with the named tool surface it selected against.
4627
4452
  promptAddendum: () => OPENCODE_ADDENDUM,
4628
4453
  dialectSchema: opencodeDialectSchema,
4629
4454
  async *runTurn(request, signal, hooks) {
@@ -4812,15 +4637,12 @@ var init_conformance3 = __esm({
4812
4637
  ]
4813
4638
  },
4814
4639
  {
4815
- // CT301: a STREAMING provider's tool input arrives late. opencode re-emits the
4816
- // tool part as a full snapshot as the args parse: DeepSeek sends `input: {}` on
4817
- // the early `pending`/`running` frame and the full args only on a later snapshot
4818
- // (live-captured against opencode 1.17: `pending {}` → `running {full}` →
4819
- // `completed {full}`). The `start` event fires immediately off the first (empty)
4820
- // frame — that's the live card — but the `done` event (and the DB row it
4821
- // upserts) must carry the LATEST non-empty input + its summary, not the
4822
- // first-seen `{}`. This is the case the pre-CT301 suite structurally couldn't
4823
- // catch: every other tool fixture bakes the full input into the first snapshot.
4640
+ // A streaming provider's tool input arrives late. opencode re-emits the tool part as a full
4641
+ // snapshot as the args parse: DeepSeek sends `input: {}` on the early `pending`/`running` frame
4642
+ // and the full args on a later one (captured on opencode 1.17: `pending {}` → `running {full}` →
4643
+ // `completed {full}`). `start` fires off the first (empty) frame — the live card — but `done`
4644
+ // (and the row it upserts) must carry the latest non-empty input and its summary. Every other
4645
+ // tool fixture bakes the full input into the first snapshot, so only this one catches it.
4824
4646
  name: "streaming tool input \u2014 late args refresh the done event (CT301)",
4825
4647
  request: makeRequest2(),
4826
4648
  nativeStream: [
@@ -4858,9 +4680,8 @@ var init_conformance3 = __esm({
4858
4680
  ]
4859
4681
  },
4860
4682
  {
4861
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
4862
- // adapter stops before the closing reply — no final text, no `result` event.
4863
- // CT1295: the server writes the `kind:'stopped'` marker.
4683
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops before
4684
+ // the closing reply — no final text, no `result` event (the server writes the stopped marker).
4864
4685
  name: "cancel mid-stream",
4865
4686
  request: makeRequest2(),
4866
4687
  nativeStream: [
@@ -4900,10 +4721,9 @@ var init_conformance3 = __esm({
4900
4721
  ]
4901
4722
  },
4902
4723
  {
4903
- // CT558/CT592: a provider 429 throttle. The wrapped `RateLimitError` is
4904
- // unwrapped by `readSessionError` then text-classified — a bare "rate limit
4905
- // (429)" with no cap noun is the throttle (`rate_limited`), distinct from the
4906
- // account cap. opencode is text-only so it inherits the CT592 split for free.
4724
+ // A provider 429 throttle. The wrapped `RateLimitError` is unwrapped by `readSessionError` and
4725
+ // text-classified: a bare "rate limit (429)" with no cap noun is the throttle (`rate_limited`),
4726
+ // not the account cap.
4907
4727
  name: "provider 429 \u2192 rate_limited (throttle, no reset)",
4908
4728
  request: makeRequest2(),
4909
4729
  nativeStream: [
@@ -4919,9 +4739,8 @@ var init_conformance3 = __esm({
4919
4739
  ]
4920
4740
  },
4921
4741
  {
4922
- // CT592: a subscription cap surfaced as text ("usage limit reached") → the cap
4923
- // (`usage_capped`). opencode carries no reset hint, so it's reset-less (the
4924
- // server renders "try again later").
4742
+ // A subscription cap surfaced as text ("usage limit reached") → `usage_capped`. opencode carries
4743
+ // no reset hint, so it's reset-less (the server says "try again later").
4925
4744
  name: "usage cap text \u2192 usage_capped (no reset)",
4926
4745
  request: makeRequest2(),
4927
4746
  nativeStream: [
@@ -4937,9 +4756,7 @@ var init_conformance3 = __esm({
4937
4756
  ]
4938
4757
  },
4939
4758
  {
4940
- // CT592: a transient provider outage (5xx / overload) surfaced as text → the
4941
- // honest `server_error`, never the cap. This is the outage class that used to
4942
- // fall through to an opaque `unknown`.
4759
+ // A transient provider outage (5xx / overload) surfaced as text → `server_error`, never the cap.
4943
4760
  name: "provider overload text \u2192 server_error",
4944
4761
  request: makeRequest2(),
4945
4762
  nativeStream: [
@@ -4957,7 +4774,7 @@ var init_conformance3 = __esm({
4957
4774
  ]
4958
4775
  },
4959
4776
  {
4960
- // CT558: an expired/invalid credential classifies as `auth_expired`.
4777
+ // An expired or invalid credential classifies as `auth_expired`.
4961
4778
  name: "auth failure \u2192 auth_expired",
4962
4779
  request: makeRequest2(),
4963
4780
  nativeStream: [
@@ -4973,8 +4790,8 @@ var init_conformance3 = __esm({
4973
4790
  ]
4974
4791
  },
4975
4792
  {
4976
- // CT1351: an unclassified failure remains as informative as a classified one:
4977
- // its provider text is visible while the raw machine reason stays unchanged.
4793
+ // An unclassified failure stays informative: its provider text is visible, and the raw machine
4794
+ // reason is unchanged.
4978
4795
  name: "unclassified failure keeps a runtime notice and raw reason",
4979
4796
  request: makeRequest2(),
4980
4797
  nativeStream: [errored("something local broke")],
@@ -4995,8 +4812,8 @@ var init_conformance3 = __esm({
4995
4812
  ]
4996
4813
  },
4997
4814
  {
4998
- // CT1351: empty, whitespace-only, and bare status-token failures still set
4999
- // the machine reason but do not add an empty/unhelpful transcript notice.
4815
+ // Empty, whitespace-only and bare status-token failures still set the machine reason but add no
4816
+ // empty or unhelpful transcript notice.
5000
4817
  name: "empty failure text emits no runtime notice",
5001
4818
  request: makeRequest2(),
5002
4819
  nativeStream: [errored("")],
@@ -5028,12 +4845,10 @@ var init_conformance3 = __esm({
5028
4845
  ]
5029
4846
  },
5030
4847
  {
5031
- // CT1144: the same zero-content success the claude-code suite pins, in
5032
- // opencode's vocabulary — the session goes idle having emitted no part at
5033
- // all. No opencode sample of the anomaly has been seen in the wild; the
5034
- // fixture exists because the host's classification is runtime-neutral, so a
5035
- // runtime that CAN produce this envelope must be shown producing it in the
5036
- // shape the classifier reads.
4848
+ // The zero-content success the claude-code suite pins, in opencode's vocabulary — the session
4849
+ // goes idle having emitted no part. No opencode sample has been seen; the fixture exists because
4850
+ // the host's classification is runtime-neutral, so a runtime that can produce this envelope must
4851
+ // be shown producing it in the shape the classifier reads.
5037
4852
  name: "zero-content success (empty runtime result)",
5038
4853
  request: makeRequest2(),
5039
4854
  nativeStream: [idle()],
@@ -5090,10 +4905,9 @@ var init_conformance3 = __esm({
5090
4905
  ]
5091
4906
  },
5092
4907
  {
5093
- // CT298: the stored state matches the current directory, so the adapter resumes
5094
- // its id and the transport reports `created:false` on the same id — NO session
5095
- // event fires (the id didn't change). This is the resume-echo case CT294
5096
- // removed; restoring it is the crux of restoring cross-turn memory.
4908
+ // The stored state matches the directory, so the adapter resumes its id and the transport reports
4909
+ // `created:false` on the same id — no session event fires. Resume is what carries cross-turn
4910
+ // memory.
5097
4911
  name: "session resume (no re-emit on echo)",
5098
4912
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
5099
4913
  nativeStream: [textPart("p1", "Back again."), idle()],
@@ -5103,12 +4917,10 @@ var init_conformance3 = __esm({
5103
4917
  ]
5104
4918
  },
5105
4919
  {
5106
- // CT298: on a resumed turn the transport mints NO client message id (supplying
5107
- // one no-ops the turn), so the decoder can't be handed a `skipMessageId` up
5108
- // front. It learns the user-message id from the stream — the `message.updated`
5109
- // user frame precedes that message's echoed part — and drops the echo, so the
5110
- // injected per-turn context still can't leak into Steps. Only the assistant
5111
- // reply surfaces. No session event (resume echo on the same id).
4920
+ // On a resumed turn the transport mints no client message id (doing so no-ops the turn), so the
4921
+ // decoder gets no `skipMessageId`. It learns the user-message id from the stream — the
4922
+ // `message.updated` user frame precedes the echoed part — and drops the echo, so the injected
4923
+ // per-turn context can't leak into Steps. Only the reply surfaces; no session event.
5112
4924
  name: "session resume \u2014 prompt echo filtered via stream-derived user id",
5113
4925
  request: makeRequest2({ session: encodeSession2({ sessionId: "s1", directory: DIR }) }),
5114
4926
  nativeStream: [
@@ -5124,17 +4936,15 @@ var init_conformance3 = __esm({
5124
4936
  ]
5125
4937
  },
5126
4938
  {
5127
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was
5128
- // created under a DIFFERENT directory, so the adapter refuses to resume and
5129
- // starts fresh — the transport creates a new session, and the emitted session
5130
- // event records the CURRENT directory.
4939
+ // Directory-mismatch degrade: the stored state was created under another directory, so the
4940
+ // adapter starts fresh — the transport creates a new session, and the session event records the
4941
+ // current directory.
5131
4942
  name: "session directory-mismatch degrades to fresh",
5132
4943
  request: makeRequest2({ session: encodeSession2({ sessionId: "old", directory: "/env/gone" }) }),
5133
4944
  nativeStream: [textPart("p1", "Fresh start."), idle()],
5134
4945
  expected: [
5135
- // CT315: the degrade is flagged on the session event (parity with
5136
- // claude-code) so the host relays it on settle and the server rewinds the
5137
- // catch-up mark, recovering the context the discarded session dropped.
4946
+ // The degrade is flagged on the session event so the host relays it on settle and the server
4947
+ // rewinds the catch-up mark, recovering context the discarded session dropped.
5138
4948
  sessionEvent2(NEW_SESSION_ID, DIR, true),
5139
4949
  { type: "text", body: "Fresh start.", terminal: true },
5140
4950
  { type: "result", ok: true }
@@ -5159,11 +4969,9 @@ var init_addendum3 = __esm({
5159
4969
  "absent without attempting discovery and invocation, and if a call fails, report the recorded",
5160
4970
  "error.",
5161
4971
  turnControlPromptGuidance("mcp__cabane_companion__"),
5162
- // CT1552: every ancillary tool is written the way Codex's own tool list shows
5163
- // it — qualified. The bare `read_binary` / `upload` / … this list used to mix
5164
- // in sat two lines under the instruction to invoke the exact qualified names,
5165
- // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
5166
- // that sentence is about operation names, after the qualified forms are given.
4972
+ // Every ancillary tool is written as Codex's own tool list shows it — qualified — so the sentence
4973
+ // instructing exact qualified names doesn't contradict itself. The bare `query` / `mutate` below
4974
+ // stay: that sentence is about operation names, after the qualified forms are given.
5167
4975
  "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
5168
4976
  "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
5169
4977
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
@@ -5419,10 +5227,9 @@ async function* decodeCodexStream(events, ctx) {
5419
5227
  threadId,
5420
5228
  cwd: ctx.cwd ?? "",
5421
5229
  promptFingerprint: ctx.promptFingerprint ?? null,
5422
- // CT1645: the baseline this turn INHERITED, carried through unchanged.
5423
- // This early write exists so a turn that dies mid-stream still leaves
5424
- // the thread id recorded; it cannot yet know what this turn will spend.
5425
- // The closing write below records the new baseline.
5230
+ // The baseline this turn inherited, carried through unchanged. This early write records the
5231
+ // thread id even if the turn dies mid-stream; it can't yet know what the turn will spend. The
5232
+ // closing write records the new baseline.
5426
5233
  usageBaseline: ctx.usageBaseline ?? null
5427
5234
  });
5428
5235
  if (threadId !== ctx.resumedThreadId || state !== ctx.storedState) {
@@ -5716,9 +5523,8 @@ function buildConfig(req, baseInstructionsFile) {
5716
5523
  return {
5717
5524
  mcp_servers,
5718
5525
  experimental_use_rmcp_client: true,
5719
- // CT871: the no-host-access prompt replacement — Cabane's composed prompt
5720
- // becomes Codex's base instructions, and Codex's own environment preamble goes
5721
- // with the harness prompt it belonged to.
5526
+ // The no-host-access prompt replacement — Cabane's composed prompt becomes Codex's base
5527
+ // instructions, and Codex's environment preamble goes with the harness prompt it belonged to.
5722
5528
  ...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
5723
5529
  ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
5724
5530
  };
@@ -5839,16 +5645,11 @@ function createSdkCodexTransport(opts = {}) {
5839
5645
  const codexOptions = {
5840
5646
  ...opts.apiKey ? { apiKey: opts.apiKey } : {},
5841
5647
  ...opts.codexPathOverride ? { codexPathOverride: opts.codexPathOverride } : {},
5842
- // The `--config` overrides (MCP servers + rmcp flag). CT1501: rendered to
5843
- // finished `--config` strings by US and passed through `configOverrides`,
5844
- // the SDK's raw passthrough — NOT through `config`, which would flatten
5845
- // them itself. A user MCP server named `my.server` cannot go in an
5846
- // override's dotted path at all (the CLI splits the path on `.` and
5847
- // ignores TOML quoting), so it has to ride inside an inline-table value,
5848
- // and the SDK's flattener cannot be steered into producing one. This is
5849
- // also what makes the PUBLISHED companion correct, where the vendored
5850
- // patch never reached — and why that patch is gone. See
5851
- // `config-overrides.ts`.
5648
+ // The `--config` overrides (MCP servers + rmcp flag), rendered to finished `--config` strings
5649
+ // here and passed through `configOverrides`, the SDK's raw passthrough — not `config`, which
5650
+ // flattens them itself. A user MCP server named `my.server` can't go in an override's dotted
5651
+ // path (the CLI splits on `.` and ignores TOML quoting), so it must ride inside an inline-table
5652
+ // value, which the SDK's flattener can't produce. See `config-overrides.ts`.
5852
5653
  configOverrides: buildConfigOverrides(spec.config),
5853
5654
  ...spec.secretEnv ? { env: codexProcessEnv(spec.secretEnv) } : {}
5854
5655
  };
@@ -5872,8 +5673,6 @@ function createCodexAdapter(deps = {}) {
5872
5673
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
5873
5674
  return {
5874
5675
  name: "codex",
5875
- // CT829: one surface, so one addendum and no parameter — CT614's `codeMode`
5876
- // argument went with the named tool surface it selected against.
5877
5676
  promptAddendum: () => CODEX_ADDENDUM,
5878
5677
  dialectSchema: codexDialectSchema,
5879
5678
  async *runTurn(request, signal, hooks) {
@@ -5965,17 +5764,14 @@ function createCodexAdapter(deps = {}) {
5965
5764
  cwd: req.local.cwd,
5966
5765
  resumedThreadId: resumeThreadId,
5967
5766
  degraded,
5968
- // CT1075: which prompt the thread carries once this turn lands. If we sent
5969
- // one, it's that one — a later copy is what the model reads, so a changed
5970
- // prompt supersedes the older copy still sitting above it. If we withheld
5971
- // it, the thread still carries whatever it did before. Read off the spec
5972
- // rather than recomputed, so the recorded fact can't disagree with what
5973
- // was actually sent.
5767
+ // Which prompt the thread carries once this turn lands. If we sent one, it's that one — the
5768
+ // later copy is what the model reads. If we withheld it, the thread carries what it did. Read
5769
+ // off the spec, so the record can't disagree with what was sent.
5974
5770
  promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
5975
5771
  storedState: req.session,
5976
5772
  usageBaseline: threadUsageBaseline,
5977
- // CT601: Codex reports no model in-stream, so record what the thread was
5978
- // started with (null on the default token). Same for the reasoning effort.
5773
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
5774
+ // default token). Same for the reasoning effort.
5979
5775
  resolvedModel: spec.model,
5980
5776
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
5981
5777
  });
@@ -6030,11 +5826,9 @@ function makeRequest3(overrides2 = {}) {
6030
5826
  systemPrompt: PROMPT,
6031
5827
  prompt: "hi there",
6032
5828
  content: [{ type: "text", text: "hi there" }],
6033
- // CT601: the resolved model + reasoning effort ride the terminal `result` off
6034
- // the run spec's model. The behavioral fixtures below don't assert them, so the
6035
- // default is the model-less ("let Codex choose") token → no `resolvedModel`
6036
- // emitted → their expected results stay unchanged; a dedicated capture fixture
6037
- // sets a real model + effort.
5829
+ // The resolved model and reasoning effort ride the terminal `result` off the run spec's model.
5830
+ // Behavioural fixtures use the model-less token (no `resolvedModel` emitted); a dedicated capture
5831
+ // fixture sets a real model and effort.
6038
5832
  config: { model: null },
6039
5833
  policy: COMPANION_POLICY2,
6040
5834
  session: null,
@@ -6106,13 +5900,12 @@ var init_conformance4 = __esm({
6106
5900
  });
6107
5901
  CODEX_CONFORMANCE_FIXTURES = [
6108
5902
  {
6109
- // A plain text reply: the held agent message seals as the terminal final at
6110
- // `turn.completed`; the usage counters ride the `result`.
5903
+ // A plain text reply: the held agent message seals as the terminal final at `turn.completed`;
5904
+ // usage rides the `result`.
6111
5905
  //
6112
- // CT1283: the counters NEST — `cached_input_tokens` is a slice of
6113
- // `input_tokens`, `reasoning_output_tokens` a slice of `output_tokens` — so
6114
- // input is 100 (not 120) and output is 50 (not 60), with the cache read broken
6115
- // out beside them. This fixture pinned the double-count before CT1283.
5906
+ // The counters nest — `cached_input_tokens` is a slice of `input_tokens`,
5907
+ // `reasoning_output_tokens` a slice of `output_tokens` — so input is 100 (not 120) and output 50
5908
+ // (not 60), with the cache read broken out beside them.
6116
5909
  name: "clean turn",
6117
5910
  request: makeRequest3(),
6118
5911
  nativeStream: [
@@ -6129,10 +5922,8 @@ var init_conformance4 = __esm({
6129
5922
  expected: [
6130
5923
  sessionEvent3(NEW_THREAD_ID),
6131
5924
  { type: "text", body: "Hello there.", terminal: true },
6132
- // CT1645: the closing session write — the thread's ending totals, recorded
6133
- // as the baseline the NEXT turn subtracts. This is a fresh thread, so the
6134
- // `result` usage below is the frame verbatim (nothing to subtract yet);
6135
- // what makes it per-turn is this figure being carried into turn two.
5925
+ // The closing session write — the thread's ending totals, recorded as the baseline the next turn
5926
+ // subtracts. A fresh thread, so the `result` usage below is the frame verbatim.
6136
5927
  sessionEvent3(NEW_THREAD_ID, DIR2, false, fingerprintPrompt(PROMPT), {
6137
5928
  inputTokens: 100,
6138
5929
  outputTokens: 50,
@@ -6204,8 +5995,8 @@ var init_conformance4 = __esm({
6204
5995
  ]
6205
5996
  },
6206
5997
  {
6207
- // CT1211: recorded MCP success shape. Codex exposes the text compatibility
6208
- // copy and the native object together; normalization must keep the object.
5998
+ // The recorded MCP success shape: Codex exposes the text copy and the native object together;
5999
+ // normalization must keep the object.
6209
6000
  name: "MCP success prefers native structured content",
6210
6001
  request: makeRequest3(),
6211
6002
  nativeStream: [
@@ -6255,8 +6046,8 @@ var init_conformance4 = __esm({
6255
6046
  ]
6256
6047
  },
6257
6048
  {
6258
- // CT1211 incident shape: MCP `isError` keeps the failed phase, while the raw
6259
- // camel-case structured payload survives instead of the generic `error` token.
6049
+ // MCP `isError` keeps the failed phase, while the raw camel-case structured payload survives
6050
+ // instead of the generic `error` token.
6260
6051
  name: "failed MCP call preserves structured receipt while remaining error phase",
6261
6052
  request: makeRequest3(),
6262
6053
  nativeStream: [
@@ -6308,8 +6099,8 @@ var init_conformance4 = __esm({
6308
6099
  ]
6309
6100
  },
6310
6101
  {
6311
- // A transport/tool failure has no MCP result payload and still uses the real
6312
- // adapter error message. CT1211 must not replace this fallback with `undefined`.
6102
+ // A transport/tool failure with no MCP result payload still uses the real adapter error message,
6103
+ // never `undefined`.
6313
6104
  name: "failed MCP call without result keeps error message fallback",
6314
6105
  request: makeRequest3(),
6315
6106
  nativeStream: [
@@ -6349,8 +6140,8 @@ var init_conformance4 = __esm({
6349
6140
  ]
6350
6141
  },
6351
6142
  {
6352
- // CT1147 stays intact when a connector includes structured metadata beside an
6353
- // image-only MCP response: no image/base64 payload enters activity receipts.
6143
+ // A connector's structured metadata beside an image-only MCP response: no image/base64 payload
6144
+ // enters activity receipts.
6354
6145
  name: "image-only MCP result remains empty despite structured metadata",
6355
6146
  request: makeRequest3(),
6356
6147
  nativeStream: [
@@ -6443,12 +6234,10 @@ var init_conformance4 = __esm({
6443
6234
  ]
6444
6235
  },
6445
6236
  {
6446
- // CT715: web_search LIFECYCLE. Unlike the other three tool kinds, a `web_search`
6447
- // item carries NO `status` field — completion is signaled by the frame TYPE
6448
- // (`item.started` → `item.completed`). The `start` fires off the first frame
6449
- // (empty query, no output card); the `done` must resolve off `item.completed`
6450
- // and carry the populated query the completed frame filled in. Before CT715 the
6451
- // card was pinned at "running" forever (status derived from a missing field).
6237
+ // web_search lifecycle. A `web_search` item carries no `status` — completion is signalled by the
6238
+ // frame type (`item.started` → `item.completed`). The `start` fires off the first frame (empty
6239
+ // query); the `done` must resolve off `item.completed` with the populated query, not stay
6240
+ // "running".
6452
6241
  name: "web_search lifecycle \u2014 completes off frame type, populated query on done",
6453
6242
  request: makeRequest3(),
6454
6243
  nativeStream: [
@@ -6479,9 +6268,8 @@ var init_conformance4 = __esm({
6479
6268
  ]
6480
6269
  },
6481
6270
  {
6482
- // CT715: a non-text web action (`action.type: "other"`) legitimately completes
6483
- // with an EMPTY query — that's Codex's own data, not our bug. It must still
6484
- // resolve to `done` (empty query acceptable; stuck-running is not).
6271
+ // A non-text web action (`action.type: "other"`) legitimately completes with an empty query —
6272
+ // Codex's own data. It must still resolve to `done`.
6485
6273
  name: "web_search lifecycle \u2014 empty-query completion still resolves to done",
6486
6274
  request: makeRequest3(),
6487
6275
  nativeStream: [
@@ -6512,9 +6300,8 @@ var init_conformance4 = __esm({
6512
6300
  ]
6513
6301
  },
6514
6302
  {
6515
- // HIGHEST-RISK: cancel mid-stream. The abort lands after the tool result; the
6516
- // adapter stops before the closing reply — no final text, no `result` event.
6517
- // CT1295: the server writes the `kind:'stopped'` marker.
6303
+ // Highest risk: cancel mid-stream. The abort lands after the tool result; the adapter stops before
6304
+ // the closing reply — no final text, no `result` event (the server writes the stopped marker).
6518
6305
  name: "cancel mid-stream",
6519
6306
  request: makeRequest3(),
6520
6307
  nativeStream: [
@@ -6595,11 +6382,8 @@ var init_conformance4 = __esm({
6595
6382
  ]
6596
6383
  },
6597
6384
  {
6598
- // CT592: Codex used to classify NOTHING — every `turn.failed` became a raw
6599
- // `error:<text>` `unknown`. It now routes the failure text through the shared
6600
- // classifier (Codex is text-only), so a provider 429 / outage / auth / cap
6601
- // failure splits into the honest taxonomy instead of all reading `unknown`.
6602
- // This is a real behavior change, not a refactor. One fixture per kind:
6385
+ // The failure text goes through the shared classifier (Codex is text-only), so a provider 429 /
6386
+ // outage / auth / cap failure splits into the honest taxonomy. One fixture per kind:
6603
6387
  name: "turn.failed 429 \u2192 rate_limited (throttle)",
6604
6388
  request: makeRequest3(),
6605
6389
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
@@ -6646,8 +6430,8 @@ var init_conformance4 = __esm({
6646
6430
  ]
6647
6431
  },
6648
6432
  {
6649
- // CT592: a Codex failure text the classifier can't place still keeps today's
6650
- // raw `error:<text>` reason (the `unknown` fallback) — nothing regresses.
6433
+ // A failure text the classifier can't place keeps the raw `error:<text>` reason (the `unknown`
6434
+ // fallback).
6651
6435
  name: "turn.failed unclassified \u2192 raw error reason (unchanged)",
6652
6436
  request: makeRequest3(),
6653
6437
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
@@ -6671,8 +6455,8 @@ var init_conformance4 = __esm({
6671
6455
  ]
6672
6456
  },
6673
6457
  {
6674
- // CT1351: empty, whitespace-only, and bare status-token failures still set
6675
- // the machine reason but do not add an empty/unhelpful transcript notice.
6458
+ // Empty, whitespace-only and bare status-token failures still set the machine reason but add no
6459
+ // empty or unhelpful transcript notice.
6676
6460
  name: "empty failure text emits no runtime notice",
6677
6461
  request: makeRequest3(),
6678
6462
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
@@ -6701,11 +6485,9 @@ var init_conformance4 = __esm({
6701
6485
  ]
6702
6486
  },
6703
6487
  {
6704
- // CT481: fail LOUD on unknown model metadata. An unknown `--model` makes Codex
6705
- // emit an `error` item ("… Defaulting to fallback metadata …") and then go
6706
- // haywire (the spurious tool call scripted after it MUST never be emitted). The
6707
- // adapter ends the turn the moment the signal lands, with a machine reason — so
6708
- // a future Codex id-drop errors clean instead of degrading mid-tool-turn.
6488
+ // Fail loud on unknown model metadata. An unknown `--model` makes Codex emit an `error` item
6489
+ // ("… Defaulting to fallback metadata …") and misbehave (the spurious tool call scripted after it
6490
+ // must never be emitted). The adapter ends the turn when the signal lands, with a machine reason.
6709
6491
  name: "unknown model metadata \u2192 fail loud (guard)",
6710
6492
  request: makeRequest3(),
6711
6493
  nativeStream: [
@@ -6751,12 +6533,10 @@ var init_conformance4 = __esm({
6751
6533
  ]
6752
6534
  },
6753
6535
  {
6754
- // CT1144: the same zero-content success the claude-code suite pins, in
6755
- // Codex's vocabulary — the thread starts, the turn completes, and nothing is
6756
- // emitted in between. No Codex sample of the anomaly has been seen in the
6757
- // wild; the fixture exists because the host's classification is runtime-
6758
- // neutral, so a runtime that CAN produce this envelope must be shown
6759
- // producing it in the shape the classifier reads.
6536
+ // The zero-content success the claude-code suite pins, in Codex's vocabulary — the thread
6537
+ // starts, the turn completes, nothing in between. No Codex sample has been seen; the fixture
6538
+ // exists because the host's classification is runtime-neutral, so a runtime that can produce
6539
+ // this envelope must be shown producing it in the shape the classifier reads.
6760
6540
  name: "zero-content success (empty runtime result)",
6761
6541
  request: makeRequest3(),
6762
6542
  nativeStream: [threadStarted(NEW_THREAD_ID), turnCompleted()],
@@ -6825,11 +6605,10 @@ var init_conformance4 = __esm({
6825
6605
  ]
6826
6606
  },
6827
6607
  {
6828
- // CT601: Codex reports no model in-stream, so the resolved model + reasoning
6829
- // effort come from what the thread was STARTED with (the run spec) and ride the
6830
- // terminal `result`. A real model + a `runtimeOptions.codex.modelReasoningEffort`
6831
- // are asserted through to `resolvedModel` (bare, `openai/`-stripped) and
6832
- // `resolvedConfig.reasoningEffort`.
6608
+ // Codex reports no model in-stream, so the resolved model and reasoning effort come from what the
6609
+ // thread was started with (the run spec) and ride the terminal `result`: a real model and a
6610
+ // `runtimeOptions.codex.modelReasoningEffort` are asserted through to `resolvedModel` (bare,
6611
+ // `openai/`-stripped) and `resolvedConfig.reasoningEffort`.
6833
6612
  name: "resolved model + reasoning effort captured from run spec",
6834
6613
  request: makeRequest3({
6835
6614
  config: {
@@ -6850,15 +6629,12 @@ var init_conformance4 = __esm({
6850
6629
  ]
6851
6630
  },
6852
6631
  {
6853
- // Session resume: the stored state matches the current directory, so the adapter
6854
- // resumes its thread id and `thread.started` echoes the SAME id — NO session
6855
- // event fires (the id didn't change AND the state is unchanged). This is what
6856
- // carries cross-turn memory.
6632
+ // Session resume: the stored state matches the directory, so the thread id is resumed and
6633
+ // `thread.started` echoes it — no session event fires. This is what carries cross-turn memory.
6857
6634
  //
6858
- // CT1075: the stored state also says the thread already holds the composed
6859
- // prompt, so this turn withholds the preamble and the state it would write is
6860
- // byte-identical to the stored one. The silence is now proof of BOTH: an id that
6861
- // didn't change and a prompt that didn't need re-sending.
6635
+ // The stored state also says the thread already holds the composed prompt, so this turn
6636
+ // withholds the preamble and the state it would write is byte-identical: the silence proves both
6637
+ // an unchanged id and a prompt that needed no re-send.
6862
6638
  name: "session resume (no re-emit on echo)",
6863
6639
  request: makeRequest3({
6864
6640
  session: encodeSession3({
@@ -6875,16 +6651,12 @@ var init_conformance4 = __esm({
6875
6651
  ]
6876
6652
  },
6877
6653
  {
6878
- // CT1075: a resumed thread the platform's state does NOT vouch for — a state
6879
- // written before CT1075 existed, so it carries no `promptFingerprint` field. The
6880
- // adapter reads the absence as "we can't say which prompt this thread holds",
6881
- // sends the full prompt exactly as it did pre-CT1075, and re-emits the state with
6882
- // the fingerprint recorded — on the SAME thread id, which pre-CT1075 emitted
6883
- // nothing at all. That re-emit is the upgrade path: without it a long-lived
6884
- // thread would re-send the whole prompt forever.
6654
+ // A resumed thread whose stored state carries no `promptFingerprint` field. The adapter reads the
6655
+ // absence as "can't say which prompt this thread holds", sends the full prompt, and re-emits the
6656
+ // state with the fingerprint — on the same thread id. That re-emit is the upgrade path: without
6657
+ // it a long-lived thread would re-send the whole prompt forever.
6885
6658
  //
6886
- // This is the fixture that fails if the static half is withheld on a thread we
6887
- // have no evidence about.
6659
+ // This fixture fails if the standing half is withheld on a thread we have no evidence about.
6888
6660
  name: "session resume with a pre-CT1075 state re-emits with the prompt fingerprint",
6889
6661
  request: makeRequest3({
6890
6662
  session: JSON.stringify({ threadId: "th1", cwd: DIR2 })
@@ -6897,14 +6669,12 @@ var init_conformance4 = __esm({
6897
6669
  ]
6898
6670
  },
6899
6671
  {
6900
- // CT1075 (review finding): the thread carries a DIFFERENT prompt than the one
6901
- // this turn composed — a charter edit, a renamed agent, a redeployed prompt
6902
- // body. The stored fingerprint doesn't match, so the new prompt is sent exactly
6903
- // as on a fresh thread and the state is re-recorded against it.
6672
+ // The thread carries a different prompt than this turn composed — a charter edit, a renamed
6673
+ // agent, a redeployed prompt body. The fingerprint doesn't match, so the new prompt is sent as on
6674
+ // a fresh thread and the state re-recorded.
6904
6675
  //
6905
- // This is the fixture that fails if a stale prompt is treated as good enough. A
6906
- // boolean flag would have passed the resume fixture above and silently withheld
6907
- // every future charter edit from this thread.
6676
+ // This fixture fails if a stale prompt is treated as good enough; a boolean flag would pass the
6677
+ // resume fixture above and withhold every future charter edit.
6908
6678
  name: "session resume with a stale prompt fingerprint re-sends the prompt",
6909
6679
  request: makeRequest3({
6910
6680
  session: encodeSession3({
@@ -6922,15 +6692,12 @@ var init_conformance4 = __esm({
6922
6692
  ]
6923
6693
  },
6924
6694
  {
6925
- // Directory-mismatch degrade (the SJ527 analogue): the stored state was created
6926
- // under a DIFFERENT directory, so the adapter refuses to resume and starts
6927
- // fresh — `thread.started` reports a NEW id, and the emitted session event
6928
- // records the CURRENT directory with `degraded: true` so the host rewinds the
6929
- // catch-up mark.
6695
+ // Directory-mismatch degrade: the stored state was created under another directory, so the
6696
+ // adapter starts fresh — `thread.started` reports a new id, and the session event records the
6697
+ // current directory with `degraded: true` so the host rewinds the catch-up mark.
6930
6698
  //
6931
- // CT1075: the refused resume means the new thread is EMPTY, so the full prompt
6932
- // goes in even though the stored state vouched for a copy — of a thread we're
6933
- // no longer in. The emitted state records the fresh thread's own answer.
6699
+ // The refused resume means the new thread is empty, so the full prompt goes in even though the
6700
+ // stored state vouched for a copy in a thread we're no longer in.
6934
6701
  name: "session directory-mismatch degrades to fresh",
6935
6702
  request: makeRequest3({
6936
6703
  session: encodeSession3({
@@ -7319,8 +7086,7 @@ function createTurnContainment(turnId, deps = {}) {
7319
7086
  `--unit=${unit}`,
7320
7087
  `--property=TimeoutStopSec=${CONTAINMENT_STOP_GRACE_SEC}s`,
7321
7088
  // Bind the scope's life to the companion's own unit, so stopping or
7322
- // restarting the companion still takes its in-flight turns with it —
7323
- // which is what happens today, and what the scope would otherwise undo.
7089
+ // restarting the companion still takes its in-flight turns with it.
7324
7090
  // Omitted outside a unit, where there is nothing to bind to.
7325
7091
  ...ownUnit ? [`--property=PartOf=${ownUnit}`] : [],
7326
7092
  "--"
@@ -7671,7 +7437,7 @@ var init_run_config = __esm({
7671
7437
  type: z15.literal("http"),
7672
7438
  url: z15.string().url(),
7673
7439
  headers: z15.record(z15.string(), z15.string()).optional(),
7674
- // CT1698: header → the name of a `secretEnv` entry holding its value. Set by
7440
+ // Header → the name of a `secretEnv` entry holding its value. Set by
7675
7441
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
7676
7442
  // header whose `${VAR}` it resolved.
7677
7443
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -7680,7 +7446,7 @@ var init_run_config = __esm({
7680
7446
  type: z15.literal("sse"),
7681
7447
  url: z15.string().url(),
7682
7448
  headers: z15.record(z15.string(), z15.string()).optional(),
7683
- // CT1698: header → the name of a `secretEnv` entry holding its value. Set by
7449
+ // Header → the name of a `secretEnv` entry holding its value. Set by
7684
7450
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
7685
7451
  // header whose `${VAR}` it resolved.
7686
7452
  headersFromEnv: z15.record(z15.string(), z15.string()).optional()
@@ -7693,9 +7459,8 @@ var init_run_config = __esm({
7693
7459
  ]);
7694
7460
  effortSchema = z15.enum(["low", "medium", "high", "xhigh", "max"]);
7695
7461
  runConfigSchema = z15.object({
7696
- // CT788: the host-access binary replaced the `assistant`/`coding`/`custom` mode
7697
- // trio + its custom tool lists — `true` grants the host filesystem/shell, absent
7698
- // is the locked surface. Kept in lockstep with `@cabane/shared`'s
7462
+ // `true` grants the host filesystem/shell; absent is the locked surface. Kept
7463
+ // in lockstep with `@cabane/shared`'s
7699
7464
  // `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
7700
7465
  // stripped, so an older companion riding a newer server never rejects the config).
7701
7466
  hostAccess: z15.boolean().optional(),
@@ -7786,7 +7551,7 @@ var init_protocol = __esm({
7786
7551
  dispatch: "CABANE_DISPATCH",
7787
7552
  // The turn id the launcher resolved, and whether this is a replay of a turn
7788
7553
  // an earlier process started. The seq floor for a replay is derived inside
7789
- // the turn from the outbox and the server, as it always was.
7554
+ // the turn from the outbox and the server.
7790
7555
  turnId: "CABANE_TURN_ID",
7791
7556
  resumed: "CABANE_RESUMED",
7792
7557
  // Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
@@ -7822,10 +7587,10 @@ var init_protocol = __esm({
7822
7587
  // Whether the harness starts under the OS sandbox. The launcher sends 'os'
7823
7588
  // only when this host can make one.
7824
7589
  sandbox: z16.enum(["os", "none"]),
7825
- // CT1684: the device's sandbox policy, validated by the launcher. Absent
7590
+ // The device's sandbox policy, validated by the launcher. Absent
7826
7591
  // when the sandbox is off.
7827
7592
  sandboxPolicy: sandboxPolicySchema.optional(),
7828
- // CT1691: every companion store the launcher's latest walk found, denied
7593
+ // Every companion store the launcher's latest walk found, denied
7829
7594
  // to the harness. Absent when the sandbox is off.
7830
7595
  companionStores: z16.array(z16.string()).optional(),
7831
7596
  logLevel: z16.string(),
@@ -7834,15 +7599,15 @@ var init_protocol = __esm({
7834
7599
  // True when this (conversation, agent) pair was already told its session
7835
7600
  // could not be saved: the notice is once per pair, not once per process.
7836
7601
  sessionWriteNoticeClaimed: z16.boolean(),
7837
- // CT1680: the runner's identity, minted with its credential. The start
7602
+ // The runner's identity, minted with its credential. The start
7838
7603
  // PATCH carries it so the lease knows which runner holds the span.
7839
7604
  runnerId: z16.string(),
7840
- // CT1681: the launcher keeps the runtime's session files beside the attempt
7605
+ // The launcher keeps the runtime's session files beside the attempt
7841
7606
  // and restores them into this runner's home, so the runner names the attempt
7842
7607
  // each context was composed for and waits for the launcher to say it's in
7843
7608
  // place. Absent: nothing to wait for (a runner on the launcher's own disk).
7844
7609
  attemptState: z16.boolean().optional(),
7845
- // CT1681: MCP `${NAME}` secrets come from the launcher, by name, instead of
7610
+ // MCP `${NAME}` secrets come from the launcher, by name, instead of
7846
7611
  // this machine's secrets.json. Absent: the runner reads the device's file.
7847
7612
  secretsFromLauncher: z16.boolean().optional()
7848
7613
  });
@@ -8431,7 +8196,7 @@ function createTurnControlMcpServer(acts, skipState) {
8431
8196
  return performAct(acts, {
8432
8197
  kind: "ask",
8433
8198
  callId: randomUUID2(),
8434
- // CT1350: the swap rides the ask, so the server commits both halves
8199
+ // The swap rides the ask, so the server commits both halves
8435
8200
  // in one transaction — the person never sees neither or both.
8436
8201
  ...args.replaces ? { replaces: args.replaces } : {},
8437
8202
  ask: hasArray ? {
@@ -8482,7 +8247,7 @@ function createTurnControlMcpServer(acts, skipState) {
8482
8247
  },
8483
8248
  { annotations: ACT_TOOL, alwaysLoad: true }
8484
8249
  ),
8485
- // CT1350: the standalone withdrawal. Not annotated destructive — an ask
8250
+ // The standalone withdrawal. Not annotated destructive — an ask
8486
8251
  // is a question, not data, and a harness that gated this on a live
8487
8252
  // operator would hang a turn nobody is watching (the `cancel_wake`
8488
8253
  // reasoning). Not idempotent either: a second call with the same id is
@@ -8501,7 +8266,7 @@ function createTurnControlMcpServer(acts, skipState) {
8501
8266
  }),
8502
8267
  { annotations: ACT_TOOL, alwaysLoad: true }
8503
8268
  ),
8504
- // CT990: the inverse. No arguments — it can only ever stand down the
8269
+ // The inverse. No arguments — it can only ever stand down the
8505
8270
  // caller's own wake in the conversation it's holding a turn in.
8506
8271
  tool(
8507
8272
  CANCEL_WAKE_TOOL,
@@ -8566,7 +8331,7 @@ function buildCompanionTurnRequest(params) {
8566
8331
  // adapter's `ResolvedMcpServers`.
8567
8332
  ...Object.keys(params.mcpServers).length > 0 ? { mcpServers: params.mcpServers } : {},
8568
8333
  ...params.secretEnv && Object.keys(params.secretEnv).length > 0 ? { secretEnv: params.secretEnv } : {},
8569
- // CT289: the auto-memory escape hatch, when the operator set it.
8334
+ // The auto-memory escape hatch, when the operator set it.
8570
8335
  ...params.claudeCode ? { claudeCode: params.claudeCode } : {}
8571
8336
  },
8572
8337
  // Host-injected turn-control tools, under their own namespace.
@@ -8889,7 +8654,7 @@ var init_transcript_writer = __esm({
8889
8654
  }
8890
8655
  this.line({ type: "_meta", ...meta });
8891
8656
  }
8892
- // Append a raw SDK message verbatim.
8657
+ // Append one event verbatim.
8893
8658
  write(msg) {
8894
8659
  this.line(msg);
8895
8660
  }
@@ -8897,7 +8662,7 @@ var init_transcript_writer = __esm({
8897
8662
  close(outcome) {
8898
8663
  this.line({ type: "_outcome", ...outcome });
8899
8664
  }
8900
- // CT1145: keep an independent copy of an empty successful envelope after the
8665
+ // Keep an independent copy of an empty successful envelope after the
8901
8666
  // footer has landed. The ordinary 200-file FIFO keeps rotating as before;
8902
8667
  // this bucket retains the newest 50 anomalies regardless of ordinary traffic.
8903
8668
  // Best-effort like every other transcript operation: observability can never
@@ -8973,6 +8738,8 @@ var init_turn_committer = __esm({
8973
8738
  summary,
8974
8739
  input,
8975
8740
  result,
8741
+ inputChars,
8742
+ resultChars,
8976
8743
  mcpServer,
8977
8744
  phase,
8978
8745
  seq
@@ -8988,6 +8755,8 @@ var init_turn_committer = __esm({
8988
8755
  summary,
8989
8756
  input,
8990
8757
  result,
8758
+ ...inputChars !== void 0 ? { inputChars } : {},
8759
+ ...resultChars !== void 0 ? { resultChars } : {},
8991
8760
  mcpServer,
8992
8761
  phase,
8993
8762
  seq,
@@ -9029,12 +8798,9 @@ var init_turn_committer = __esm({
9029
8798
  // Renders each `TurnEvent` onto the pump's callback sink (tool-log truncation),
9030
8799
  // routing a throwing commit to `onError`.
9031
8800
  emit;
9032
- // CT261: route one `TurnEvent` from the adapter. `text` / `tool` / `thinking`
9033
- // flow through the pump (rendered + committed); `session` / `result` are the
9034
- // dispatcher's concern and ignored here. A throwing commit (a flaky/4xx HTTP
9035
- // POST) is routed to the error hook and swallowed — a failed commit is
9036
- // warn-only and must never fail the turn (the role the classification's
9037
- // `safeEmit` used to play before the adapter owned the decode).
8801
+ // `text` / `tool` / `thinking` flow through the pump; `session` / `result` are
8802
+ // the dispatcher's and ignored here. A throwing commit is routed to the error
8803
+ // hook and swallowed: a failed commit must never fail the turn.
9038
8804
  async ingestEvent(event) {
9039
8805
  if (event.type === "session" || event.type === "result") return;
9040
8806
  try {
@@ -9261,18 +9027,15 @@ var init_turn_execution = __esm({
9261
9027
  turnLog;
9262
9028
  abortController = new AbortController();
9263
9029
  outcome = initialOutcome();
9264
- // CT1503: this turn's shell containment, once the claude-code adapter is built.
9265
- // Null before that and on a turn that never reached adapter construction — a
9266
- // startup failure has nothing to reap, and reaping is a no-op rather than a
9267
- // special case.
9030
+ // This turn's shell containment, once the claude-code adapter is built. Null
9031
+ // before that, so a startup failure's reap is a no-op.
9268
9032
  containment = null;
9269
9033
  seqCounter = 0;
9270
- // CT11: per-turn monotonic counter mirroring the in-process dispatcher's.
9271
- //
9272
- // CT1380: 0 for a NEW turn only — a resume is seeded in `fetchContext` first.
9034
+ // Per-turn monotonic seq. Starts at 0 for a NEW turn only; a resume is seeded
9035
+ // in `fetchContext` first.
9273
9036
  nextSeq = () => ++this.seqCounter;
9274
- // CT1380: a REPLAY, told to us by the supervisor — not derivable here, since
9275
- // `turnId` is set on every dispatch. `resumedFromSeq` is what it seeded from.
9037
+ // A REPLAY, told to us by the supervisor — not derivable here, since `turnId`
9038
+ // is set on every dispatch. `resumedFromSeq` is what it seeded from.
9276
9039
  resumed;
9277
9040
  resumedFromSeq;
9278
9041
  // Whether the span this resumed run continues already has first words —
@@ -9280,8 +9043,8 @@ var init_turn_execution = __esm({
9280
9043
  resumedSpoke = false;
9281
9044
  turnContext;
9282
9045
  resolvedMcpServers;
9283
- // CT1698: the harness's secret environment for this turn. Only ever handed
9284
- // to the adapter; never logged, never merged into `process.env`.
9046
+ // The harness's secret environment for this turn. Only ever handed to the
9047
+ // adapter; never logged, never merged into `process.env`.
9285
9048
  secretEnv = {};
9286
9049
  effectiveCwd;
9287
9050
  hookEnv;
@@ -9295,19 +9058,15 @@ var init_turn_execution = __esm({
9295
9058
  turnControlIntentFetched = false;
9296
9059
  applyRecordedTurnControlIntent = async () => {
9297
9060
  };
9298
- // CT1292: the committer is built before the lease watchdog arms, so the
9299
- // failed-commit hint reaches it through this slot. Safe as a no-op until the
9300
- // watchdog replaces it — the committer only fires it from inside the adapter
9301
- // loop, which runs after.
9061
+ // The committer is built before the lease watchdog arms, so the failed-commit
9062
+ // hint reaches it through this slot. A no-op until then is safe: the committer
9063
+ // only fires it from inside the adapter loop, which runs after.
9302
9064
  noteCommitFailed = () => {
9303
9065
  };
9304
9066
  disarmWatchdogs = () => {
9305
9067
  };
9306
- // CT1503: reap this turn's shell containment. Idempotent (the containment drops
9307
- // what it has already stopped) and total — a reap that fails is reported and
9308
- // swallowed, because a turn that produced a good answer must not be recorded as
9309
- // failed on account of a leftover process, and the failure is visible in the log
9310
- // either way.
9068
+ // Idempotent, and a failed reap is logged and swallowed: a turn that produced a
9069
+ // good answer must not be recorded as failed on account of a leftover process.
9311
9070
  async reapContainment() {
9312
9071
  const containment = this.containment;
9313
9072
  if (!containment) return;
@@ -9333,20 +9092,18 @@ var init_turn_execution = __esm({
9333
9092
  );
9334
9093
  }
9335
9094
  }
9336
- // Codo's stack review, blocking finding #2: set the moment `acquireLease`'s
9337
- // PATCH returns — the lease is granted and this is the pair's running span,
9338
- // so every exit after this point must settle THE SPAN, not just clear the
9095
+ // Set the moment `acquireLease`'s PATCH returns: this is the pair's running
9096
+ // span, so every exit after this must settle THE SPAN, not just clear the
9339
9097
  // participant flag.
9340
9098
  admitted = false;
9341
- // Rooms: one recompose per turn when the lease refuses a stale context.
9099
+ // One recompose per turn when the lease refuses a stale context.
9342
9100
  recomposedForLease = false;
9343
- // CT1677: the execution source the CURRENT context was adopted as. Reassigned
9344
- // when a `dispatch_context_stale` recompose adopts a replacement, so a piece of
9345
- // work — preparation, the committer, a notice — captures it when that work
9346
- // begins and never reads it again: earlier work keeps its source. Undefined
9347
- // only before the first adoption; no work runs until it is set.
9101
+ // The execution source the CURRENT context was adopted as. A
9102
+ // `dispatch_context_stale` recompose reassigns it, so each piece of work
9103
+ // (preparation, the committer, a notice) captures it when it begins and keeps
9104
+ // that source. No work runs before it is set.
9348
9105
  executionSourceId;
9349
- // CT1677: one fresh fetch per turn when adoption is refused.
9106
+ // One fresh fetch per turn when adoption is refused.
9350
9107
  refetchedForAdoption = false;
9351
9108
  concluded(reason, errorReason) {
9352
9109
  return new TurnConcluded(reason, errorReason);
@@ -9414,21 +9171,18 @@ var init_turn_execution = __esm({
9414
9171
  this.supervisor.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
9415
9172
  return { ok: false, durationMs, reason, runtime: this.turnContext?.runtime };
9416
9173
  }
9417
- // The post-admission analogue of `concludeBeforeRun` (Codo's stack review,
9418
- // blocking finding #2): this turn WAS admitted — it holds the conversation's
9419
- // lease — so the clear must NAME the turn and DECLARE its terminal outcome.
9420
- // A bare pre-run clear here would reset the participant flag while the span
9421
- // stayed running until the lost sweep. `outcome` follows the settle wire's
9422
- // rule: a failure names its reason; without one the turn settled — the
9423
- // runtime-unavailable exit posts its user-facing `final` before concluding,
9424
- // so the turn genuinely produced its terminal output.
9174
+ // The admitted turn holds the conversation's lease, so the clear must NAME the
9175
+ // turn and DECLARE its outcome; a bare clear resets the participant flag and
9176
+ // leaves the span running until the lost sweep. A failure names its reason;
9177
+ // without one the turn settled (the runtime-unavailable exit has already posted
9178
+ // its `final`).
9425
9179
  async concludeAdmittedRun(reason, errorReason) {
9426
9180
  const { payload, turnLog, startedAt, turnId } = this;
9427
9181
  const body = {
9428
9182
  activeRunStartedAt: null,
9429
9183
  turnId,
9430
- // CT1379: `runtimeIncomplete` is a real failure that deliberately carries
9431
- // no `errorReason`, so the inference alone would read it as settled.
9184
+ // `runtimeIncomplete` is a real failure that deliberately carries no
9185
+ // `errorReason`, so the inference alone would read it as settled.
9432
9186
  outcome: errorReason || this.outcome.runtimeIncomplete ? "failed" : "settled"
9433
9187
  };
9434
9188
  if (errorReason) body.errorReason = errorReason.slice(0, 200);
@@ -9522,11 +9276,10 @@ var init_turn_execution = __esm({
9522
9276
  throw this.concluded(reason, reason);
9523
9277
  }
9524
9278
  }
9525
- // CT1677: register this context's issuance as the execution source of the
9526
- // work about to run on it. A context with no issuance has nothing to adopt,
9527
- // and that ends the turn exactly as a failed adoption does: this companion
9528
- // never commits a row whose provenance it cannot name. Only a refusal —
9529
- // nothing to adopt any more — earns a fresh fetch.
9279
+ // Register this context's issuance as the execution source of the work about to
9280
+ // run on it. No issuance ends the turn as a failed adoption does: this companion
9281
+ // never commits a row whose provenance it cannot name. Only a refusal earns a
9282
+ // fresh fetch.
9530
9283
  async adoptSource(context) {
9531
9284
  const { payload, turnId, turnLog } = this;
9532
9285
  if (!context.issuanceId) {
@@ -9575,9 +9328,8 @@ var init_turn_execution = __esm({
9575
9328
  secretEnv,
9576
9329
  missing
9577
9330
  } = resolveMcpSecrets(
9578
- // CT262: the user MCP DEFINITIONS (placeholder form) come from the turn
9579
- // context now, not a separate `getAgentSelf` run-config fetch. CT1698: and
9580
- // the room connections' entries, whose values arrive in `secretEnv`.
9331
+ // User MCP definitions in placeholder form, plus the room connections'
9332
+ // entries, whose values arrive in `secretEnv`.
9581
9333
  this.turnContext.mcpServers ?? {},
9582
9334
  secretStore,
9583
9335
  this.turnContext.secretEnv
@@ -9690,15 +9442,12 @@ var init_turn_execution = __esm({
9690
9442
  agentUsername: this.opts.agentUsername,
9691
9443
  runtime: this.turnContext.runtime,
9692
9444
  hostAccess: this.turnContext.policy.hostFs,
9693
- // CT317/CT319: the trigger message's referenced-entry paths — what the
9694
- // tasker prepare hook keys its per-task env off. Defaults to `[]` for
9695
- // an older API. The conversation anchor is gone (CT319).
9445
+ // The trigger message's referenced-entry paths, which a prepare hook
9446
+ // may key its per-task env off. `[]` from an older API.
9696
9447
  triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
9697
9448
  title: this.turnContext.conversation.title,
9698
- // CT943: the dispatching message's text — where an `env:` directive
9699
- // rides. The server has always sent the trigger body on the turn
9700
- // context (for the live feed); this is the first thing to read it as
9701
- // an INPUT, so a dispatch can ask for its environment in words.
9449
+ // Where an `env:` directive rides, so a dispatch can ask for its
9450
+ // environment in words.
9702
9451
  messageBody: this.turnContext.message.body
9703
9452
  },
9704
9453
  { turnId, deviceId: this.opts.deviceId },
@@ -9744,37 +9493,30 @@ var init_turn_execution = __esm({
9744
9493
  payload.agentId,
9745
9494
  {
9746
9495
  activeRunStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
9747
- // CT1679: a runner leases under its launcher's boot id — the one the
9748
- // device heartbeats — or the lost-run sweep reads it as a restart.
9496
+ // A runner leases under its launcher's boot id, the one the device
9497
+ // heartbeats, or the lost-run sweep reads it as a restart.
9749
9498
  bootId: this.opts.inRunner?.bootId ?? bootId,
9750
- // CT1680: the runner holding the span, beside the device's boot id.
9751
9499
  ...this.opts.inRunner ? { runnerId: this.opts.inRunner.runnerId } : {},
9752
- // CT1380: the durable record that this turn resumed, and from where.
9500
+ // The durable record that this turn resumed, and from where.
9753
9501
  ...this.resumedFromSeq !== void 0 ? { resumedFromSeq: this.resumedFromSeq } : {},
9754
- // CT1454: the issuance the context THIS attempt runs on came from. Read
9755
- // off `this.turnContext`, which is the fetch that composed this request —
9756
- // so a retransmitted start echoes the same id and reuses the same record,
9757
- // and a resumed attempt echoes its own, newer one. The post-admission
9758
- // fresh-session recompose (`freshRequest`) deliberately does not touch it:
9759
- // it adopts only the prompt, never the run-config, so its issuance must
9760
- // not become this attempt's evidence.
9502
+ // The issuance of the fetch that composed this request, so a
9503
+ // retransmitted start reuses the same record and a resumed attempt
9504
+ // echoes its own. `freshRequest` deliberately leaves it alone: it adopts
9505
+ // only the prompt, never the run-config.
9761
9506
  ...this.turnContext.issuanceId ? { issuanceId: this.turnContext.issuanceId } : {},
9762
- // CT33: hand the server this turn's id so the new-run chokepoint's
9763
- // `closeAbandonedTurns` sweep excludes it. The prepare hook may have
9764
- // already emitted a "preparing" activity row for this turn above (it
9765
- // runs before this flip), which would otherwise make the sweep mistake
9766
- // this live turn for an abandoned one and close it with a `stopped`.
9507
+ // So `closeAbandonedTurns` excludes this turn: a "preparing" row emitted
9508
+ // before this flip would otherwise read as abandoned and be stopped.
9767
9509
  turnId,
9768
9510
  // The newest message this turn's read covered: the lease records it,
9769
9511
  // and settle moves the cursor to it. Omitted when the server sent
9770
9512
  // none (an older API), so the cursor stays where it was.
9771
9513
  ...this.turnContext.readThrough !== void 0 ? { readThrough: this.turnContext.readThrough } : {},
9772
9514
  ...this.turnContext.readRevision !== void 0 ? { readRevision: this.turnContext.readRevision } : {},
9773
- // Rooms: the attempt this request's session belongs to (null = fresh).
9515
+ // The attempt this request's session belongs to (null = fresh).
9774
9516
  ...this.turnContext.attemptId !== void 0 ? { attemptId: this.turnContext.attemptId } : {},
9775
- // CT1543: the frame this start acts on. The turn-context fetch already
9776
- // checked it, but that was before an assembly this turn awaited — the
9777
- // lease checks it again where the stamp is actually taken.
9517
+ // The frame this start acts on. The context fetch checked it before an
9518
+ // assembly this turn awaited; the lease re-checks where the stamp is
9519
+ // taken.
9778
9520
  ...payload.episode !== void 0 ? { dispatchMessageId: payload.messageId, dispatchEpisode: payload.episode } : {}
9779
9521
  }
9780
9522
  );
@@ -9814,21 +9556,18 @@ var init_turn_execution = __esm({
9814
9556
  this.request = buildCompanionTurnRequest({
9815
9557
  turnContext: this.turnContext,
9816
9558
  baseUrl: this.opts.baseUrl,
9817
- // ACC8: always present; `fetchTurnContext` refused a context without one.
9559
+ // Always present: `fetchContext` refuses a context without one.
9818
9560
  turnToken: this.turnContext.turnToken,
9819
- // SJ524: the hook-resolved cwd overrides the static local cwd.
9820
9561
  ...this.effectiveCwd ? { cwd: this.effectiveCwd } : {},
9821
- // CT1103: the prepare-hook env plus the authoritative host-access token.
9822
9562
  env: this.turnEnv,
9823
9563
  mcpServers: this.resolvedMcpServers,
9824
9564
  secretEnv: this.secretEnv,
9825
9565
  turnControlServer,
9826
- // CT238: this turn's conversation, forwarded as the active-conversation
9827
- // header so a cross-thread post/spawn stamps its origin.
9566
+ // Sent as the active-conversation header so a cross-conversation post
9567
+ // stamps its origin.
9828
9568
  activeConversationId: payload.conversationId,
9829
- // Workspace coordinate retained in the compatible TurnRequest envelope.
9830
9569
  workspaceId,
9831
- // CT289: the operator's auto-memory escape hatch (machine-local), when set.
9570
+ // The operator's machine-local auto-memory escape hatch, when set.
9832
9571
  ...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
9833
9572
  });
9834
9573
  }
@@ -9865,8 +9604,8 @@ var init_turn_execution = __esm({
9865
9604
  if (this.opts.claudeCodeAvailable?.() ?? true) {
9866
9605
  this.containment = createTurnContainment(this.turnId, {
9867
9606
  log: (msg, meta) => turnLog.debug(meta ?? {}, msg),
9868
- // CT1679: a group outside the runner's own is reported, so a launcher
9869
- // can stop it if the runner dies without reaping it.
9607
+ // A group outside the runner's own is reported, so a launcher can stop
9608
+ // it if the runner dies without reaping it.
9870
9609
  ...this.opts.inRunner?.onProcessGroup ? { onProcessGroup: this.opts.inRunner.onProcessGroup } : {}
9871
9610
  });
9872
9611
  const containment = this.containment;
@@ -9887,8 +9626,8 @@ var init_turn_execution = __esm({
9887
9626
  const serverUrl = this.opts.opencodeServerUrl;
9888
9627
  adapters.push(
9889
9628
  createOpencodeAdapter(
9890
- // CT1679: the transport's per-server turn mutex is per process; in a
9891
- // runner the turn also holds the device-wide lock every runner shares.
9629
+ // The transport's per-server turn mutex is per process; in a runner the
9630
+ // turn also holds the device-wide lock every runner shares.
9892
9631
  this.opts.inRunner ? {
9893
9632
  transport: this.opts.inRunner.opencodeTransport(serverUrl),
9894
9633
  onWarn
@@ -9935,7 +9674,7 @@ var init_turn_execution = __esm({
9935
9674
  kind: "runtime_notice",
9936
9675
  turnId,
9937
9676
  parentMessageId: payload.messageId,
9938
- // CT1677: the notice is written by this attempt's source, like its speech.
9677
+ // Written by this attempt's source, like its speech.
9939
9678
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {}
9940
9679
  });
9941
9680
  } catch (postErr) {
@@ -9949,16 +9688,13 @@ var init_turn_execution = __esm({
9949
9688
  throw this.concluded(`runtime_unavailable:${err.runtime}`);
9950
9689
  }
9951
9690
  }
9952
- // The adapter refused to resume this pair's stored session and is starting a
9953
- // fresh one. The request it holds was composed for a session that had the
9954
- // conversation so far — the read is what landed since the cursor, the
9955
- // agent's own words left out because the session held them. A fresh session
9956
- // holds none of it, so fetch the context again as a first turn on an empty
9957
- // session (the whole conversation, own words included, the first-turn
9958
- // material) and hand the adapter that to run on — before it speaks or acts.
9959
- // The lease already granted stands; the read reaches at least its mark. A
9960
- // fetch that fails returns null and the adapter falls back to the request
9961
- // it has, flagged degraded, so the server rewinds the cursor at settle.
9691
+ // The adapter refused to resume the stored session and is starting fresh. The
9692
+ // request it holds assumed the session had the conversation so far (only what
9693
+ // landed since the cursor, the agent's own words left out); a fresh session
9694
+ // has none of it. So refetch as a first turn on an empty session and hand the
9695
+ // adapter that, before it speaks or acts. The granted lease stands. A failed
9696
+ // fetch returns null: the adapter runs the request it has, flagged degraded,
9697
+ // and the server rewinds the cursor at settle.
9962
9698
  async freshRequest(reason) {
9963
9699
  const { payload, turnId, turnLog } = this;
9964
9700
  try {
@@ -9969,9 +9705,8 @@ var init_turn_execution = __esm({
9969
9705
  false,
9970
9706
  true,
9971
9707
  void 0,
9972
- // CT1657: the turn's abort. This recompose happens mid-run with the
9973
- // adapter waiting on it, so a Stop here must end the wait rather than
9974
- // hold the turn open for the rest of the retry schedule.
9708
+ // The adapter is waiting on this, so a Stop must end the wait rather
9709
+ // than hold the turn open for the rest of the retry schedule.
9975
9710
  this.abortController.signal
9976
9711
  );
9977
9712
  turnLog.debug(
@@ -10023,19 +9758,17 @@ var init_turn_execution = __esm({
10023
9758
  conversationId: payload.conversationId,
10024
9759
  agentId: payload.agentId,
10025
9760
  turnId,
10026
- // CT113: the message this turn answers — passed back on every commit so
10027
- // the reply records the Q→A edge the in-process dispatcher stamps directly.
9761
+ // The message this turn answers, on every commit so the reply records the
9762
+ // Q→A edge.
10028
9763
  parentMessageId: payload.messageId,
10029
9764
  signal: abortController.signal,
10030
9765
  log: turnLog,
10031
9766
  nextSeq: this.nextSeq,
10032
9767
  spoke: this.resumedSpoke,
10033
- // CT1677: admitted by now, so this is the source the model's work runs on.
9768
+ // Admitted by now, so this is the source the model's work runs on.
10034
9769
  ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {},
10035
- // CT1292: a commit that didn't land may mean this turn's lease is gone.
10036
- // `noteCommitFailed` is a field slot the lease watchdog fills when it
10037
- // arms (it starts as a no-op) — the committer only fires it from inside
10038
- // the adapter loop, which runs after the watchdog has replaced it.
9770
+ // A commit that didn't land may mean the lease is gone; see
9771
+ // `noteCommitFailed`.
10039
9772
  onCommitFailed: (err) => this.noteCommitFailed(err)
10040
9773
  });
10041
9774
  const usesHttpTurnControl = turnRuntime === "codex" || turnRuntime === "opencode";
@@ -10139,9 +9872,9 @@ var init_turn_execution = __esm({
10139
9872
  workspaceId,
10140
9873
  payload.conversationId,
10141
9874
  payload.agentId,
10142
- // CT1680: named for its turn, which a runner credential
10143
- // requires once its start is admitted; the server then binds
10144
- // the capture to that turn's attempt rather than the pair's.
9875
+ // Named for its turn, which a runner credential requires once
9876
+ // admitted; the server binds the capture to that turn's attempt
9877
+ // rather than the pair's.
10145
9878
  { agentSessionId: event.state, turnId }
10146
9879
  );
10147
9880
  } catch (err) {
@@ -10246,9 +9979,7 @@ var init_turn_execution = __esm({
10246
9979
  }
10247
9980
  const body = {
10248
9981
  activeRunStartedAt: null,
10249
- // CT277: the server keys the turn-end metadata UPDATE (ended_at + tokens)
10250
- // off this turn id, so the settle PATCH must carry it — the start PATCH
10251
- // isn't enough. Always sent (every settle closes a real turn).
9982
+ // The server keys the turn-end metadata (ended_at, tokens) off this id.
10252
9983
  turnId
10253
9984
  };
10254
9985
  if (o.turnUsage) {
@@ -10417,11 +10148,11 @@ var init_dispatcher = __esm({
10417
10148
  this.aborts = opts.aborts ?? /* @__PURE__ */ new Map();
10418
10149
  }
10419
10150
  opts;
10420
- // CT1288: keyed (conversation|agent) → per-TURN controllers. Stop is
10421
- // pair-scoped and must reach every live loop for the pair.
10151
+ // (conversation|agent) → per-TURN controllers. Stop is pair-scoped and must
10152
+ // reach every live loop for the pair.
10422
10153
  aborts;
10423
- // CT1109: pairs already told "your session couldn't be saved" — the notice is
10424
- // once per (conversation, agent), while the rejection repeats every turn.
10154
+ // Pairs already told their session couldn't be saved: the notice is once per
10155
+ // pair, while the rejection repeats every turn.
10425
10156
  sessionWriteNotified = /* @__PURE__ */ new Set();
10426
10157
  notifyStart(info) {
10427
10158
  try {
@@ -10438,11 +10169,6 @@ var init_dispatcher = __esm({
10438
10169
  // The supervisor supplies a stable turnId on both fresh and resumed dispatch.
10439
10170
  // resumed is separate: only replay seeds sequence/speech recovery from the
10440
10171
  // prior span. A direct caller omitting turnId gets a fresh id.
10441
- //
10442
- // CT1261: a short `handle` creates a `TurnExecution` and runs it. The
10443
- // supervisor seam hands the execution exactly the three cross-turn duties —
10444
- // the abort registry, the session-write notice latch, the observer — and
10445
- // nothing else.
10446
10172
  async handle(payload, opts = {}) {
10447
10173
  const key = runKey(payload.conversationId, payload.agentId);
10448
10174
  const supervisor = {
@@ -10488,8 +10214,8 @@ var init_dispatcher = __esm({
10488
10214
  }
10489
10215
  return new TurnExecution(this.opts, supervisor, payload, opts).run();
10490
10216
  }
10491
- // SJ383: cancel a specific (conversation, agent) run if one is in flight in
10492
- // THIS companion process. Returns true if an in-flight run was aborted.
10217
+ // Cancel a (conversation, agent) run in flight in THIS process. True if one
10218
+ // was aborted.
10493
10219
  cancel(conversationId, agentId) {
10494
10220
  const key = runKey(conversationId, agentId);
10495
10221
  const pair2 = this.aborts.get(key);
@@ -11212,7 +10938,7 @@ var init_outbox = __esm({
11212
10938
  }
11213
10939
  workspaceId;
11214
10940
  log;
11215
- // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
10941
+ // Resolved lazily (per call) so tests that swap HOME between
11216
10942
  // cases route writes at the right tmpdir.
11217
10943
  dir() {
11218
10944
  return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
@@ -11278,7 +11004,7 @@ var init_outbox = __esm({
11278
11004
  );
11279
11005
  return entries;
11280
11006
  }
11281
- // CT1380: the highest `seq` this turn has queued but not yet delivered — the
11007
+ // The highest `seq` this turn has queued but not yet delivered — the
11282
11008
  // half of a resumed turn's seq floor the SERVER CANNOT SEE. A commit that
11283
11009
  // failed transiently before the crash is sitting in this directory with its
11284
11010
  // seq already spent, and the restart's drain is fire-and-forget
@@ -12091,9 +11817,8 @@ function parseHarnessRuntime(raw) {
12091
11817
  function deriveHarnessSnapshot(signals) {
12092
11818
  const advertised = new Set(
12093
11819
  buildCompanionManifest({
12094
- // CT1082 + CT1379: connected AND the bundled binary the SDK launches is
12095
- // there — the manifest's own rule, restated here through the same function
12096
- // rather than re-decided. PATH is deliberately absent from both gates now.
11820
+ // Connected AND the bundled binary present: the manifest's own rule. PATH
11821
+ // is deliberately in neither gate.
12097
11822
  claudeCode: signals.claudeCodeConnected && signals.claudeBundled === "present",
12098
11823
  opencode: signals.opencodeConfigured,
12099
11824
  codex: signals.codexEnabled && signals.codexBundled === "present"
@@ -12131,10 +11856,8 @@ function deriveClaudeCode(signals, manifestHas) {
12131
11856
  ...base,
12132
11857
  state: "detected_not_exposed",
12133
11858
  version: signals.claudeVersion,
12134
- // CT1379: connecting records INTENT; it is not the same as becoming
12135
- // runnable. With the bundled binary missing, a connect succeeds and the
12136
- // device still advertises nothing — so this says so up front rather than
12137
- // promising routing it can't deliver.
11859
+ // Connecting records INTENT, not runnability: with the bundled binary
11860
+ // missing a connect succeeds and nothing is advertised, so say so.
12138
11861
  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()}`,
12139
11862
  enable: "claude-code"
12140
11863
  };
@@ -12172,8 +11895,7 @@ function deriveCodex(signals, manifestHas) {
12172
11895
  ...base,
12173
11896
  state: "detected_not_exposed",
12174
11897
  version: signals.codexVersion,
12175
- // CT1379: same distinction as Claude Code — enabling is intent, not
12176
- // launchability.
11898
+ // As for Claude Code: enabling is intent, not launchability.
12177
11899
  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()}`,
12178
11900
  enable: "codex"
12179
11901
  };
@@ -12254,12 +11976,10 @@ async function probeHarnessSignals(cfg, deps = {}) {
12254
11976
  return {
12255
11977
  claudeOnPath: claudePresence.status === "present",
12256
11978
  claudeVersion,
12257
- // CT1082: the user's opt-in. Presence alone exposes nothing now, so this is
12258
- // the manifest gate and the probe above is only a suggestion.
11979
+ // The user's opt-in, the manifest gate; the probe above only suggests.
12259
11980
  claudeCodeConnected: isClaudeCodeConnected(cfg),
12260
- // CT1379: the executable a Claude turn actually launches. Resolved every time
12261
- // the signals are, so a binary that disappears between beats is caught on the
12262
- // next one rather than at turn time.
11981
+ // The executable a Claude turn launches, resolved with every probe so a
11982
+ // binary that disappears is caught on the next beat, not at turn time.
12263
11983
  claudeBundled: resolveBundled("claude-code").status,
12264
11984
  codexBundled: resolveBundled("codex").status,
12265
11985
  // A parseable `codex --version` is our presence signal (presence alone never
@@ -12268,7 +11988,7 @@ async function probeHarnessSignals(cfg, deps = {}) {
12268
11988
  codexVersion,
12269
11989
  codexEnabled: isCodexEnabled(cfg),
12270
11990
  opencodeConfigured: !!configuredServerUrl,
12271
- // A version came back ⟺ the serve answered its health endpoint (CT584).
11991
+ // A version came back ⟺ the serve answered its health endpoint.
12272
11992
  opencodeReachable: opencodeVersion !== null,
12273
11993
  opencodeVersion,
12274
11994
  opencodeDetectedUrl: opencodeVersion !== null ? opencodeProbeUrl : null
@@ -12837,13 +12557,11 @@ function resolvePairBaseUrl(server) {
12837
12557
  function writePairedConfig(paired) {
12838
12558
  const { local, note, hadPriorConfig } = loadConfigTolerant();
12839
12559
  const config = {
12840
- // CT1082: a device pairs connected to NOTHING — `claudeCode: { enabled: false }`
12841
- // written explicitly, not left absent, because absence is exactly what marks a
12842
- // pre-CT1082 config for the grandfathering migration. Omit it and a brand-new
12843
- // device gets auto-connected on its first start by the behaviour this task
12844
- // removes; stamp it on a RE-pair and an upgrading user's working device gets
12845
- // disconnected instead. So it's written only when there was no config here
12846
- // before. `local` spreads after, so a re-pair keeps the answer already given.
12560
+ // A device pairs connected to NOTHING: `claudeCode: { enabled: false }`
12561
+ // written explicitly, since absence is what the harness migration reads.
12562
+ // Omitted, a new device is auto-connected on first start; stamped on a
12563
+ // RE-pair, an upgrading user's working device is disconnected. So only when
12564
+ // there was no config before; `local` spreads after, keeping a prior answer.
12847
12565
  ...hadPriorConfig ? {} : { claudeCode: { enabled: false } },
12848
12566
  ...local,
12849
12567
  baseUrl: paired.baseUrl,
@@ -13151,7 +12869,7 @@ var CompanionStateHub = class {
13151
12869
  deviceId = null;
13152
12870
  deviceLabel = null;
13153
12871
  deviceError = null;
13154
- // CT586: the latest harness snapshot the supervisor probed, or null before the
12872
+ // The latest harness snapshot the supervisor probed, or null before the
13155
12873
  // first probe (see CompanionStatusJson.harnesses).
13156
12874
  harnesses = null;
13157
12875
  // ---- subscription (SSE) ----
@@ -13178,7 +12896,7 @@ var CompanionStateHub = class {
13178
12896
  this.deviceError = null;
13179
12897
  this.emitStatus();
13180
12898
  }
13181
- // ---- harnesses (CT586) ----
12899
+ // ---- harnesses ----
13182
12900
  // Replace the harness snapshot and push a status change (so the SSE feed flips
13183
12901
  // the Companion's Harnesses surface live). The supervisor calls this on each
13184
12902
  // heartbeat-cadence probe and on an on-demand recheck.
@@ -13796,7 +13514,7 @@ var DeviceApi = class {
13796
13514
  AbortSignal.timeout(5e3)
13797
13515
  );
13798
13516
  }
13799
- // CT1146: report that a dispatch addressed to THIS device can't be run, because
13517
+ // Report that a dispatch addressed to THIS device can't be run, because
13800
13518
  // the agent isn't one this device runs (and still wasn't after a forced
13801
13519
  // assignments refresh). The server clears the Working flag, retires the dispatch
13802
13520
  // and posts an explanatory system message.
@@ -13810,7 +13528,7 @@ var DeviceApi = class {
13810
13528
  return this.request("POST", "/api/companion/dispatch-drop", body);
13811
13529
  }
13812
13530
  // Per-device liveness ping. Reports the companion build version and the env-var
13813
- // names the operator's secret store exposes (never values), so CT30's UI can
13531
+ // names the operator's secret store exposes (never values), so the UI can
13814
13532
  // warn pre-emptively about an agent that needs a secret this device lacks.
13815
13533
  heartbeat(body) {
13816
13534
  return this.request("POST", "/api/companion/heartbeat", body ?? {});
@@ -13993,7 +13711,7 @@ var ConnectorHealthStore = class {
13993
13711
  lastError: outcome.errorReason ?? null
13994
13712
  });
13995
13713
  }
13996
- // CT639: the store's current opinion for one runtime, or undefined when it has
13714
+ // The store's current opinion for one runtime, or undefined when it has
13997
13715
  // no observation. Read by the dispatcher at idle-timeout fire: an idle reap
13998
13716
  // while this runtime is definitively `limited` (a present, future `limitedUntil`
13999
13717
  // recorded by a sibling/earlier turn's classified cap settle) is the account cap
@@ -15574,24 +15292,20 @@ var CompanionSupervisor = class {
15574
15292
  config;
15575
15293
  log;
15576
15294
  hub;
15577
- // CT1379: the bundled-executable resolver — the execution-presence half of both
15578
- // routing gates. Synchronous and cheap (a module resolve plus a stat), so unlike
15579
- // the CLI probes it needs no boot-frozen fallback: a caller that asks before the
15580
- // first harness refresh simply resolves now.
15295
+ // The execution-presence half of both routing gates. Synchronous and cheap (a
15296
+ // module resolve plus a stat), so a caller before the first harness refresh
15297
+ // simply resolves now.
15581
15298
  resolveBundled;
15582
- // CT1379: the last bundled status LOGGED per harness, so a missing binary is
15583
- // reported at startup and on each healthy↔missing transition rather than on
15584
- // every 30s beat (the CT484 latch pattern, per runtime).
15299
+ // The last bundled status LOGGED per harness, so a missing binary is reported
15300
+ // at startup and on each healthy↔missing transition, not every beat.
15585
15301
  loggedBundledStatus = /* @__PURE__ */ new Map();
15586
- // CT571: the probed per-harness versions, reported on each heartbeat manifest.
15587
- // CT586: no longer boot-frozen — the per-heartbeat harness re-probe refreshes
15588
- // it, so a harness updated mid-run reports its new version without a restart.
15302
+ // Refreshed by the per-heartbeat re-probe, so a harness updated mid-run
15303
+ // reports its new version without a restart.
15589
15304
  harnessVersions;
15590
- // CT586: the latest harness detection signals (presence + reachability), probed
15591
- // on the heartbeat cadence + on demand. Feeds BOTH the heartbeat manifest's
15592
- // live presence and the Companion's Harnesses surface off ONE source, so the UI
15593
- // can never disagree with what the manifest advertises. Null until the first
15594
- // probe (the heartbeat then falls back to the boot `claudeCode`).
15305
+ // The latest harness signals (presence, reachability), probed on the heartbeat
15306
+ // cadence and on demand. ONE source for both the manifest and the Harnesses
15307
+ // surface, so the UI never disagrees with what is advertised. Null until the
15308
+ // first probe.
15595
15309
  harnessSignals = null;
15596
15310
  // Fresh per attempt, never the cached heartbeat signal: installing a harness
15597
15311
  // between attempts must be observed immediately.
@@ -15604,8 +15318,8 @@ var CompanionSupervisor = class {
15604
15318
  unrunnableRetryDelaysMs;
15605
15319
  deviceApi = null;
15606
15320
  deviceSub = null;
15607
- // ACC8: whether the device stream is open, which is what every workspace's
15608
- // link on the dashboard reports.
15321
+ // Whether the device stream is open, which is what every workspace's link on
15322
+ // the dashboard reports.
15609
15323
  deviceConnected = false;
15610
15324
  recoveryChecked = false;
15611
15325
  recovering = false;
@@ -15617,8 +15331,7 @@ var CompanionSupervisor = class {
15617
15331
  pollTimer = null;
15618
15332
  refreshing = false;
15619
15333
  stopped = false;
15620
- // CT484: latch so the companion/server version-skew warning is logged once, not
15621
- // on every 30s heartbeat.
15334
+ // The version-skew warning is logged once, not every heartbeat.
15622
15335
  versionSkewWarned = false;
15623
15336
  // This device's id, captured from the heartbeat / assignments response. The SSE
15624
15337
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
@@ -15628,12 +15341,12 @@ var CompanionSupervisor = class {
15628
15341
  // The in-flight assignments pull, exposed so a caller that needs fresh data
15629
15342
  // (pull-on-miss) can `await` the running pull rather than racing past it.
15630
15343
  inFlightRefresh = null;
15631
- // CT566: device-global connector-health memory — every dispatcher records its
15632
- // turn's classified outcome here (per runtime); the heartbeat echoes it so the
15633
- // server's per-connector quota state survives a restart/race.
15344
+ // Device-global connector-health memory: every dispatcher records its turn's
15345
+ // classified outcome per runtime; the heartbeat echoes it so the server's
15346
+ // quota state survives a restart or race.
15634
15347
  connectorHealth = new ConnectorHealthStore();
15635
- // ACC10: which dispatches this process is running, so the copy of a frame that
15636
- // arrives on the other stream doesn't run the turn a second time.
15348
+ // Which dispatches this process is running, so a redelivered frame doesn't
15349
+ // run the turn a second time.
15637
15350
  claims = new DispatchClaims();
15638
15351
  constructor(opts) {
15639
15352
  this.config = opts.config;
@@ -15654,10 +15367,10 @@ var CompanionSupervisor = class {
15654
15367
  }
15655
15368
  this.latchBootBundledState();
15656
15369
  }
15657
- // CT1681: what a runner container said it can run, probed once at start;
15658
- // null until then, and when no container could be started.
15370
+ // What a runner container said it can run, probed once at start; null until
15371
+ // then, and when no container could be started.
15659
15372
  runnerImage = null;
15660
- // CT1681: the companion every container mounts — this one's own install.
15373
+ // The companion every container mounts: this one's own install.
15661
15374
  runnerCompanion() {
15662
15375
  return companionSource({
15663
15376
  configured: this.config.runners?.docker?.companionDir,
@@ -15769,10 +15482,7 @@ var CompanionSupervisor = class {
15769
15482
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
15770
15483
  this.pollTimer.unref?.();
15771
15484
  }
15772
- // The set of SSE subscriber `finished` promises — start.ts awaits these to
15773
- // keep the foreground process alive. With the poll loop running there's always
15774
- // at least the loops keeping the event loop alive; the SSE promises let a
15775
- // clean teardown resolve.
15485
+ // start.ts awaits these; they resolve on a clean teardown.
15776
15486
  finishedPromises() {
15777
15487
  return [...this.deviceSub ? [this.deviceSub.finished] : []];
15778
15488
  }
@@ -15800,39 +15510,26 @@ var CompanionSupervisor = class {
15800
15510
  bootId,
15801
15511
  version: COMPANION_VERSION,
15802
15512
  exposedSecretNames: store.names(),
15803
- // Report each runtime only when this device can actually run it:
15804
- // CT1379 claude-code and codex when the SDK's own bundled binary is there
15805
- // and the user asked for the harness, CT270 opencode when the operator
15806
- // configured an `opencode serve`.
15513
+ // Each runtime only when this device can actually run it.
15807
15514
  manifest: buildCompanionManifest({
15808
- // CT1082/CT1379: connected AND launchable. Execution presence is the
15809
- // live bundled-binary resolution (refreshed on this beat), consent is
15810
- // the user's `claudeCode.enabled`. Claude Code rides neither alone.
15515
+ // Consented (`claudeCode.enabled`) AND launchable (the live bundled
15516
+ // binary). Neither alone.
15811
15517
  claudeCode: this.claudeCodeOffered(),
15812
15518
  opencode: !!this.config.opencode?.serverUrl,
15813
- // CT481/CT1379: advertise codex when the operator enabled it AND its
15814
- // vendored binary is there. The enable flag alone used to be enough,
15815
- // which is how a device with a half-installed 331 MB platform package
15816
- // advertised a runtime that could not start.
15519
+ // Enabled AND its vendored binary present: a half-installed 331 MB
15520
+ // platform package otherwise advertises a runtime that cannot start.
15817
15521
  codex: this.codexOffered(),
15818
- // CT1379: and say WHY when an asked-for harness is absent from the list
15819
- // above. Diagnostic only — the server never routes on it — and always
15522
+ // WHY an asked-for harness is absent above. Diagnostic only, and always
15820
15523
  // sent, so an empty array clears a previously reported issue.
15821
15524
  runtimeIssues: this.runtimeIssues(),
15822
- // CT571/CT586: each runtime's `version` from the latest harness probe
15823
- // (fail-soft to null). Informational only — the server matches on name.
15525
+ // Informational only; the server matches on name.
15824
15526
  versions: this.harnessVersions,
15825
- // CT1681: shown on the device page.
15527
+ // Shown on the device page.
15826
15528
  ...runnersEnabled(this.config) ? { runner: this.runnerManifest() } : {}
15827
15529
  }),
15828
- // CT566: echo the last classified credential state per runtime, when the
15829
- // companion has seen any. Omitted (undefined) until the first observed
15830
- // failure/heal, so a fresh companion's beat is unchanged and the server's
15831
- // manifest synthesis (status-less rows) still runs.
15530
+ // Omitted until the first observed failure or heal, so the server's
15531
+ // status-less manifest synthesis still runs for a fresh companion.
15832
15532
  ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
15833
- // CT584: include enumerated models only when the probe SUCCEEDED (non-null).
15834
- // A null (probe failed / no opencode) omits the field, and the server then
15835
- // leaves this device's stored availability untouched.
15836
15533
  ...opencodeModels !== null ? { models: opencodeModels } : {}
15837
15534
  });
15838
15535
  this.hub.setDevice({ deviceId: res.deviceId });
@@ -15847,11 +15544,8 @@ var CompanionSupervisor = class {
15847
15544
  );
15848
15545
  }
15849
15546
  }
15850
- // CT484: belt-and-suspenders beside shipping the companion in lockstep inside the
15851
- // artifact — if the server reports a build version that differs from this
15852
- // companion's, warn loudly (once). This is exactly the skew that bit the M1 walk:
15853
- // an npm-pinned companion lagging a from-develop server. The elimination (bundled
15854
- // companion) makes it match by construction; this catches a companion run out of band.
15547
+ // Warn once when the server's build version differs from this companion's: an
15548
+ // npm-pinned companion lagging the server is the usual cause of odd failures.
15855
15549
  checkVersionSkew(serverVersion) {
15856
15550
  if (this.versionSkewWarned) return;
15857
15551
  if (!serverVersion || serverVersion === COMPANION_VERSION) return;
@@ -16048,10 +15742,9 @@ var CompanionSupervisor = class {
16048
15742
  `Stopped running ${runner.displayName} (unassigned)`
16049
15743
  );
16050
15744
  }
16051
- // CT1379: can this machine LAUNCH the harness — is the SDK's own bundled
16052
- // executable there? Live (the per-beat re-resolve), falling back to resolving
16053
- // right now before the first refresh lands. Execution presence ONLY; consent is
16054
- // the caller's half.
15745
+ // Can this machine LAUNCH the harness — is the SDK's bundled executable there?
15746
+ // The per-beat result, or resolved now before the first refresh. Execution
15747
+ // presence ONLY; consent is the caller's half.
16055
15748
  bundledStatus(runtime) {
16056
15749
  const cached2 = runtime === "codex" ? this.harnessSignals?.codexBundled : this.harnessSignals?.claudeBundled;
16057
15750
  return cached2 ?? this.resolveBundled(runtime).status;
@@ -16062,23 +15755,19 @@ var CompanionSupervisor = class {
16062
15755
  bundledResolution(runtime) {
16063
15756
  return this.resolveBundled(runtime);
16064
15757
  }
16065
- // CT1082/CT1379: does this device OFFER claude-code — connected by its user, and
16066
- // able to start the binary a turn runs? The ONE signal both the heartbeat
16067
- // manifest and the dispatcher's adapter registry read, so what the device
16068
- // advertises and what it can select can't disagree (the CT833 invariant, with
16069
- // consent in front of it and the real executable underneath it).
15758
+ // Does this device OFFER claude-code — connected by its user, and able to start
15759
+ // the binary a turn runs? The ONE signal both the heartbeat manifest and the
15760
+ // adapter registry read, so what the device advertises and what it can select
15761
+ // can't disagree.
16070
15762
  claudeCodeOffered() {
16071
15763
  return isClaudeCodeConnected(this.config) && this.bundledStatus("claude-code") === "present";
16072
15764
  }
16073
- // CT1379: and the same question for codex, which until now was the enable flag
16074
- // alone — so an enabled device with no vendored binary advertised a runtime it
16075
- // could not start. Same shape as Claude Code, same predicate on both sides.
15765
+ // The same question for codex: enabled AND its vendored binary present.
16076
15766
  codexOffered() {
16077
15767
  return isCodexEnabled(this.config) && this.bundledStatus("codex") === "present";
16078
15768
  }
16079
- // CT1379: the manifest's diagnostic half, derived from the same two facts as the
16080
- // two predicates above so an issue and an advertisement can never both be made
16081
- // for one harness.
15769
+ // Derived from the same two facts as the predicates above, so an issue and an
15770
+ // advertisement are never both made for one harness.
16082
15771
  runtimeIssues() {
16083
15772
  return runtimeIssuesFor({
16084
15773
  claudeCode: {
@@ -16088,26 +15777,16 @@ var CompanionSupervisor = class {
16088
15777
  codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") }
16089
15778
  });
16090
15779
  }
16091
- // CT1379: a missing binary is reported ONCE, and again only when the answer
16092
- // CHANGES — with the runtime, the platform/architecture and the resolution
16093
- // result, which is enough to find the occurrence in `companion.log` without a
16094
- // repro and carries nothing that could be a credential or a vendor's output.
16095
- //
16096
- // Two things this deliberately does NOT do (Codo's review, blocking finding #2):
15780
+ // A missing binary is reported ONCE, and again only when the answer CHANGES,
15781
+ // with runtime, platform/arch and status: enough to find it in `companion.log`
15782
+ // without a repro, and nothing that could be a credential or vendor output.
16097
15783
  //
16098
- // - It does not re-announce the state the device booted in. `warnAboutHarnessReadiness`
16099
- // already said that on stderr at startup, in the specified words, at the one
16100
- // moment a person is watching a terminal — and the `--daemon` launcher runs
16101
- // that preflight in the FOREGROUND for exactly that reason, because the
16102
- // child's output goes to a file. The boot state is latched in the
16103
- // constructor, so the first refresh reports nothing new; it lands here as a
16104
- // debug record instead, present for diagnosis without competing for
16105
- // attention with the warning the operator already read.
16106
- // - It does not report a transition in generic prose. A binary that goes
16107
- // missing at 3am is the case the operator will actually meet, and a line
16108
- // that says only "binary is missing" leaves them with no repair and no
16109
- // restart instruction. Transitions carry the specified copy verbatim — the
16110
- // same sentence the startup path prints.
15784
+ // - The boot state is not re-announced: `warnAboutHarnessReadiness` said it
15785
+ // on stderr at startup, when a person is watching (the `--daemon` launcher
15786
+ // runs that preflight in the FOREGROUND because the child logs to a file).
15787
+ // It is latched in the constructor and recorded at debug.
15788
+ // - A transition carries the same repair copy the startup path prints, not
15789
+ // generic prose: a binary missing at 3am needs the repair and the restart.
16111
15790
  logBundledTransitions() {
16112
15791
  for (const runtime of ["claude-code", "codex"]) {
16113
15792
  const intended = runtime === "codex" ? isCodexEnabled(this.config) : isClaudeCodeConnected(this.config);
@@ -16130,9 +15809,8 @@ var CompanionSupervisor = class {
16130
15809
  this.log.debug(meta, "companion: bundled harness binary is missing (harness not in use)");
16131
15810
  }
16132
15811
  }
16133
- // CT1379: the boot state, latched so the first refresh reports no news, and
16134
- // recorded once as evidence. Runs in the constructor — the resolve is a module
16135
- // lookup plus a stat, so it costs nothing and needs no await.
15812
+ // The boot state, latched so the first refresh reports no news, and recorded
15813
+ // once as evidence. Synchronous: a module lookup plus a stat.
16136
15814
  latchBootBundledState() {
16137
15815
  for (const runtime of ["claude-code", "codex"]) {
16138
15816
  const resolution = this.resolveBundled(runtime);
@@ -16150,35 +15828,34 @@ var CompanionSupervisor = class {
16150
15828
  );
16151
15829
  }
16152
15830
  }
16153
- // CT1679: the locks every runner on this device shares, held here.
15831
+ // The locks every runner on this device shares.
16154
15832
  runnerLocks = new DeviceLocks();
16155
- // CT1679: `runners.sandbox`, checked against this host once. A host that
16156
- // can't make the sandbox says so once and runs its turns unwrapped.
15833
+ // `runners.sandbox`, checked against this host once. A host that can't make
15834
+ // the sandbox says so once and runs its turns unwrapped.
16157
15835
  sandboxUsable;
16158
15836
  sandboxHost = {
16159
15837
  osSandboxUsable: () => this.sandboxUsable ??= osSandboxUsable(this.log)
16160
15838
  };
16161
- // CT1684: `runners.policy`, parsed once. An invalid one is kept as the
16162
- // error, which every sandboxed turn is refused with.
15839
+ // `runners.policy`, parsed once. An invalid one is kept as the error, which
15840
+ // every sandboxed turn is refused with.
16163
15841
  parsedSandboxPolicy;
16164
15842
  sandboxPolicy() {
16165
15843
  this.parsedSandboxPolicy ??= parseSandboxPolicy(this.config.runners?.policy);
16166
15844
  return this.parsedSandboxPolicy;
16167
15845
  }
16168
- // CT1691: the walk for other companions' stores, run off the turn path.
15846
+ // The walk for other companions' stores, run off the turn path.
16169
15847
  storeWatch;
16170
15848
  companionStores() {
16171
15849
  this.storeWatch ??= companionStoreWatch(this.log);
16172
15850
  return this.storeWatch;
16173
15851
  }
16174
- // CT1693: the sandbox one agent's turns run under — its `agents` row, then
16175
- // the device's. Resolved from the current config every time it's asked, so
16176
- // a reload changes it; only the host probe is kept.
15852
+ // The sandbox one agent's turns run under: its `agents` row, then the
15853
+ // device's. Resolved from the current config on every ask, so a reload
15854
+ // changes it; only the host probe is kept.
16177
15855
  runnerSandboxFor(row) {
16178
15856
  return runnerSandboxFor(this.config, row, this.sandboxHost);
16179
15857
  }
16180
- // CT1693: what the heartbeat says about this device's runners, resolved
16181
- // against the agents it runs right now.
15858
+ // Resolved against the agents this device runs right now.
16182
15859
  runnerManifest() {
16183
15860
  const agents = [...this.workspaces.values()].flatMap(
16184
15861
  (wr) => [...wr.agents.values()].map((agent) => ({
@@ -16192,7 +15869,7 @@ var CompanionSupervisor = class {
16192
15869
  ...runnerSandboxReport(this.config, agents, this.sandboxHost)
16193
15870
  };
16194
15871
  }
16195
- // CT1679: one provider for the device, built on first use.
15872
+ // One provider for the device, built on first use.
16196
15873
  runnerProvider() {
16197
15874
  this.providerForRunners ??= this.dockerRunners() ? this.createDockerRunners() : createLocalProcessProvider();
16198
15875
  return this.providerForRunners;
@@ -16234,8 +15911,7 @@ var CompanionSupervisor = class {
16234
15911
  log: this.log
16235
15912
  });
16236
15913
  }
16237
- // CT1681: the runner command for this device's provider: the image's own
16238
- // companion in a container, this process's otherwise.
15914
+ // The image's own companion in a container, this process's otherwise.
16239
15915
  runnerCommandFor() {
16240
15916
  if (this.runnerCommand) return this.runnerCommand;
16241
15917
  return this.dockerRunners() ? ["cabane-companion", "run-turn"] : selfRunnerCommand();
@@ -16267,36 +15943,20 @@ var CompanionSupervisor = class {
16267
15943
  aborts: ctx.aborts,
16268
15944
  runConfig: ctx.runConfig,
16269
15945
  log: this.log.child({ agentName: ctx.agentDisplayName ?? ctx.agentUsername }),
16270
- // CT833: register the claude-code adapter only when this device actually
16271
- // offers claude-code — read per turn (not captured here), so a harness
16272
- // installed or connected after boot works on the next turn exactly as it
16273
- // appears on the next beat. CT1082: "offers" means connected as well as
16274
- // runnable, so a disconnected harness can't be selected either.
16275
- // CT1379: and the same for the bundled binary — the predicate is shared with
16276
- // the heartbeat above, so a runtime advertised by one and refused by the
16277
- // other is not expressible.
15946
+ // Read PER TURN off the heartbeat's own predicates, so a harness installed
15947
+ // or connected after boot works on the next turn exactly as it appears on
15948
+ // the next beat, and a runtime advertised by one and refused by the other
15949
+ // is not expressible.
16278
15950
  claudeCodeAvailable: () => this.claudeCodeOffered(),
16279
- // CT270: the opencode server URL (operator-configured), when this device
16280
- // offers the opencode runtime. Threaded so an opencode turn selects the
16281
- // opencode adapter; unset leaves the device claude-code-only.
16282
15951
  ...this.config.opencode?.serverUrl ? { opencodeServerUrl: this.config.opencode.serverUrl } : {},
16283
- // CT481: register the codex adapter when this device offers codex; a miss
16284
- // leaves an `openai/…` turn to fail loudly (no silent claude-code fallback).
16285
- // CT1379: read PER TURN, like claude-code above, and off the same predicate
16286
- // the heartbeat uses — it was captured here at dispatcher-construction time
16287
- // from the enable flag alone, so a device could register an adapter for a
16288
- // binary it did not have and keep it registered after the binary came back.
16289
15952
  codexAvailable: () => this.codexOffered(),
16290
- // CT556: per-turn timeout watchdog windows, from the companion's own env
16291
- // (`AGENT_IDLE_TIMEOUT_MS` / `AGENT_TOTAL_TIMEOUT_MS`). Unset → the
16292
- // dispatcher's baked-in defaults (CT1503: 15 min idle / 6h total). An
16293
- // override set here still wins, and is deliberately not validated against
16294
- // the tool ceilings it may reorder — see DEFAULT_AGENT_IDLE_TIMEOUT_MS.
15953
+ // Unset uses the dispatcher's defaults. An override is deliberately not
15954
+ // validated against the tool ceilings it may reorder; see
15955
+ // DEFAULT_AGENT_IDLE_TIMEOUT_MS.
16295
15956
  ...positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") !== void 0 ? { idleTimeoutMs: positiveIntEnv("AGENT_IDLE_TIMEOUT_MS") } : {},
16296
15957
  ...positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") !== void 0 ? { totalTimeoutMs: positiveIntEnv("AGENT_TOTAL_TIMEOUT_MS") } : {},
16297
15958
  observer: this.hub.observerFor(ctx.workspaceId, ctx.agentId, ctx.workspaceSlug),
16298
15959
  transcriptDir: transcriptsDir(),
16299
- // CT566: the shared connector-health memory the turn records into on settle.
16300
15960
  connectorHealth: this.connectorHealth,
16301
15961
  ...runnersEnabled(this.config) ? {
16302
15962
  runner: {
@@ -16320,11 +15980,10 @@ var CompanionSupervisor = class {
16320
15980
  this.workspaces.delete(workspaceId);
16321
15981
  this.hub.removeWorkspace(workspaceId);
16322
15982
  }
16323
- // ---- SSE event routing (per workspace, BY agentId) ----
16324
- // ACC10: the device stream's frames — dispatch and cancel, addressed to this
16325
- // device and authenticated as it. ACC8: the only stream this companion opens.
16326
- // It keeps no cursor: it replays what is still offered on every connect
16327
- // instead of resuming from a `Last-Event-ID`.
15983
+ // ---- SSE event routing (BY agentId) ----
15984
+ // Dispatch and cancel, addressed to this device. The stream keeps no cursor:
15985
+ // it replays what is still offered on every connect instead of resuming from
15986
+ // a `Last-Event-ID`.
16328
15987
  async handleDeviceMessage(ev) {
16329
15988
  const wire = this.parseFrame(ev);
16330
15989
  if (!wire) return;
@@ -16360,9 +16019,9 @@ var CompanionSupervisor = class {
16360
16019
  return null;
16361
16020
  }
16362
16021
  }
16363
- // ACC10: the dedupe gate. A reconnect replays every dispatch still offered,
16364
- // including one whose turn is already running here; the first copy runs the
16365
- // turn and a later one waits for it and then does nothing.
16022
+ // A reconnect replays every dispatch still offered, including one whose turn
16023
+ // is already running here; the first copy runs it and a later one waits for
16024
+ // it and then does nothing.
16366
16025
  //
16367
16026
  // A cancel needs no gate: `dispatcher.cancel` on an already-aborted run is a
16368
16027
  // no-op that reports `false`, so a second copy is inert.
@@ -16378,8 +16037,6 @@ var CompanionSupervisor = class {
16378
16037
  if (claim) this.claims.release(wire.id);
16379
16038
  }
16380
16039
  }
16381
- // Everything below here is the routing as it was: it takes a frame and knows
16382
- // nothing about which stream carried it.
16383
16040
  async routeFrame(wr, wire) {
16384
16041
  if (wire.type === "device:cancel_requested") {
16385
16042
  const payload2 = {
@@ -16445,17 +16102,12 @@ var CompanionSupervisor = class {
16445
16102
  await tail;
16446
16103
  if (wr.chains.get(chainKey) === tail) wr.chains.delete(chainKey);
16447
16104
  }
16448
- // CT1146: tell the server this device cannot run a dispatch it was addressed to.
16449
- //
16105
+ // Tell the server this device cannot run a dispatch it was addressed to.
16450
16106
  // Returns whether the report LANDED. The server checks the recorded dispatch
16451
- // destination even if the agent's assignment has moved. Reporting promptly
16452
- // explains the roster miss; the thirty-minute unanswered-offer deadline is
16453
- // only the backstop when no report lands.
16107
+ // destination even if the assignment has moved.
16454
16108
  //
16455
- // Bounded retry, because the common failure is transient (a 503 mid-deploy, a
16456
- // dropped socket) and one more attempt a second later usually lands. Bounded
16457
- // hard — this runs inline on the event loop, and an unbounded wait here would
16458
- // wedge the workspace's whole stream.
16109
+ // Retried, because the common failure is transient (a 503 mid-deploy), and
16110
+ // bounded hard: this runs inline, and an unbounded wait would wedge the stream.
16459
16111
  async reportUnrunnableDispatch(workspaceId, payload) {
16460
16112
  if (!this.deviceApi) return false;
16461
16113
  let lastErr;
@@ -16495,9 +16147,8 @@ var CompanionSupervisor = class {
16495
16147
  );
16496
16148
  return false;
16497
16149
  }
16498
- // CT1680: a runner is gone without having settled its turn. Same bounded
16499
- // retry as the unrunnable report above, on the device credential, which is
16500
- // the one the launcher still holds whatever became of the runner.
16150
+ // A runner is gone without having settled its turn. Same bounded retry, on the
16151
+ // device credential, which the launcher holds whatever became of the runner.
16501
16152
  async reportRunnerGone(report) {
16502
16153
  if (!this.deviceApi) return;
16503
16154
  let lastErr;
@@ -16522,12 +16173,9 @@ var CompanionSupervisor = class {
16522
16173
  "Couldn't tell Cabane a turn's runner stopped; its credential expires on its own."
16523
16174
  );
16524
16175
  }
16525
- // Pull-on-miss for the assignment-change race: a dispatch arrived for an agent
16526
- // we don't yet run. If it's addressed to THIS device, the assignment just
16527
- // changed under us (the poll hasn't caught it) — force a fresh assignments pull
16528
- // and return the now-live runner. Returns undefined when the dispatch isn't for
16529
- // this device, or the agent still isn't ours after pulling (a genuine
16530
- // not-ours dispatch) — the caller then settles and ignores it as before.
16176
+ // Pull-on-miss: a dispatch addressed to THIS device for an agent we don't run
16177
+ // yet means the assignment changed under us. Undefined when it isn't for this
16178
+ // device, or the agent still isn't ours after the pull.
16531
16179
  async recoverRacedAgent(wr, payload) {
16532
16180
  if (!this.deviceId || payload.deviceId !== this.deviceId) return void 0;
16533
16181
  if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
@@ -16568,9 +16216,8 @@ var CompanionSupervisor = class {
16568
16216
  };
16569
16217
  const tail = this.runDispatch(
16570
16218
  wr,
16571
- // No frame delivered this one — it came off `/runs` — so there is no
16572
- // dispatch id to dedupe on. The `recoveredTurns` set above is this
16573
- // path's own guard.
16219
+ // Off `/runs`, not a frame, so no dispatch id to dedupe on;
16220
+ // `recoveredTurns` is this path's guard.
16574
16221
  "",
16575
16222
  payload,
16576
16223
  agent,
@@ -16592,9 +16239,7 @@ var CompanionSupervisor = class {
16592
16239
  this.recovering = false;
16593
16240
  }
16594
16241
  }
16595
- // One turn: the SJ516 F7 / CT26 skip-vs-resume dedupe, the dispatch, then
16596
- // the console summary. Mirrors the old per-workspace `handleEvent`, now
16597
- // scoped to one agent.
16242
+ // One turn: the skip-vs-resume dedupe, the dispatch, then the console summary.
16598
16243
  async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId) {
16599
16244
  const workspaceId = wr.workspaceId;
16600
16245
  if (dispatchId && hasDispatched(workspaceId, dispatchId)) {
@@ -16679,7 +16324,7 @@ var CompanionSupervisor = class {
16679
16324
  );
16680
16325
  }
16681
16326
  }
16682
- // ---- per-agent outbox drain scheduler (CT25) ----
16327
+ // ---- per-agent outbox drain scheduler ----
16683
16328
  makeDrain(api, agentId) {
16684
16329
  let drainTimer;
16685
16330
  let drainDelay = DRAIN_BASE_MS;
@@ -16748,19 +16393,16 @@ var CompanionSupervisor = class {
16748
16393
  saveConfig(next);
16749
16394
  configureLogger(this.log, next);
16750
16395
  }
16751
- // ---- harnesses (CT586) ----
16752
- // Probe all three harnesses, cache the signals + versions, and push the derived
16753
- // per-harness snapshot to the hub (which flips the Companion's Harnesses surface
16754
- // over SSE). Fail-soft by construction — `probeHarnessSignals` never throws — but
16755
- // wrapped anyway so a harness refresh can never take a heartbeat down. Called on
16756
- // the heartbeat cadence (fresh presence for the manifest + UI) and on demand.
16396
+ // ---- harnesses ----
16397
+ // Probe all three harnesses, cache signals and versions, and push the snapshot
16398
+ // to the hub. `probeHarnessSignals` never throws, but this is wrapped anyway so
16399
+ // a refresh can never take a heartbeat down.
16757
16400
  async refreshHarnessStatuses(presence = {}) {
16758
16401
  try {
16759
16402
  const signals = await probeHarnessSignals(this.config, {
16760
16403
  probePresence: this.probePresence,
16761
16404
  presence,
16762
- // CT1379: the bundled executables are resolved on the same cadence as the
16763
- // CLI probes, through the supervisor's seam so a test pins the host fact.
16405
+ // Through the supervisor's seam, so a test pins the host fact.
16764
16406
  resolveBundled: this.resolveBundled
16765
16407
  });
16766
16408
  this.harnessSignals = signals;
@@ -16783,11 +16425,10 @@ var CompanionSupervisor = class {
16783
16425
  async recheckHarnesses() {
16784
16426
  await this.refreshHarnessStatuses();
16785
16427
  }
16786
- // CT1085 §1 step 3: beat NOW and wait for it to land. `start` calls this
16787
- // between bringing the runtime up and asking the person anything, so the
16788
- // browser's connect step is already showing what this machine has ("your
16789
- // terminal is asking") rather than sitting blank while the terminal blocks on
16790
- // an answer. Coalesces with an in-flight beat rather than stacking a second.
16428
+ // Beat NOW and wait for it to land. `start` calls this before asking the
16429
+ // person anything, so the browser's connect step already shows what this
16430
+ // machine has rather than sitting blank while the terminal waits on an answer.
16431
+ // Coalesces with an in-flight beat.
16791
16432
  async heartbeatNow() {
16792
16433
  if (this.inFlightHeartbeat) {
16793
16434
  await this.inFlightHeartbeat;
@@ -16796,35 +16437,25 @@ var CompanionSupervisor = class {
16796
16437
  this.kickHeartbeat();
16797
16438
  if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
16798
16439
  }
16799
- // CT1085: the config as it stands right now — after any `enableHarness` write.
16800
- // The control socket's connect handler needs it to run the shake-out check
16801
- // against the URL/flag that was just persisted, not the one this process booted
16802
- // with.
16440
+ // The config as it stands now, after any `enableHarness` write: the control
16441
+ // socket's shake-out check needs what was just persisted, not the boot config.
16803
16442
  currentConfig() {
16804
16443
  return this.config;
16805
16444
  }
16806
- // Friendly enable for the config-driven harnesses — flip the flag the app owns in
16807
- // `~/.cabane/config.json`, no hand-edited JSON. It never installs a binary and
16808
- // never drives a login (BYO — Decided).
16445
+ // Enable a config-driven harness by flipping the flag the app owns in
16446
+ // `~/.cabane/config.json`. Never installs a binary or drives a login (BYO).
16809
16447
  //
16810
- // CT1379: this records INTENT; it does not by itself expose anything. A harness
16811
- // is advertised only when the user asked for it AND the SDK's own bundled binary
16812
- // resolves, so a successful enable on a companion whose optional dependency
16813
- // never installed leaves the device advertising nothing — correctly, and with
16814
- // the Harnesses surface saying why. Read every "expose" below as "record the
16815
- // half of the answer the user owns".
16816
- // - claude-code (CT1082): set `claudeCode.enabled`, after checking `claude` is
16817
- // on PATH. This is the callable the terminal offer ("We found Claude Code.
16818
- // Connect it? [Y/n]") and the web pairing flow both wire to.
16819
- // - codex: set `codex.enabled` after checking `codex` is on PATH (the CLI +
16820
- // `codex login` remain the user's).
16821
- // - opencode: set `opencode.serverUrl` — but ONLY after health-probing the URL,
16822
- // so we never advertise a serve that isn't there. An unreachable URL is a
16823
- // typed failure the UI shows in place, not a silent bad write.
16824
- // On success, persist, re-probe, and beat immediately, so a runtime that IS
16825
- // launchable starts taking turns at once rather than on the next tick — and one
16826
- // that isn't reports its issue just as promptly. Returns a typed outcome (never
16827
- // throws for a reachable/valid input).
16448
+ // This records INTENT, the user's half: a harness is advertised only when also
16449
+ // its bundled binary resolves, so an enable on a companion whose optional
16450
+ // dependency never installed advertises nothing, and the Harnesses surface
16451
+ // says why.
16452
+ // - claude-code: `claudeCode.enabled`, after a presence check. The terminal
16453
+ // offer and the web pairing flow both call this.
16454
+ // - codex: `codex.enabled`, after a presence check (login stays the user's).
16455
+ // - opencode: `opencode.serverUrl`, ONLY after health-probing the URL; an
16456
+ // unreachable one is a typed failure shown in place.
16457
+ // On success: persist, re-probe and beat at once, so the answer is true in
16458
+ // Cabane promptly either way. Never throws for a valid input.
16828
16459
  async enableHarness(input) {
16829
16460
  let next;
16830
16461
  let checkedPresence = null;
@@ -16875,9 +16506,8 @@ var CompanionSupervisor = class {
16875
16506
  return { ok: true };
16876
16507
  }
16877
16508
  // Re-create every running agent's Dispatcher from the CURRENT config, keeping
16878
- // the runner (its api/outbox/chains/SSE) intact. Used after a runtime-enabling
16879
- // config change so constructor-captured options — per-agent cwd/prepareHook and
16880
- // the adapter registry — pick up their new values without an app restart.
16509
+ // the runner (its api, outbox, chains) intact, so constructor-captured options
16510
+ // pick up a config change without a restart.
16881
16511
  rebuildDispatchers() {
16882
16512
  for (const wr of this.workspaces.values()) {
16883
16513
  for (const runner of wr.agents.values()) {
@@ -17045,8 +16675,7 @@ async function createCompanionRuntime(opts = {}) {
17045
16675
  }));
17046
16676
  await warnAboutHarnessReadiness(cfg, {
17047
16677
  probeClaude: async () => claudeCode,
17048
- // CT1085: the CLI's onboarding script owns the terminal and says this in
17049
- // its own words (the `!` block, or the per-harness offer), so it hands us a
16678
+ // The CLI's onboarding script says this in its own words, so it hands a
17050
16679
  // sink that logs instead. Every other caller keeps the stderr line.
17051
16680
  ...opts.onReadinessWarning ? { warn: opts.onReadinessWarning } : {}
17052
16681
  });
@@ -17083,11 +16712,10 @@ async function createCompanionRuntime(opts = {}) {
17083
16712
  const clearRuntimeOnExit = () => clearRuntimeStateIfOurs(instanceId);
17084
16713
  process.on("exit", clearRuntimeOnExit);
17085
16714
  const hub = new CompanionStateHub({
17086
- // CT29: one device, one base URL — the cabane instance this device is paired
17087
- // with. The dashboard's connection line shows it.
16715
+ // The dashboard's connection line shows it.
17088
16716
  baseUrl: cfg.baseUrl,
17089
16717
  companionVersion: COMPANION_VERSION,
17090
- // SJ516 F4: surfaced on `/api/status` so `stop`/`status` can confirm the
16718
+ // Surfaced on `/api/status` so `stop`/`status` can confirm the
17091
16719
  // process behind the marker pid is this companion (not a recycled pid).
17092
16720
  instanceId
17093
16721
  });
@@ -17140,7 +16768,7 @@ async function createCompanionRuntime(opts = {}) {
17140
16768
  socket: control.path,
17141
16769
  ...dashboard ? { url: dashboard.url, port: dashboard.port } : {},
17142
16770
  startedAt,
17143
- // SJ495: the daemon launcher sets this env on the detached child, so the
16771
+ // The daemon launcher sets this env on the detached child, so the
17144
16772
  // marker records whether this companion is backgrounded (foreground start
17145
16773
  // leaves it unset → false).
17146
16774
  daemon: process.env.CABANE_COMPANION_DAEMON === "1",
@@ -17244,7 +16872,7 @@ async function startDaemon(opts = {}, deps = {}) {
17244
16872
  });
17245
16873
  await ensureRuntime(cfg, {
17246
16874
  probeClaude: async () => claudeOnPath2,
17247
- // CT1085: the onboarding script already said what's connectable, in its own
16875
+ // The onboarding script already said what's connectable, in its own
17248
16876
  // words. Saying it again here, on stderr, in the middle of the closing block
17249
16877
  // would be the same news twice.
17250
16878
  ...quiet ? { warn: () => {