@cabane/companion 0.6.202 → 0.6.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.js CHANGED
@@ -167,25 +167,7 @@ var prepareHookSchema = z.object({
167
167
  // Wall-clock cap for the hook. Provisioning is slow (minutes), so the
168
168
  // default is generous; a hook that hangs past this is killed and the turn
169
169
  // fails with a clear timeout message rather than pinning the companion.
170
- timeoutMs: z.number().int().positive().optional(),
171
- // Re-invoke the hook when a prepared result already exists, in VALIDATE
172
- // mode: the cached result is handed back as `prepared` and stdout is
173
- // ignored, so the cwd cannot move. Its only power is to exit non-zero and
174
- // fail the turn. The hook resolves once per (conversation, agent), so a
175
- // later message asking for a different environment is otherwise silently
176
- // ignored; this lets a hook refuse it out loud.
177
- //
178
- // Default OFF: a hook that provisions unconditionally must not start
179
- // running on every turn because the companion upgraded.
180
- validateCached: z.boolean().optional(),
181
- // Independent operator-owned reporter. Receives sanitized failure metadata
182
- // on stdin; never re-enters this hook and cannot change the turn outcome.
183
- failureReporter: z.object({
184
- command: z.string().min(1),
185
- args: z.array(z.string()).optional(),
186
- env: z.record(z.string(), z.string()).optional(),
187
- timeoutMs: z.number().int().positive().max(3e4).optional()
188
- }).strict().optional()
170
+ timeoutMs: z.number().int().positive().optional()
189
171
  }).strict();
190
172
  var DEFAULT_TIMEOUT_MS = 10 * 6e4;
191
173
  var prepareResultSchema = z.object({
@@ -260,8 +242,7 @@ var companionConfigSchema = z2.object({
260
242
  // The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
261
243
  // `agents` row names one. Per-agent hooks alone make the config a second
262
244
  // silent roster: an agent nobody listed gets no hook and no error, and starts
263
- // in the operator's `$HOME` (an agent woke in `/home/joshed` this way on
264
- // 2026-08-17).
245
+ // in the operator's `$HOME`.
265
246
  prepareHook: prepareHookSchema.optional(),
266
247
  // `dashboardPort` and `autoOpen` are IGNORED by the CLI (no port, no browser)
267
248
  // but stay in the schema so an existing file still parses; the Electron shell
@@ -298,9 +279,10 @@ var companionConfigSchema = z2.object({
298
279
  root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
299
280
  // An argv prefix the operator puts in front of every turn's runner: the companion starts
300
281
  // `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
301
- // is the operator's (a box of their own making, ENG225); the companion knows nothing of it
282
+ // is the operator's (a container or VM per turn, say); the companion knows nothing of it
302
283
  // beyond handing the runner over, with the turn's inputs in its environment and the
303
- // credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
284
+ // credential and secrets on its stdin (`runner/launch.ts`, `wrapped`). The README lists
285
+ // what a wrapper can rely on.
304
286
  runner: z2.object({
305
287
  command: z2.array(z2.string().min(1)).min(1)
306
288
  }).strict().optional()
@@ -312,33 +294,38 @@ function workRoot(cfg) {
312
294
  if (root.startsWith("~/")) return join4(homedir5(), root.slice(2));
313
295
  return root;
314
296
  }
315
- function retiredConfigKeys(raw) {
316
- if (!raw || typeof raw !== "object") return [];
317
- const obj = raw;
297
+ var RETIRED_ROW_KEYS = ["sandbox"];
298
+ var RETIRED_HOOK_KEYS = ["validateCached", "failureReporter"];
299
+ function isObject(value) {
300
+ return !!value && typeof value === "object" && !Array.isArray(value);
301
+ }
302
+ function splitRetired(raw) {
318
303
  const keys = [];
319
- if ("runners" in obj) keys.push("runners");
320
- const agents = obj.agents;
321
- if (agents && typeof agents === "object") {
322
- for (const [key, row] of Object.entries(agents)) {
323
- if (row && typeof row === "object" && "sandbox" in row) keys.push(`agents.${key}.sandbox`);
304
+ const without = (obj, names, path) => {
305
+ const out = {};
306
+ for (const [k, v] of Object.entries(obj)) {
307
+ if (names.includes(k)) keys.push(`${path}${k}`);
308
+ else out[k] = v;
324
309
  }
310
+ return out;
311
+ };
312
+ const hook = (row, path) => isObject(row.prepareHook) ? { ...row, prepareHook: without(row.prepareHook, RETIRED_HOOK_KEYS, `${path}prepareHook.`) } : row;
313
+ const kept = hook(without(raw, ["runners"], ""), "");
314
+ if (isObject(kept.agents)) {
315
+ const agents = {};
316
+ for (const [key, row] of Object.entries(kept.agents)) {
317
+ const path = `agents.${key}.`;
318
+ agents[key] = isObject(row) ? hook(without(row, RETIRED_ROW_KEYS, path), path) : row;
319
+ }
320
+ kept.agents = agents;
325
321
  }
326
- return keys;
322
+ return { keys, kept };
323
+ }
324
+ function retiredConfigKeys(raw) {
325
+ return isObject(raw) ? splitRetired(raw).keys : [];
327
326
  }
328
327
  function withoutRetiredKeys(raw) {
329
- if (retiredConfigKeys(raw).length === 0) return raw;
330
- const { runners: _runners, ...rest } = raw;
331
- if (!rest.agents || typeof rest.agents !== "object") return rest;
332
- const agents = {};
333
- for (const [key, row] of Object.entries(rest.agents)) {
334
- if (row && typeof row === "object" && "sandbox" in row) {
335
- const { sandbox: _sandbox, ...kept } = row;
336
- agents[key] = kept;
337
- } else {
338
- agents[key] = row;
339
- }
340
- }
341
- return { ...rest, agents };
328
+ return isObject(raw) ? splitRetired(raw).kept : raw;
342
329
  }
343
330
  function retiredConfigKeysOnDisk() {
344
331
  try {
@@ -1598,7 +1585,7 @@ var emptyHeartbeatStatus = () => ({
1598
1585
  });
1599
1586
 
1600
1587
  // src/supervisor.ts
1601
- import { hostname as hostname2 } from "os";
1588
+ import { hostname } from "os";
1602
1589
 
1603
1590
  // packages/agent-runtime/src/failure.ts
1604
1591
  import { z as z4 } from "zod";
@@ -1950,7 +1937,7 @@ var CabaneApi = class {
1950
1937
  // text use (turnId, seq) within each kind for nonzero seq; tool activity
1951
1938
  // uses (turnId, toolUseId). Legacy zero/missing seq bypasses row deduplication.
1952
1939
  // Server fences and the outbox's discard/eviction bounds still apply.
1953
- async durableCommit(kind, path, body, turnId, seq, signal) {
1940
+ async durableCommit(kind, path, body, turnId, seq, signal, trailPath) {
1954
1941
  try {
1955
1942
  await this.request("POST", path, body, {
1956
1943
  retry: true,
@@ -1961,7 +1948,16 @@ var CabaneApi = class {
1961
1948
  if (!outbox) throw err;
1962
1949
  if (signal?.aborted || isAbortError(err)) throw err;
1963
1950
  if (!isRetryable(err)) throw err;
1964
- outbox.persist({ enqueuedAt: Date.now(), turnId, seq, method: "POST", path, body, kind });
1951
+ outbox.persist({
1952
+ enqueuedAt: Date.now(),
1953
+ turnId,
1954
+ seq,
1955
+ method: "POST",
1956
+ path,
1957
+ body,
1958
+ kind,
1959
+ ...trailPath ? { trailPath } : {}
1960
+ });
1965
1961
  this.opts.log?.debug(
1966
1962
  { kind, turnId, seq, err: err instanceof Error ? err.message : String(err) },
1967
1963
  "companion: commit queued to outbox after transient failure (will drain when the API returns)"
@@ -1986,6 +1982,29 @@ var CabaneApi = class {
1986
1982
  progressed = true;
1987
1983
  } catch (err) {
1988
1984
  if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
1985
+ if (entry.kind === "message" && entry.trailPath && isAlreadyPublished(err)) {
1986
+ try {
1987
+ await this.request("POST", entry.trailPath, trailOf(entry.body), {
1988
+ retry: true
1989
+ });
1990
+ } catch (trailErr) {
1991
+ if (!(trailErr instanceof ApiError && trailErr.status >= 400 && trailErr.status < 500)) {
1992
+ break;
1993
+ }
1994
+ this.opts.log?.warn(
1995
+ {
1996
+ kind: entry.kind,
1997
+ turnId: entry.turnId,
1998
+ seq: entry.seq,
1999
+ ...apiErrorLogFields(trailErr)
2000
+ },
2001
+ `Cabane rejected a postscript; it could not be filed. ${mayNeedUpdate()}`
2002
+ );
2003
+ }
2004
+ outbox.remove(entry.turnId, entry.seq);
2005
+ progressed = true;
2006
+ continue;
2007
+ }
1989
2008
  if (entry.kind === "active-run" && isTurnEndedRefusal(err)) {
1990
2009
  this.opts.log?.debug(
1991
2010
  { kind: entry.kind, turnId: entry.turnId, ...apiErrorLogFields(err) },
@@ -2061,6 +2080,19 @@ var CabaneApi = class {
2061
2080
  (e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
2062
2081
  );
2063
2082
  }
2083
+ // The `seq` of this turn's SELECTED ANSWER still waiting in the outbox — the
2084
+ // `declared` commit the server's `answerArmed` cannot see — or null when none
2085
+ // is queued. A resumed run that re-armed over it would let a newer text
2086
+ // consume the arm first; with the selection queued, the resumed run starts
2087
+ // declared, its texts are trail, and the drain delivers the selection.
2088
+ outboxDeclaredSeqForTurn(turnId) {
2089
+ const outbox = this.opts.outbox;
2090
+ if (!outbox) return null;
2091
+ const queued = outbox.list().find(
2092
+ (e) => e.turnId === turnId && e.kind === "message" && e.body?.declared === true
2093
+ );
2094
+ return queued ? queued.seq : null;
2095
+ }
2064
2096
  // ADOPT a turn context's issuance as the execution source of its work, once,
2065
2097
  // before preparation emits or queues anything, so every row can name a source
2066
2098
  // the server durably recorded and will never reap. Adoption is idempotent, so
@@ -2234,15 +2266,36 @@ var CabaneApi = class {
2234
2266
  // seq also orders speech against the turn's tool/thinking/text trail.
2235
2267
  // Transport/5xx failures get bounded retries and optional outbox delivery;
2236
2268
  // terminal 4xx and cancellation stop delivery rather than proving no commit.
2237
- postTurnMessage(workspaceId, conversationId, body, signal) {
2238
- return this.durableCommit(
2239
- "message",
2240
- `/api/workspaces/${workspaceId}/conversations/${conversationId}/messages`,
2241
- body,
2242
- body.turnId,
2243
- body.seq ?? 0,
2244
- signal
2245
- );
2269
+ //
2270
+ // A turn's answer can be out before a later speech commit arrives — the
2271
+ // declaration published it, or another host's selection drained first — and
2272
+ // the server refuses the commit `409 already_published`. The text is a
2273
+ // postscript then: with `trail` (the committing agent, which names the text
2274
+ // route), it is filed as trail at the same seq, live here and from the outbox
2275
+ // on a drain. Without `trail` (a runtime notice) the refusal is terminal.
2276
+ async postTurnMessage(workspaceId, conversationId, body, signal, trail) {
2277
+ const trailPath = trail ? `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${trail.agentId}/text` : void 0;
2278
+ try {
2279
+ await this.durableCommit(
2280
+ "message",
2281
+ `/api/workspaces/${workspaceId}/conversations/${conversationId}/messages`,
2282
+ body,
2283
+ body.turnId,
2284
+ body.seq ?? 0,
2285
+ signal,
2286
+ trailPath
2287
+ );
2288
+ } catch (err) {
2289
+ if (!trailPath || !isAlreadyPublished(err)) throw err;
2290
+ await this.durableCommit(
2291
+ "text",
2292
+ trailPath,
2293
+ trailOf(body),
2294
+ body.turnId,
2295
+ body.seq ?? 0,
2296
+ signal
2297
+ );
2298
+ }
2246
2299
  }
2247
2300
  // Report a tool transition (start/done/error). The server checks span
2248
2301
  // ownership, upserts by (turnId, toolUseId), and publishes agent_activity SSE.
@@ -2288,6 +2341,18 @@ var CabaneApi = class {
2288
2341
  );
2289
2342
  }
2290
2343
  };
2344
+ function isAlreadyPublished(err) {
2345
+ return err instanceof ApiError && err.status === 409 && apiErrorCode(err) === "already_published";
2346
+ }
2347
+ function trailOf(body) {
2348
+ const b = body;
2349
+ return {
2350
+ turnId: b.turnId,
2351
+ text: b.body,
2352
+ ...b.seq !== void 0 ? { seq: b.seq } : {},
2353
+ ...b.executionSourceId !== void 0 ? { executionSourceId: b.executionSourceId } : {}
2354
+ };
2355
+ }
2291
2356
  function isRetryable(err) {
2292
2357
  if (err instanceof ApiError) return err.status >= 500;
2293
2358
  if (isAbortError(err)) return false;
@@ -2778,6 +2843,8 @@ var turnOutcomes = ["success", "failure", "cancelled", "skipped"];
2778
2843
  var turnSessionModes = ["fresh", "resumed", "degraded"];
2779
2844
  var turnRuntimeResultKinds = ["success", "error", "no_terminal"];
2780
2845
  var turnFinalSources = [
2846
+ // The agent declared its answer with the `answer` turn act (CT1973).
2847
+ "declared",
2781
2848
  "runtime_text",
2782
2849
  "progress_promotion",
2783
2850
  "host_fallback",
@@ -3063,11 +3130,6 @@ function truncate(s, max) {
3063
3130
  return `${s.slice(0, max)}\u2026[+${s.length - max} chars]`;
3064
3131
  }
3065
3132
 
3066
- // packages/agent-runtime/src/claude-code/index.ts
3067
- import {
3068
- query as defaultQuery
3069
- } from "@anthropic-ai/claude-agent-sdk";
3070
-
3071
3133
  // packages/agent-runtime/src/turn-control/descriptions.ts
3072
3134
  var MAX_ASK_QUESTION_BYTES = 400;
3073
3135
  var MAX_ASK_HEADLINE_BYTES = 120;
@@ -3078,7 +3140,9 @@ var MAX_ASK_OPTIONS = 4;
3078
3140
  var MAX_WAKE_NOTE_BYTES = 2e3;
3079
3141
  var MAX_WITHDRAW_REASON_BYTES = 400;
3080
3142
  var MAX_SKIP_REASON_BYTES = 500;
3143
+ var TURN_CONTROL_MCP_SERVER = "cabane_companion";
3081
3144
  var TURN_CONTROL_TOOLS = [
3145
+ "answer",
3082
3146
  "ask",
3083
3147
  "cancel_wake",
3084
3148
  "skip_turn",
@@ -3086,6 +3150,7 @@ var TURN_CONTROL_TOOLS = [
3086
3150
  "withdraw_ask"
3087
3151
  ];
3088
3152
  var TURN_CONTROL_TOOL_DESCRIPTIONS = {
3153
+ answer: `Call this just before you write your answer: the text you write next is what the conversation keeps as your message. Call it once.`,
3089
3154
  ask: `Ask a person a structured question you need answered to continue, then end your turn; don't wait for the reply. \`targetUserId\` is a person on your roster, by their \`@handle\`; to bring in an agent, name them with \`@handle\` in your reply.
3090
3155
  - Form: one question (\`headline\`, optional \`question\` body, \`options\`) or \`questions\`, 1\u2013${MAX_ASK_ITEMS} \`{ headline, body?, options? }\`; never both. Decisions that arise together go in ONE call, in the order you want them answered. A second \`ask\` is a separate item, never an edit; raise one only for a genuinely new question.
3091
3156
  - \`headline\`: one sentence ending in \`?\`. Body (\`question\` or each item's \`body\`): a sentence or two of framing, not a report; inline Markdown only, no lists or headings. \`options\`: ${MIN_ASK_OPTIONS}\u2013${MAX_ASK_OPTIONS} one-click answers (always for a yes/no go-ahead), each a distinct course of action you can carry out without asking back.
@@ -3100,6 +3165,7 @@ var TURN_CONTROL_TOOL_DESCRIPTIONS = {
3100
3165
  withdraw_ask: `Withdraw an open ask \u2014 yours or anyone's in this conversation \u2014 because the question is moot, was wrong, or has been overtaken. \`reason\` is required and is shown to the person and, if it wasn't your ask, to the agent who asked it. If there is a current question to put in its place, don't call this \u2014 call \`ask\` with \`replaces\` so the two land as one and the person never sees neither or both. Never withdraw to hurry an answer; to act on a deadline, arm a \`wake_me\` and decide then.`
3101
3166
  };
3102
3167
  var TURN_CONTROL_FIELD_DESCRIPTIONS = {
3168
+ answer: {},
3103
3169
  ask: {
3104
3170
  targetUserId: "The person to ask: their `@handle` from your turn context's roster (preferred), or their user id.",
3105
3171
  question: `SINGLE-question form: a short body \u2014 one or two sentences of framing the headline can't hold. NOT a report (up to ${MAX_ASK_QUESTION_BYTES} characters, inline markdown only). Provide EITHER this or \`questions\`, not both. Put the crisp one-sentence question in \`headline\`.`,
@@ -3128,8 +3194,14 @@ var TURN_CONTROL_FIELD_DESCRIPTIONS = {
3128
3194
  }
3129
3195
  };
3130
3196
 
3197
+ // packages/agent-runtime/src/claude-code/index.ts
3198
+ import {
3199
+ query as defaultQuery
3200
+ } from "@anthropic-ai/claude-agent-sdk";
3201
+
3131
3202
  // packages/agent-runtime/src/turn-control/prompt-guidance.ts
3132
3203
  var MEANINGS = {
3204
+ answer: "declare the text you write next as your answer",
3133
3205
  ask: "put a structured question to a person",
3134
3206
  cancel_wake: "cancel your own armed wake in this conversation",
3135
3207
  skip_turn: "end without posting a reply",
@@ -4527,6 +4599,15 @@ function deepestError(node) {
4527
4599
  }
4528
4600
 
4529
4601
  // packages/agent-runtime/src/opencode/loop.ts
4602
+ function opencodeMcpServerOf(tool2, servers) {
4603
+ let best = null;
4604
+ for (const server of servers ?? []) {
4605
+ if (tool2.length > server.length + 1 && tool2.startsWith(`${server}_`)) {
4606
+ if (best === null || server.length > best.length) best = server;
4607
+ }
4608
+ }
4609
+ return best;
4610
+ }
4530
4611
  async function* decodeOpencodeStream(events, ctx) {
4531
4612
  let held = null;
4532
4613
  const pending = /* @__PURE__ */ new Map();
@@ -4553,12 +4634,14 @@ async function* decodeOpencodeStream(events, ctx) {
4553
4634
  if (sealed) yield sealed;
4554
4635
  const key = part.callID ?? part.id;
4555
4636
  const input = part.state.input;
4637
+ const server = opencodeMcpServerOf(part.tool, ctx.mcpServers);
4638
+ const origin = server ? { mcpServer: server } : {};
4556
4639
  if (!startedTools.has(key)) {
4557
4640
  const name = prettyToolName(part.tool);
4558
4641
  const summary = summarizeToolInput(input);
4559
4642
  pending.set(key, { name, summary, input });
4560
4643
  startedTools.add(key);
4561
- yield { type: "tool", id: key, name, phase: "start", summary, input };
4644
+ yield { type: "tool", id: key, name, phase: "start", summary, input, ...origin };
4562
4645
  }
4563
4646
  const entry = pending.get(key);
4564
4647
  if (entry && hasToolInput(input)) {
@@ -4578,7 +4661,8 @@ async function* decodeOpencodeStream(events, ctx) {
4578
4661
  phase,
4579
4662
  summary: started?.summary ?? "",
4580
4663
  input: started?.input ?? input,
4581
- result
4664
+ result,
4665
+ ...origin
4582
4666
  };
4583
4667
  }
4584
4668
  } else {
@@ -4662,7 +4746,6 @@ function parseOpencodeModel(model) {
4662
4746
 
4663
4747
  // packages/agent-runtime/src/opencode/run-spec.ts
4664
4748
  var CABANE_MCP_SERVER2 = "cabane";
4665
- var TURN_CONTROL_MCP_SERVER = "cabane_companion";
4666
4749
  var ACTIVE_CONVERSATION_HEADER2 = "x-cabane-active-conversation";
4667
4750
  function buildRunSpec(req, resumeSessionId) {
4668
4751
  const { config } = req;
@@ -5056,7 +5139,10 @@ function createOpencodeAdapter(deps = {}) {
5056
5139
  }
5057
5140
  yield* decodeOpencodeStream(result.events, {
5058
5141
  signal,
5059
- sessionId: result.sessionId
5142
+ sessionId: result.sessionId,
5143
+ // The keys the turn's servers were installed under: what the decoder
5144
+ // splits a raw `<server>_<tool>` id on.
5145
+ mcpServers: Object.keys(spec.mcp)
5060
5146
  });
5061
5147
  }
5062
5148
  };
@@ -5192,7 +5278,15 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
5192
5278
  expected: [
5193
5279
  sessionEvent2(NEW_SESSION_ID),
5194
5280
  // start: fires off the first (empty) snapshot — empty input + summary, by design.
5195
- { type: "tool", id: "call_1", name: "cabane_list", phase: "start", summary: "", input: {} },
5281
+ {
5282
+ type: "tool",
5283
+ id: "call_1",
5284
+ name: "cabane_list",
5285
+ phase: "start",
5286
+ summary: "",
5287
+ input: {},
5288
+ mcpServer: "cabane"
5289
+ },
5196
5290
  // done: carries the full args captured from the later snapshot, and the
5197
5291
  // summary recomputed from them — NOT the first-seen `{}`.
5198
5292
  {
@@ -5202,8 +5296,60 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
5202
5296
  phase: "done",
5203
5297
  summary: "/product/tasks",
5204
5298
  input: { path: "/product/tasks" },
5205
- result: "CT301.md"
5299
+ result: "CT301.md",
5300
+ mcpServer: "cabane"
5301
+ },
5302
+ { type: "result", ok: true }
5303
+ ]
5304
+ },
5305
+ {
5306
+ // The turn-control server's `answer` (CT1973). opencode names an MCP tool `<server>_<tool>`,
5307
+ // so the installed keys split the raw id: `cabane_companion_answer` is the turn-control
5308
+ // server's, never the workspace server's `companion_answer`. The pump binds on that.
5309
+ name: "turn-control answer is read as the turn-control server\u2019s, not cabane\u2019s",
5310
+ request: makeRequest2({
5311
+ cabane: {
5312
+ mcpUrl: "https://cabane.test/api/mcp",
5313
+ turnControlUrl: "https://cabane.test/api/turn-control",
5314
+ bearer: "tok",
5315
+ activeConversationId: "conv"
5316
+ }
5317
+ }),
5318
+ nativeStream: [
5319
+ textPart("p1", "On it."),
5320
+ toolPart("t1", "cabane_companion_answer", { status: "running", input: {} }, "call_1"),
5321
+ toolPart(
5322
+ "t1",
5323
+ "cabane_companion_answer",
5324
+ { status: "completed", input: {}, output: '{"ok":true,"kind":"answer"}' },
5325
+ "call_1"
5326
+ ),
5327
+ textPart("p2", "The answer."),
5328
+ idle()
5329
+ ],
5330
+ expected: [
5331
+ sessionEvent2(NEW_SESSION_ID),
5332
+ { type: "text", body: "On it.", terminal: false },
5333
+ {
5334
+ type: "tool",
5335
+ id: "call_1",
5336
+ name: "cabane_companion_answer",
5337
+ phase: "start",
5338
+ summary: "",
5339
+ input: {},
5340
+ mcpServer: "cabane_companion"
5341
+ },
5342
+ {
5343
+ type: "tool",
5344
+ id: "call_1",
5345
+ name: "cabane_companion_answer",
5346
+ phase: "done",
5347
+ summary: "",
5348
+ input: {},
5349
+ result: '{"ok":true,"kind":"answer"}',
5350
+ mcpServer: "cabane_companion"
5206
5351
  },
5352
+ { type: "text", body: "The answer.", terminal: true },
5207
5353
  { type: "result", ok: true }
5208
5354
  ]
5209
5355
  },
@@ -5561,6 +5707,7 @@ function readToolItem(item, eventType) {
5561
5707
  name: server && server !== "cabane" ? `${server}.${tool2}` : tool2,
5562
5708
  input: item.arguments,
5563
5709
  status: toStatus(item.status),
5710
+ ...server ? { server } : {},
5564
5711
  ...item.status === "completed" ? { result } : item.status === "failed" ? { result: hasMcpReceipt(result) ? result : readMcpError(item.error) } : {}
5565
5712
  };
5566
5713
  }
@@ -5789,11 +5936,20 @@ async function* decodeCodexStream(events, ctx) {
5789
5936
  if (!tool2) continue;
5790
5937
  yield* flushInterim();
5791
5938
  const name = prettyToolName(tool2.name);
5939
+ const origin = tool2.server ? { mcpServer: tool2.server } : {};
5792
5940
  if (!startedTools.has(tool2.id)) {
5793
5941
  const summary = summarizeToolInput(tool2.input);
5794
5942
  pending.set(tool2.id, { name, summary, input: tool2.input });
5795
5943
  startedTools.add(tool2.id);
5796
- yield { type: "tool", id: tool2.id, name, phase: "start", summary, input: tool2.input };
5944
+ yield {
5945
+ type: "tool",
5946
+ id: tool2.id,
5947
+ name,
5948
+ phase: "start",
5949
+ summary,
5950
+ input: tool2.input,
5951
+ ...origin
5952
+ };
5797
5953
  }
5798
5954
  const entry = pending.get(tool2.id);
5799
5955
  if (entry && hasToolInput2(tool2.input)) {
@@ -5811,7 +5967,8 @@ async function* decodeCodexStream(events, ctx) {
5811
5967
  phase: tool2.status === "completed" ? "done" : "error",
5812
5968
  summary: started?.summary ?? "",
5813
5969
  input: started?.input ?? tool2.input,
5814
- result: tool2.result
5970
+ result: tool2.result,
5971
+ ...origin
5815
5972
  };
5816
5973
  }
5817
5974
  continue;
@@ -5902,7 +6059,6 @@ function parseCodexModel(model) {
5902
6059
 
5903
6060
  // packages/agent-runtime/src/codex/run-spec.ts
5904
6061
  var CABANE_MCP_SERVER3 = "cabane";
5905
- var TURN_CONTROL_MCP_SERVER2 = "cabane_companion";
5906
6062
  var ACTIVE_CONVERSATION_HEADER3 = "x-cabane-active-conversation";
5907
6063
  var ENV_ENVELOPE_KEYS = [
5908
6064
  "CABANE_CONVERSATION_ID",
@@ -6031,7 +6187,7 @@ function buildConfig(req) {
6031
6187
  default_tools_approval_mode: "approve"
6032
6188
  };
6033
6189
  if (req.cabane.turnControlUrl) {
6034
- mcp_servers[TURN_CONTROL_MCP_SERVER2] = {
6190
+ mcp_servers[TURN_CONTROL_MCP_SERVER] = {
6035
6191
  url: req.cabane.turnControlUrl,
6036
6192
  http_headers: {
6037
6193
  Authorization: `Bearer ${req.cabane.bearer}`,
@@ -6209,7 +6365,7 @@ function createCodexAdapter(deps = {}) {
6209
6365
  if (signal.aborted) return;
6210
6366
  if (compactions === null) {
6211
6367
  deps.onWarn?.(
6212
- "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions (ENG210)",
6368
+ "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions",
6213
6369
  { threadId: decision.resume }
6214
6370
  );
6215
6371
  } else if (decision.compactions === compactions) {
@@ -6219,7 +6375,7 @@ function createCodexAdapter(deps = {}) {
6219
6375
  };
6220
6376
  } else if (decision.compactions !== null && compactions > decision.compactions) {
6221
6377
  deps.onWarn?.(
6222
- "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions (ENG210)",
6378
+ "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions",
6223
6379
  {
6224
6380
  threadId: decision.resume,
6225
6381
  compactions,
@@ -6481,6 +6637,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6481
6637
  type: "tool",
6482
6638
  id: "t1",
6483
6639
  name: "read",
6640
+ mcpServer: "cabane",
6484
6641
  phase: "start",
6485
6642
  summary: "sleep.md",
6486
6643
  input: { path: "sleep.md" }
@@ -6489,6 +6646,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6489
6646
  type: "tool",
6490
6647
  id: "t1",
6491
6648
  name: "read",
6649
+ mcpServer: "cabane",
6492
6650
  phase: "done",
6493
6651
  summary: "sleep.md",
6494
6652
  input: { path: "sleep.md" },
@@ -6534,6 +6692,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6534
6692
  type: "tool",
6535
6693
  id: "query-success",
6536
6694
  name: "query",
6695
+ mcpServer: "cabane",
6537
6696
  phase: "start",
6538
6697
  summary: "",
6539
6698
  input: { op: "files.read", args: { path: "/ABOUT.md" } }
@@ -6542,6 +6701,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6542
6701
  type: "tool",
6543
6702
  id: "query-success",
6544
6703
  name: "query",
6704
+ mcpServer: "cabane",
6545
6705
  phase: "done",
6546
6706
  summary: "",
6547
6707
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
@@ -6588,6 +6748,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6588
6748
  type: "tool",
6589
6749
  id: "query-failed",
6590
6750
  name: "query",
6751
+ mcpServer: "cabane",
6591
6752
  phase: "start",
6592
6753
  summary: "",
6593
6754
  input: { op: "files.read", args: { path: "/ABOUT.md" } }
@@ -6596,6 +6757,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6596
6757
  type: "tool",
6597
6758
  id: "query-failed",
6598
6759
  name: "query",
6760
+ mcpServer: "cabane",
6599
6761
  phase: "error",
6600
6762
  summary: "",
6601
6763
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
@@ -6605,6 +6767,54 @@ var CODEX_CONFORMANCE_FIXTURES = [
6605
6767
  { type: "result", ok: true }
6606
6768
  ]
6607
6769
  },
6770
+ {
6771
+ // The turn-control server's `answer` (CT1973): the tool event carries the server Codex names
6772
+ // beside the tool, which is what the pump binds the declaration to. A non-cabane server keeps
6773
+ // its prefix on the card as before.
6774
+ name: "turn-control answer carries its server",
6775
+ request: makeRequest3(),
6776
+ nativeStream: [
6777
+ threadStarted(NEW_THREAD_ID),
6778
+ agentMessage("m1", "On it."),
6779
+ toolFrame("item.completed", {
6780
+ id: "answer-1",
6781
+ type: "mcp_tool_call",
6782
+ server: "cabane_companion",
6783
+ tool: "answer",
6784
+ arguments: {},
6785
+ status: "completed",
6786
+ result: { content: [{ type: "text", text: '{"ok":true,"kind":"answer"}' }] }
6787
+ }),
6788
+ agentMessage("m2", "The answer."),
6789
+ turnCompleted()
6790
+ ],
6791
+ expected: [
6792
+ startedSession(NEW_THREAD_ID),
6793
+ { type: "text", body: "On it.", terminal: false },
6794
+ {
6795
+ type: "tool",
6796
+ id: "answer-1",
6797
+ name: "cabane_companion.answer",
6798
+ mcpServer: "cabane_companion",
6799
+ phase: "start",
6800
+ summary: "",
6801
+ input: {}
6802
+ },
6803
+ {
6804
+ type: "tool",
6805
+ id: "answer-1",
6806
+ name: "cabane_companion.answer",
6807
+ mcpServer: "cabane_companion",
6808
+ phase: "done",
6809
+ summary: "",
6810
+ input: {},
6811
+ result: [{ type: "text", text: '{"ok":true,"kind":"answer"}' }]
6812
+ },
6813
+ { type: "text", body: "The answer.", terminal: true },
6814
+ closingSession(NEW_THREAD_ID),
6815
+ { type: "result", ok: true }
6816
+ ]
6817
+ },
6608
6818
  {
6609
6819
  // A transport/tool failure with no MCP result payload still uses the real adapter error message,
6610
6820
  // never `undefined`.
@@ -6630,6 +6840,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6630
6840
  type: "tool",
6631
6841
  id: "mcp-failed",
6632
6842
  name: "other.lookup",
6843
+ mcpServer: "other",
6633
6844
  phase: "start",
6634
6845
  summary: "receipt",
6635
6846
  input: { q: "receipt" }
@@ -6638,6 +6849,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6638
6849
  type: "tool",
6639
6850
  id: "mcp-failed",
6640
6851
  name: "other.lookup",
6852
+ mcpServer: "other",
6641
6853
  phase: "error",
6642
6854
  summary: "receipt",
6643
6855
  input: { q: "receipt" },
@@ -6674,6 +6886,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6674
6886
  type: "tool",
6675
6887
  id: "mcp-image",
6676
6888
  name: "other.render",
6889
+ mcpServer: "other",
6677
6890
  phase: "start",
6678
6891
  summary: "",
6679
6892
  input: {}
@@ -6682,6 +6895,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6682
6895
  type: "tool",
6683
6896
  id: "mcp-image",
6684
6897
  name: "other.render",
6898
+ mcpServer: "other",
6685
6899
  phase: "done",
6686
6900
  summary: "",
6687
6901
  input: {},
@@ -6729,11 +6943,20 @@ var CODEX_CONFORMANCE_FIXTURES = [
6729
6943
  ],
6730
6944
  expected: [
6731
6945
  startedSession(NEW_THREAD_ID),
6732
- { type: "tool", id: "t1", name: "list", phase: "start", summary: "", input: {} },
6733
6946
  {
6734
6947
  type: "tool",
6735
6948
  id: "t1",
6736
6949
  name: "list",
6950
+ mcpServer: "cabane",
6951
+ phase: "start",
6952
+ summary: "",
6953
+ input: {}
6954
+ },
6955
+ {
6956
+ type: "tool",
6957
+ id: "t1",
6958
+ name: "list",
6959
+ mcpServer: "cabane",
6737
6960
  phase: "done",
6738
6961
  summary: "/product/tasks",
6739
6962
  input: { path: "/product/tasks" },
@@ -7303,8 +7526,6 @@ var ConnectorHealthStore = class {
7303
7526
 
7304
7527
  // src/dispatcher.ts
7305
7528
  import { randomUUID as randomUUID4 } from "crypto";
7306
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
7307
- import { join as join21 } from "path";
7308
7529
 
7309
7530
  // src/runner/launch.ts
7310
7531
  import { createInterface } from "readline";
@@ -7554,7 +7775,7 @@ function selfRunnerCommand() {
7554
7775
  function wrappedRunnerCommand(prefix, own) {
7555
7776
  return [...prefix ?? [], ...own];
7556
7777
  }
7557
- var BOXED_ENV_DROPPED = [
7778
+ var WRAPPED_ENV_DROPPED = [
7558
7779
  "ANTHROPIC_API_KEY",
7559
7780
  "CLAUDE_CODE_OAUTH_TOKEN",
7560
7781
  "CODEX_API_KEY",
@@ -7563,11 +7784,11 @@ var BOXED_ENV_DROPPED = [
7563
7784
  "DBUS_SESSION_BUS_ADDRESS",
7564
7785
  "XDG_RUNTIME_DIR"
7565
7786
  ];
7566
- function runnerExecEnv(base, boxed) {
7787
+ function runnerExecEnv(base, wrapped) {
7567
7788
  const env = {};
7568
7789
  for (const [k, v] of Object.entries(base)) {
7569
7790
  if (v === void 0) continue;
7570
- if (boxed && BOXED_ENV_DROPPED.includes(k)) continue;
7791
+ if (wrapped && WRAPPED_ENV_DROPPED.includes(k)) continue;
7571
7792
  env[k] = v;
7572
7793
  }
7573
7794
  return env;
@@ -7789,13 +8010,13 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7789
8010
  return { ok: false, durationMs: durationMs2, reason: reason2 };
7790
8011
  }
7791
8012
  try {
7792
- const boxed = launch.boxed === true;
7793
- const env = runnerExecEnv(launch.env ?? process.env, boxed);
8013
+ const wrapped = launch.wrapped === true;
8014
+ const env = runnerExecEnv(launch.env ?? process.env, wrapped);
7794
8015
  const grants = minted.deviceGrants.length > 0 ? launch.grants?.(minted.deviceGrants) ?? {} : {};
7795
8016
  const input = {
7796
8017
  baseUrl: opts.baseUrl,
7797
8018
  credential: minted.credential,
7798
- ...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
8019
+ ...wrapped ? { secrets: launch.secrets?.() ?? {} } : {},
7799
8020
  ...Object.keys(grants).length > 0 ? { grants } : {},
7800
8021
  dispatch: payload,
7801
8022
  turnId,
@@ -7869,7 +8090,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7869
8090
  locks?.release(lockOwner(event.id));
7870
8091
  return;
7871
8092
  case "process_group":
7872
- if (!boxed) groups.push(event.pgid);
8093
+ if (!wrapped) groups.push(event.pgid);
7873
8094
  return;
7874
8095
  case "remote_session":
7875
8096
  remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
@@ -7944,7 +8165,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7944
8165
 
7945
8166
  // src/turn-execution.ts
7946
8167
  import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
7947
- import { mkdirSync as mkdirSync9 } from "fs";
8168
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
7948
8169
 
7949
8170
  // src/grants.ts
7950
8171
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
@@ -7998,7 +8219,7 @@ function loadGrantsFile(report) {
7998
8219
  const result = grantsFileSchema.safeParse(parsed);
7999
8220
  if (!result.success) {
8000
8221
  report.unreadable(
8001
- `${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_PROD_ACCESS": { "RENDER_API_KEY": "rnd_\u2026" } }).`
8222
+ `${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_STAGING_DB": { "DATABASE_URL": "postgres://\u2026" } }).`
8002
8223
  );
8003
8224
  return {};
8004
8225
  }
@@ -8012,7 +8233,7 @@ import { join as join14 } from "path";
8012
8233
  // src/turn-control-tools.ts
8013
8234
  import { randomUUID as randomUUID2 } from "crypto";
8014
8235
  import { z as z16 } from "zod";
8015
- var COMPANION_LOCAL_MCP_SERVER = "cabane_companion";
8236
+ var COMPANION_LOCAL_MCP_SERVER = TURN_CONTROL_MCP_SERVER;
8016
8237
  var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
8017
8238
  var ASK_TOOL = "ask";
8018
8239
  var ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${ASK_TOOL}`;
@@ -8041,10 +8262,6 @@ import { homedir as homedir7 } from "os";
8041
8262
  import { join as join17 } from "path";
8042
8263
  import { createInterface as createInterface2 } from "readline";
8043
8264
 
8044
- // src/prepare-failure.ts
8045
- import { spawn as spawn3 } from "child_process";
8046
- import { hostname } from "os";
8047
-
8048
8265
  // src/prepared.ts
8049
8266
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
8050
8267
  import { join as join18 } from "path";
@@ -8120,7 +8337,7 @@ function requiredSecretNames(mcpServers) {
8120
8337
  }
8121
8338
 
8122
8339
  // src/harness-usable.ts
8123
- import { spawn as spawn4 } from "child_process";
8340
+ import { spawn as spawn3 } from "child_process";
8124
8341
 
8125
8342
  // src/harness-versions.ts
8126
8343
  var EMPTY = { claudeCode: null, opencode: null, codex: null };
@@ -8223,7 +8440,7 @@ function runBounded(path, args, env) {
8223
8440
  };
8224
8441
  let child;
8225
8442
  try {
8226
- child = spawn4(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
8443
+ child = spawn3(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
8227
8444
  } catch {
8228
8445
  resolve({ code: null, output: "", error: "spawn" });
8229
8446
  return;
@@ -8390,7 +8607,7 @@ function hostReport() {
8390
8607
  }
8391
8608
 
8392
8609
  // src/providers/local-process.ts
8393
- import { spawn as spawn5 } from "child_process";
8610
+ import { spawn as spawn4 } from "child_process";
8394
8611
  import { constants as constants2 } from "os";
8395
8612
  var DEFAULT_KILL_GRACE_MS = 3e4;
8396
8613
  var DEFAULT_REAP_GRACE_MS = 3e3;
@@ -8401,7 +8618,7 @@ function exitCode(code, signal) {
8401
8618
  }
8402
8619
  function createLocalProcessProvider(opts = {}) {
8403
8620
  const killGraceMs = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
8404
- const spawnFn = opts.spawnFn ?? spawn5;
8621
+ const spawnFn = opts.spawnFn ?? spawn4;
8405
8622
  const reapGraceMs = opts.reapGraceMs ?? DEFAULT_REAP_GRACE_MS;
8406
8623
  const killFn = opts.killFn ?? ((pid, signal) => process.kill(pid, signal));
8407
8624
  const groupAlive = (pgid) => {
@@ -8605,7 +8822,7 @@ function checkReason(check) {
8605
8822
 
8606
8823
  // src/harness-copy.ts
8607
8824
  import { homedir as homedir8 } from "os";
8608
- import { join as join22 } from "path";
8825
+ import { join as join21 } from "path";
8609
8826
  var HARNESS_LABELS = {
8610
8827
  "claude-code": "Claude Code",
8611
8828
  codex: "Codex",
@@ -8619,7 +8836,7 @@ function failureText(runtime, reason) {
8619
8836
  case "spawn":
8620
8837
  return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
8621
8838
  case "config":
8622
- return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join22(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8839
+ return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8623
8840
  case "unreadable":
8624
8841
  return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
8625
8842
  }
@@ -8878,13 +9095,13 @@ var DispatchClaims = class {
8878
9095
  import {
8879
9096
  existsSync as existsSync14,
8880
9097
  mkdirSync as mkdirSync10,
8881
- readdirSync as readdirSync3,
9098
+ readdirSync as readdirSync2,
8882
9099
  readFileSync as readFileSync11,
8883
9100
  renameSync as renameSync5,
8884
9101
  rmSync as rmSync7,
8885
9102
  writeFileSync as writeFileSync6
8886
9103
  } from "fs";
8887
- import { join as join23 } from "path";
9104
+ import { join as join22 } from "path";
8888
9105
  var MAX_ENTRIES = 2e3;
8889
9106
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8890
9107
  var Outbox = class {
@@ -8897,10 +9114,10 @@ var Outbox = class {
8897
9114
  // Resolved lazily (per call) so tests that swap HOME between
8898
9115
  // cases route writes at the right tmpdir.
8899
9116
  dir() {
8900
- return join23(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
9117
+ return join22(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8901
9118
  }
8902
9119
  fileFor(turnId, seq) {
8903
- return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
9120
+ return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8904
9121
  }
8905
9122
  // Persist a commit for later draining. Atomic (temp file + rename) so a
8906
9123
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -8935,14 +9152,14 @@ var Outbox = class {
8935
9152
  if (!existsSync14(dir2)) return [];
8936
9153
  let names;
8937
9154
  try {
8938
- names = readdirSync3(dir2);
9155
+ names = readdirSync2(dir2);
8939
9156
  } catch {
8940
9157
  return [];
8941
9158
  }
8942
9159
  const entries = [];
8943
9160
  for (const name of names) {
8944
9161
  if (!name.endsWith(".json")) continue;
8945
- const full = join23(dir2, name);
9162
+ const full = join22(dir2, name);
8946
9163
  try {
8947
9164
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
8948
9165
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -8979,7 +9196,7 @@ var Outbox = class {
8979
9196
  maxSeqForTurn(turnId) {
8980
9197
  const dir2 = this.dir();
8981
9198
  if (!existsSync14(dir2)) return 0;
8982
- const names = readdirSync3(dir2);
9199
+ const names = readdirSync2(dir2);
8983
9200
  const prefix = `${encodeURIComponent(turnId)}__`;
8984
9201
  let max = 0;
8985
9202
  for (const name of names) {
@@ -9000,7 +9217,7 @@ var Outbox = class {
9000
9217
  const dir2 = this.dir();
9001
9218
  if (!existsSync14(dir2)) return 0;
9002
9219
  try {
9003
- return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
9220
+ return readdirSync2(dir2).filter((n) => n.endsWith(".json")).length;
9004
9221
  } catch {
9005
9222
  return 0;
9006
9223
  }
@@ -9164,9 +9381,9 @@ var SseSubscriber = class {
9164
9381
  };
9165
9382
 
9166
9383
  // src/updater.ts
9167
- import { spawn as spawn6 } from "child_process";
9384
+ import { spawn as spawn5 } from "child_process";
9168
9385
  import { copyFileSync as copyFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync14, renameSync as renameSync9, rmSync as rmSync10 } from "fs";
9169
- import { dirname as dirname11, join as join27 } from "path";
9386
+ import { dirname as dirname11, join as join26 } from "path";
9170
9387
 
9171
9388
  // src/boot-rule.ts
9172
9389
  import {
@@ -9178,7 +9395,7 @@ import {
9178
9395
  symlinkSync,
9179
9396
  writeFileSync as writeFileSync7
9180
9397
  } from "fs";
9181
- import { dirname as dirname8, join as join24 } from "path";
9398
+ import { dirname as dirname8, join as join23 } from "path";
9182
9399
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
9183
9400
  var MAX_STAGE_ATTEMPTS = 3;
9184
9401
  function superseded(pending, ...running) {
@@ -9205,32 +9422,32 @@ import {
9205
9422
  lstatSync,
9206
9423
  mkdirSync as mkdirSync12,
9207
9424
  readFileSync as readFileSync13,
9208
- readdirSync as readdirSync4,
9425
+ readdirSync as readdirSync3,
9209
9426
  realpathSync as realpathSync5,
9210
9427
  renameSync as renameSync7,
9211
9428
  rmSync as rmSync9,
9212
9429
  symlinkSync as symlinkSync2
9213
9430
  } from "fs";
9214
- import { dirname as dirname9, join as join25 } from "path";
9431
+ import { dirname as dirname9, join as join24 } from "path";
9215
9432
  import { fileURLToPath as fileURLToPath5 } from "url";
9216
9433
  function versionsDir() {
9217
- return join25(cabaneDir(), "versions");
9434
+ return join24(cabaneDir(), "versions");
9218
9435
  }
9219
9436
  function currentLink() {
9220
- return join25(cabaneDir(), "current");
9437
+ return join24(cabaneDir(), "current");
9221
9438
  }
9222
9439
  function bootGuardPath() {
9223
- return join25(cabaneDir(), "boot-guard.mjs");
9440
+ return join24(cabaneDir(), "boot-guard.mjs");
9224
9441
  }
9225
9442
  function versionPackageRoot(version) {
9226
- return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
9443
+ return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
9227
9444
  }
9228
9445
  function ownPackageRoot(moduleUrl = import.meta.url) {
9229
9446
  return dirname9(dirname9(fileURLToPath5(moduleUrl)));
9230
9447
  }
9231
9448
  function packageVersion(root) {
9232
9449
  try {
9233
- const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
9450
+ const pkg2 = JSON.parse(readFileSync13(join24(root, "package.json"), "utf8"));
9234
9451
  return typeof pkg2.version === "string" ? pkg2.version : null;
9235
9452
  } catch {
9236
9453
  return null;
@@ -9239,7 +9456,7 @@ function packageVersion(root) {
9239
9456
  function currentRoot() {
9240
9457
  try {
9241
9458
  const root = realpathSync5(currentLink());
9242
- return existsSync15(join25(root, "dist", "cli.js")) ? root : null;
9459
+ return existsSync15(join24(root, "dist", "cli.js")) ? root : null;
9243
9460
  } catch {
9244
9461
  return null;
9245
9462
  }
@@ -9260,9 +9477,9 @@ function pruneVersions(keep) {
9260
9477
  const dir2 = versionsDir();
9261
9478
  if (!existsSync15(dir2)) return [];
9262
9479
  const removed = [];
9263
- for (const name of readdirSync4(dir2)) {
9480
+ for (const name of readdirSync3(dir2)) {
9264
9481
  if (keep.includes(name)) continue;
9265
- rmSync9(join25(dir2, name), { recursive: true, force: true });
9482
+ rmSync9(join24(dir2, name), { recursive: true, force: true });
9266
9483
  removed.push(name);
9267
9484
  }
9268
9485
  return removed;
@@ -9281,7 +9498,7 @@ function compareVersions(a, b) {
9281
9498
 
9282
9499
  // src/update-state.ts
9283
9500
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
9284
- import { dirname as dirname10, join as join26 } from "path";
9501
+ import { dirname as dirname10, join as join25 } from "path";
9285
9502
  import { z as z18 } from "zod";
9286
9503
  var resultSchema = z18.object({
9287
9504
  kind: z18.enum(["updated", "rolled_back", "failed"]),
@@ -9305,7 +9522,7 @@ var stateSchema = z18.object({
9305
9522
  failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
9306
9523
  });
9307
9524
  function updateStatePath(dir2 = cabaneDir()) {
9308
- return join26(dir2, "update.json");
9525
+ return join25(dir2, "update.json");
9309
9526
  }
9310
9527
  function readUpdateState(path = updateStatePath()) {
9311
9528
  try {
@@ -9354,7 +9571,7 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
9354
9571
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
9355
9572
  }
9356
9573
  function writeBootGuard(root = ownPackageRoot()) {
9357
- const built = join27(root, "dist", "boot-guard.js");
9574
+ const built = join26(root, "dist", "boot-guard.js");
9358
9575
  if (!existsSync16(built)) return false;
9359
9576
  const target = bootGuardPath();
9360
9577
  mkdirSync14(dirname11(target), { recursive: true });
@@ -9371,7 +9588,7 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
9371
9588
  let stderr = "";
9372
9589
  let child;
9373
9590
  try {
9374
- child = spawn6(command, [...args], {
9591
+ child = spawn5(command, [...args], {
9375
9592
  stdio: ["ignore", "pipe", "pipe"],
9376
9593
  env: opts.env ?? process.env
9377
9594
  });
@@ -9394,17 +9611,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
9394
9611
  async function stageVersion(version, harnesses, deps = {}) {
9395
9612
  const run = deps.run ?? runProcess;
9396
9613
  const node = deps.execPath ?? process.execPath;
9397
- const target = join27(versionsDir(), version);
9614
+ const target = join26(versionsDir(), version);
9398
9615
  const placed = versionPackageRoot(version);
9399
9616
  if (existsSync16(placed)) {
9400
9617
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
9401
9618
  return { ok: true, root: placed };
9402
9619
  }
9403
- const npm = join27(dirname11(node), "npm");
9620
+ const npm = join26(dirname11(node), "npm");
9404
9621
  if (!existsSync16(npm))
9405
9622
  return { ok: false, reason: `there is no npm beside Node at ${dirname11(node)}` };
9406
9623
  mkdirSync14(versionsDir(), { recursive: true });
9407
- const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
9624
+ const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
9408
9625
  rmSync10(staging, { recursive: true, force: true });
9409
9626
  const installed = await run(
9410
9627
  npm,
@@ -9433,7 +9650,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9433
9650
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
9434
9651
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
9435
9652
  }
9436
- const root = join27(staging, "node_modules", "@cabane", "companion");
9653
+ const root = join26(staging, "node_modules", "@cabane", "companion");
9437
9654
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
9438
9655
  if (verdict) {
9439
9656
  rmSync10(staging, { recursive: true, force: true });
@@ -9445,7 +9662,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9445
9662
  }
9446
9663
  async function verifyInstall(root, version, harnesses, deps) {
9447
9664
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
9448
- const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
9665
+ const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
9449
9666
  timeoutMs: 6e4,
9450
9667
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
9451
9668
  });
@@ -9453,7 +9670,7 @@ async function verifyInstall(root, version, harnesses, deps) {
9453
9670
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
9454
9671
  }
9455
9672
  if (harnesses.length === 0) return null;
9456
- const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
9673
+ const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
9457
9674
  timeoutMs: 6e4
9458
9675
  });
9459
9676
  let resolutions = {};
@@ -9870,13 +10087,13 @@ var CompanionSupervisor = class {
9870
10087
  if (this.stopped) return;
9871
10088
  this.log.info(
9872
10089
  { protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
9873
- `Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
10090
+ `Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname()}`
9874
10091
  );
9875
10092
  const retired = retiredConfigKeysOnDisk();
9876
10093
  if (retired.length > 0) {
9877
10094
  this.log.warn(
9878
10095
  { keys: retired },
9879
- `config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed. The companion does no sandboxing, and every turn runs in a child process of its own.`
10096
+ `config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed.`
9880
10097
  );
9881
10098
  }
9882
10099
  if (this.config.deviceToken) {
@@ -9916,7 +10133,7 @@ var CompanionSupervisor = class {
9916
10133
  log: this.log,
9917
10134
  lastEventId: null,
9918
10135
  onOpen: () => {
9919
- this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname2()}`);
10136
+ this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname()}`);
9920
10137
  this.deviceConnected = true;
9921
10138
  for (const wr of this.workspaces.values()) {
9922
10139
  this.hub.setConnected(wr.workspaceId, true);
@@ -10462,9 +10679,9 @@ var CompanionSupervisor = class {
10462
10679
  this.runnerCommand ?? selfRunnerCommand()
10463
10680
  ),
10464
10681
  ...this.config.runner?.command ? {
10465
- boxed: true,
10682
+ wrapped: true,
10466
10683
  secrets: () => loadSecretValuesTolerant(
10467
- (m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
10684
+ (m) => this.log.warn({ err: m }, "Could not read the secrets file for a wrapped turn.")
10468
10685
  )
10469
10686
  } : {},
10470
10687
  // Read per dispatch, for the secret variables its mint named and no others.
@@ -11143,9 +11360,9 @@ async function waitForBoundedHeartbeat(heartbeat) {
11143
11360
  }
11144
11361
  function defaultReexec() {
11145
11362
  clearRuntimeState();
11146
- void import("child_process").then(({ spawn: spawn7 }) => {
11363
+ void import("child_process").then(({ spawn: spawn6 }) => {
11147
11364
  try {
11148
- const child = spawn7(process.execPath, process.argv.slice(1), {
11365
+ const child = spawn6(process.execPath, process.argv.slice(1), {
11149
11366
  stdio: "inherit",
11150
11367
  detached: false
11151
11368
  });
@@ -11216,9 +11433,9 @@ function handleUncaught(log, err, origin) {
11216
11433
 
11217
11434
  // src/crash-marker.ts
11218
11435
  import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync9 } from "fs";
11219
- import { join as join28 } from "path";
11436
+ import { join as join27 } from "path";
11220
11437
  function crashMarkerPath() {
11221
- return join28(cabaneDir(), "last-error.json");
11438
+ return join27(cabaneDir(), "last-error.json");
11222
11439
  }
11223
11440
  function recordCrash(rec) {
11224
11441
  try {
@@ -11238,7 +11455,7 @@ function clearCrash() {
11238
11455
  // src/stray-companion.ts
11239
11456
  import { spawnSync } from "child_process";
11240
11457
  import { readFileSync as readFileSync16 } from "fs";
11241
- import { join as join29 } from "path";
11458
+ import { join as join28 } from "path";
11242
11459
  var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
11243
11460
  var START = /\sstart(\s|$)/;
11244
11461
  function companionStarts(psOutput) {
@@ -11255,7 +11472,7 @@ function companionStarts(psOutput) {
11255
11472
  }
11256
11473
  function stateDirOf(env) {
11257
11474
  const home = env.CABANE_COMPANION_HOME || env.HOME;
11258
- return home ? join29(home, ".cabane") : null;
11475
+ return home ? join28(home, ".cabane") : null;
11259
11476
  }
11260
11477
  function envFromPsE(output, command) {
11261
11478
  const line = output.replace(/\n$/, "");