@cabane/companion 0.6.141 → 0.6.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +276 -165
  2. package/dist/runtime.js +230 -126
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15,14 +15,14 @@ var init_errors = __esm({
15
15
  "src/errors.ts"() {
16
16
  "use strict";
17
17
  CompanionError = class extends Error {
18
- constructor(message) {
19
- super(message);
18
+ constructor(message2) {
19
+ super(message2);
20
20
  this.name = "CompanionError";
21
21
  }
22
22
  };
23
23
  ApiError = class extends CompanionError {
24
- constructor(status2, message, body) {
25
- super(message);
24
+ constructor(status2, message2, body) {
25
+ super(message2);
26
26
  this.status = status2;
27
27
  this.body = body;
28
28
  this.name = "ApiError";
@@ -31,14 +31,14 @@ var init_errors = __esm({
31
31
  body;
32
32
  };
33
33
  ConfigError = class extends CompanionError {
34
- constructor(message) {
35
- super(message);
34
+ constructor(message2) {
35
+ super(message2);
36
36
  this.name = "ConfigError";
37
37
  }
38
38
  };
39
39
  PrepareHookError = class extends CompanionError {
40
- constructor(message) {
41
- super(message);
40
+ constructor(message2) {
41
+ super(message2);
42
42
  this.name = "PrepareHookError";
43
43
  }
44
44
  };
@@ -1063,8 +1063,8 @@ function apiErrorCode(err) {
1063
1063
  function apiErrorMessage(err) {
1064
1064
  const body = err instanceof ApiError ? err.body : void 0;
1065
1065
  if (body !== null && typeof body === "object") {
1066
- const message = body.message;
1067
- if (typeof message === "string" && message.length > 0) return message;
1066
+ const message2 = body.message;
1067
+ if (typeof message2 === "string" && message2.length > 0) return message2;
1068
1068
  }
1069
1069
  return err instanceof Error ? err.message : String(err);
1070
1070
  }
@@ -1097,9 +1097,9 @@ function describeErrorBody(body) {
1097
1097
  return text.length > ERROR_BODY_LOG_CAP ? `${text.slice(0, ERROR_BODY_LOG_CAP)}\u2026` : text;
1098
1098
  }
1099
1099
  function describeError(err) {
1100
- const message = err instanceof Error ? err.message : String(err);
1100
+ const message2 = err instanceof Error ? err.message : String(err);
1101
1101
  const cause = describeCause(err);
1102
- return cause === void 0 ? message : `${message}: ${cause}`;
1102
+ return cause === void 0 ? message2 : `${message2}: ${cause}`;
1103
1103
  }
1104
1104
  function describeCause(err) {
1105
1105
  if (!(err instanceof Error)) return void 0;
@@ -2564,8 +2564,7 @@ var init_addendum = __esm({
2564
2564
  CLAUDE_CODE_ADDENDUM = [
2565
2565
  "On this harness the Cabane tools register under MCP prefixes. The workspace tools are",
2566
2566
  "`mcp__cabane__query`, `mcp__cabane__mutate` and `mcp__cabane__describe`; the ancillary",
2567
- "tools are `mcp__cabane__list_workspaces`,",
2568
- "`mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
2567
+ "tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
2569
2568
  "`mcp__cabane__finalize_upload`, and `mcp__cabane__mint_render_token`.",
2570
2569
  turnControlPromptGuidance("mcp__cabane_companion__"),
2571
2570
  "Where this prompt names a tool by its bare verb, call",
@@ -2987,15 +2986,15 @@ async function* decodeSdkStream(iter, ctx) {
2987
2986
  }
2988
2987
  } catch (err) {
2989
2988
  if (ctx.signal.aborted) throw err;
2990
- const message = err instanceof Error ? err.message : String(err);
2991
- const failure = mcpInventory.initReceived ? classifyErrorText(message) : classifyFailure(signals, { text: message });
2989
+ const message2 = err instanceof Error ? err.message : String(err);
2990
+ const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
2992
2991
  if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
2993
2992
  ok = false;
2994
2993
  if (failure) {
2995
2994
  resultReason = encodeFailureReason(failure);
2996
2995
  } else {
2997
2996
  resultReason = "session_start_failed";
2998
- if (ctx.startEvidence) ctx.startEvidence.thrown = message.slice(0, 200);
2997
+ if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
2999
2998
  }
3000
2999
  sawResult = true;
3001
3000
  }
@@ -3890,7 +3889,7 @@ var init_addendum2 = __esm({
3890
3889
  "Every tool here goes by its plain name: the workspace tools are `query`, `mutate` and",
3891
3890
  "`describe`.",
3892
3891
  turnControlPromptGuidance(""),
3893
- "The ancillary tools are `list_workspaces`, `read_binary`, `upload`, `begin_upload`,",
3892
+ "The ancillary tools are `read_binary`, `upload`, `begin_upload`,",
3894
3893
  "`finalize_upload`, `mint_render_token`; and the host tools are plain verbs too (`bash`,",
3895
3894
  "`read`, `edit`). Mind the collision: a bare `read` or `edit` is the HOST tool, acting on the",
3896
3895
  "local machine \u2014 a workspace read is `query` with the operation name `files.read`. The",
@@ -3980,9 +3979,9 @@ function readSessionError(properties) {
3980
3979
  if (typeof err === "string") return err;
3981
3980
  const rec2 = asRecord(err);
3982
3981
  if (!rec2) return "unknown";
3983
- const { name, message } = deepestError(rec2);
3984
- if (message && name && !isGenericErrorName(name)) return `${name}: ${message}`;
3985
- if (message) return message;
3982
+ const { name, message: message2 } = deepestError(rec2);
3983
+ if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
3984
+ if (message2) return message2;
3986
3985
  if (name) return name;
3987
3986
  if (typeof rec2.name === "string") return rec2.name;
3988
3987
  return "unknown";
@@ -3994,10 +3993,10 @@ function deepestError(node) {
3994
3993
  const best = {};
3995
3994
  let current = node;
3996
3995
  for (let depth = 0; current && depth < 8; depth++) {
3997
- const message = typeof current.message === "string" ? current.message : void 0;
3996
+ const message2 = typeof current.message === "string" ? current.message : void 0;
3998
3997
  const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
3999
- if (message) {
4000
- best.message = message;
3998
+ if (message2) {
3999
+ best.message = message2;
4001
4000
  if (name) best.name = name;
4002
4001
  } else if (name && !best.name) {
4003
4002
  best.name = name;
@@ -4660,9 +4659,9 @@ var init_conformance3 = __esm({
4660
4659
  properties: { part: { type: "text", id: partId, messageID, text } }
4661
4660
  });
4662
4661
  idle = () => ({ type: "session.idle", properties: {} });
4663
- errored = (message) => ({
4662
+ errored = (message2) => ({
4664
4663
  type: "session.error",
4665
- properties: { error: { message } }
4664
+ properties: { error: { message: message2 } }
4666
4665
  });
4667
4666
  sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
4668
4667
  type: "session",
@@ -5075,8 +5074,8 @@ var init_addendum3 = __esm({
5075
5074
  // in sat two lines under the instruction to invoke the exact qualified names,
5076
5075
  // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
5077
5076
  // that sentence is about operation names, after the qualified forms are given.
5078
- "The ancillary tools are `mcp__cabane__list_workspaces`, `mcp__cabane__read_binary`,",
5079
- "`mcp__cabane__upload`, `mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
5077
+ "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
5078
+ "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
5080
5079
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
5081
5080
  "There is no Cabane `read`/`write`/`search` tool \u2014 those are",
5082
5081
  "operation NAMES you pass to `query` / `mutate`. One more harness fact: you can interleave",
@@ -5106,8 +5105,8 @@ function readItemText(item) {
5106
5105
  function readItemMessage(item) {
5107
5106
  return str(item.message) ?? "";
5108
5107
  }
5109
- function isModelMetadataError(message) {
5110
- return message.includes("Defaulting to fallback metadata");
5108
+ function isModelMetadataError(message2) {
5109
+ return message2.includes("Defaulting to fallback metadata");
5111
5110
  }
5112
5111
  function readToolItem(item, eventType) {
5113
5112
  const type = str(item.type);
@@ -5352,11 +5351,11 @@ async function* decodeCodexStream(events, ctx) {
5352
5351
  if (!item) continue;
5353
5352
  const itemType = readItemType(item);
5354
5353
  if (itemType === "error") {
5355
- const message = readItemMessage(item);
5356
- if (isModelMetadataError(message)) {
5354
+ const message2 = readItemMessage(item);
5355
+ if (isModelMetadataError(message2)) {
5357
5356
  yield* flushInterim();
5358
5357
  ok = false;
5359
- reason = `model_unavailable:${message.slice(0, 200)}`;
5358
+ reason = `model_unavailable:${message2.slice(0, 200)}`;
5360
5359
  settled = true;
5361
5360
  break;
5362
5361
  }
@@ -5523,7 +5522,7 @@ var init_model3 = __esm({
5523
5522
  });
5524
5523
 
5525
5524
  // packages/agent-runtime/src/codex/run-spec.ts
5526
- function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
5525
+ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
5527
5526
  const { policy, config } = req;
5528
5527
  const directory = req.local.cwd ?? "";
5529
5528
  const dialect = readCodexDialect(config.runtimeOptions);
@@ -5540,7 +5539,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
5540
5539
  skipGitRepoCheck: true,
5541
5540
  ...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
5542
5541
  baseInstructionsFile,
5543
- input: buildInput(req, promptRidesInput),
5542
+ input: withImages(buildInput(req, promptRidesInput), imagePaths),
5544
5543
  promptRidesInput,
5545
5544
  promptFingerprint,
5546
5545
  config: buildConfig(req, baseInstructionsFile),
@@ -5556,6 +5555,13 @@ function buildInput(req, promptRidesInput) {
5556
5555
 
5557
5556
  ${body}` : body;
5558
5557
  }
5558
+ function withImages(text, imagePaths) {
5559
+ if (imagePaths.length === 0) return text;
5560
+ return [
5561
+ { type: "text", text },
5562
+ ...imagePaths.map((path) => ({ type: "local_image", path }))
5563
+ ];
5564
+ }
5559
5565
  function buildConfig(req, baseInstructionsFile) {
5560
5566
  const mcp_servers = {};
5561
5567
  for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
@@ -5827,12 +5833,35 @@ function createCodexAdapter(deps = {}) {
5827
5833
  }
5828
5834
  }
5829
5835
  }
5836
+ let images = null;
5830
5837
  try {
5838
+ const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
5839
+ if (imageUrls.length > 0) {
5840
+ if (!deps.prepareImages) {
5841
+ deps.onWarn?.(
5842
+ "codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
5843
+ { images: imageUrls.length }
5844
+ );
5845
+ } else {
5846
+ try {
5847
+ images = await deps.prepareImages(imageUrls, signal);
5848
+ } catch (err) {
5849
+ if (!signal.aborted) {
5850
+ deps.onWarn?.(
5851
+ "codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
5852
+ { err: err instanceof Error ? err.message : String(err) }
5853
+ );
5854
+ }
5855
+ }
5856
+ }
5857
+ if (signal.aborted) return;
5858
+ }
5831
5859
  const spec = buildRunSpec2(
5832
5860
  req,
5833
5861
  resumeThreadId,
5834
5862
  instructions?.path ?? null,
5835
- threadPromptFingerprint
5863
+ threadPromptFingerprint,
5864
+ images?.paths ?? []
5836
5865
  );
5837
5866
  if (spec.model === null) {
5838
5867
  deps.onWarn?.("Codex is using its default model because no model was resolved", {
@@ -5861,6 +5890,15 @@ function createCodexAdapter(deps = {}) {
5861
5890
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
5862
5891
  });
5863
5892
  } finally {
5893
+ if (images) {
5894
+ try {
5895
+ await images.cleanup();
5896
+ } catch (err) {
5897
+ deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
5898
+ err: err instanceof Error ? err.message : String(err)
5899
+ });
5900
+ }
5901
+ }
5864
5902
  if (instructions) {
5865
5903
  try {
5866
5904
  await instructions.cleanup();
@@ -5953,14 +5991,14 @@ var init_conformance4 = __esm({
5953
5991
  type: "turn.completed",
5954
5992
  ...usage ? { usage } : {}
5955
5993
  });
5956
- turnFailed = (message) => ({
5994
+ turnFailed = (message2) => ({
5957
5995
  type: "turn.failed",
5958
- error: { message }
5996
+ error: { message: message2 }
5959
5997
  });
5960
- fatalError = (message) => ({ type: "error", message });
5961
- errorItem = (id, message) => ({
5998
+ fatalError = (message2) => ({ type: "error", message: message2 });
5999
+ errorItem = (id, message2) => ({
5962
6000
  type: "item.completed",
5963
- item: { id, type: "error", message }
6001
+ item: { id, type: "error", message: message2 }
5964
6002
  });
5965
6003
  OP_SUCCESS = { ok: true, result: { source: "native" } };
5966
6004
  OP_FAILURE = {
@@ -6898,8 +6936,8 @@ function unenforceable(policy) {
6898
6936
  deniedDomains: as === "deniedDomains" ? [host] : []
6899
6937
  });
6900
6938
  if (!check.success) {
6901
- const message = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
6902
- return `runners.policy.${field}.${i}: ${message}`;
6939
+ const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
6940
+ return `runners.policy.${field}.${i}: ${message2}`;
6903
6941
  }
6904
6942
  }
6905
6943
  }
@@ -6913,8 +6951,8 @@ function unenforceable(policy) {
6913
6951
  try {
6914
6952
  new RegExp(globToRegex(path));
6915
6953
  } catch (err) {
6916
- const message = err instanceof Error ? err.message : String(err);
6917
- return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message})`;
6954
+ const message2 = err instanceof Error ? err.message : String(err);
6955
+ return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
6918
6956
  }
6919
6957
  }
6920
6958
  }
@@ -7119,8 +7157,8 @@ async function reapTurnScopes(turnId) {
7119
7157
  });
7120
7158
  return { failures: [] };
7121
7159
  } catch (err) {
7122
- const message = err instanceof Error ? err.message : String(err);
7123
- return classifyStopError(message) === "absent" ? { failures: [] } : { failures: [message.slice(0, 200)] };
7160
+ const message2 = err instanceof Error ? err.message : String(err);
7161
+ return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
7124
7162
  }
7125
7163
  }
7126
7164
  function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
@@ -7136,13 +7174,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
7136
7174
  return false;
7137
7175
  }
7138
7176
  }
7139
- function classifyStopError(message) {
7177
+ function classifyStopError(message2) {
7140
7178
  if (/failed to connect to bus|failed to get d-?bus connection|connection refused|refusing to operate|spawn systemctl|systemctl.*ENOENT|ENOENT.*systemctl/i.test(
7141
- message
7179
+ message2
7142
7180
  )) {
7143
7181
  return "failure";
7144
7182
  }
7145
- if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message))
7183
+ if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
7146
7184
  return "absent";
7147
7185
  return "failure";
7148
7186
  }
@@ -7251,9 +7289,9 @@ function createTurnContainment(turnId, deps = {}) {
7251
7289
  timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
7252
7290
  });
7253
7291
  } catch (err) {
7254
- const message = err instanceof Error ? err.message : String(err);
7255
- if (classifyStopError(message) === "failure") {
7256
- result.failures.push({ target: unit, error: message.slice(0, 200) });
7292
+ const message2 = err instanceof Error ? err.message : String(err);
7293
+ if (classifyStopError(message2) === "failure") {
7294
+ result.failures.push({ target: unit, error: message2.slice(0, 200) });
7257
7295
  continue;
7258
7296
  }
7259
7297
  result.reaped.push(unit);
@@ -7617,8 +7655,8 @@ function runnerStdin(input) {
7617
7655
  })}
7618
7656
  `;
7619
7657
  }
7620
- function encodeLauncherMessage(message) {
7621
- return `${JSON.stringify(message)}
7658
+ function encodeLauncherMessage(message2) {
7659
+ return `${JSON.stringify(message2)}
7622
7660
  `;
7623
7661
  }
7624
7662
  function decodeLauncherMessage(line) {
@@ -8480,6 +8518,74 @@ var init_codex_instructions = __esm({
8480
8518
  }
8481
8519
  });
8482
8520
 
8521
+ // src/codex-images.ts
8522
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
8523
+ import { tmpdir as tmpdir2 } from "os";
8524
+ import { join as join12 } from "path";
8525
+ function createCodexImagePreparer(opts) {
8526
+ const doFetch = opts.fetch ?? fetch;
8527
+ const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
8528
+ return async (urls, signal) => {
8529
+ signal.throwIfAborted();
8530
+ const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
8531
+ const cleanup = async () => {
8532
+ try {
8533
+ await rm2(dir2, { recursive: true, force: true });
8534
+ } catch (err) {
8535
+ opts.warn(
8536
+ { dir: dir2, err: message(err) },
8537
+ "codex images: could not remove the scratch directory"
8538
+ );
8539
+ }
8540
+ };
8541
+ const prepareOne = async (url, index) => {
8542
+ try {
8543
+ const res = await doFetch(url, {
8544
+ signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
8545
+ });
8546
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
8547
+ const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
8548
+ const ext = EXTENSIONS[type];
8549
+ if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
8550
+ const bytes = new Uint8Array(await res.arrayBuffer());
8551
+ const path = join12(dir2, `image-${index + 1}.${ext}`);
8552
+ await writeFile2(path, bytes, { mode: 384 });
8553
+ return path;
8554
+ } catch (err) {
8555
+ if (!signal.aborted) {
8556
+ opts.warn(
8557
+ { image: index + 1, of: urls.length, err: message(err) },
8558
+ "codex images: an image could not be prepared; the turn runs without it"
8559
+ );
8560
+ }
8561
+ return null;
8562
+ }
8563
+ };
8564
+ const paths = await Promise.all(urls.map(prepareOne));
8565
+ if (signal.aborted) {
8566
+ await cleanup();
8567
+ signal.throwIfAborted();
8568
+ }
8569
+ return { paths: paths.filter((p) => p !== null), cleanup };
8570
+ };
8571
+ }
8572
+ function message(err) {
8573
+ return err instanceof Error ? err.message : String(err);
8574
+ }
8575
+ var FETCH_TIMEOUT_MS, EXTENSIONS;
8576
+ var init_codex_images = __esm({
8577
+ "src/codex-images.ts"() {
8578
+ "use strict";
8579
+ FETCH_TIMEOUT_MS = 3e4;
8580
+ EXTENSIONS = {
8581
+ "image/png": "png",
8582
+ "image/jpeg": "jpg",
8583
+ "image/gif": "gif",
8584
+ "image/webp": "webp"
8585
+ };
8586
+ }
8587
+ });
8588
+
8483
8589
  // src/turn-seq-floor.ts
8484
8590
  function resolveSeqFloor(sources, ctx) {
8485
8591
  const { serverFloor, outboxFloor } = sources;
@@ -8523,15 +8629,15 @@ var init_turn_seq_floor = __esm({
8523
8629
  import { spawn as spawn5 } from "child_process";
8524
8630
  import { hostname as hostname2 } from "os";
8525
8631
  function prepareFailureCause(error) {
8526
- const message = error instanceof Error ? error.message : String(error);
8527
- if (/timed out/i.test(message)) return "prepare hook timed out";
8528
- if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message))
8632
+ const message2 = error instanceof Error ? error.message : String(error);
8633
+ if (/timed out/i.test(message2)) return "prepare hook timed out";
8634
+ if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
8529
8635
  return "prepare hook module missing; check the configured command and its installation for this device";
8530
- if (/not valid JSON|JSON must carry|produced no output/.test(message))
8636
+ if (/not valid JSON|JSON must carry|produced no output/.test(message2))
8531
8637
  return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
8532
- if (/ENOENT/.test(message)) return "prepare hook executable missing (ENOENT)";
8533
- if (/EACCES/.test(message)) return "prepare hook executable is not accessible (EACCES)";
8534
- const exit = message.match(/exited with code (\d+)/)?.[1];
8638
+ if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
8639
+ if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
8640
+ const exit = message2.match(/exited with code (\d+)/)?.[1];
8535
8641
  return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
8536
8642
  }
8537
8643
  async function runPrepareWithReporting(runner, hook, input, context, reportError) {
@@ -8588,15 +8694,15 @@ var init_prepare_failure = __esm({
8588
8694
 
8589
8695
  // src/prepared.ts
8590
8696
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
8591
- import { join as join12 } from "path";
8697
+ import { join as join13 } from "path";
8592
8698
  function dirFor(workspaceId) {
8593
- return join12(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8699
+ return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8594
8700
  }
8595
8701
  function conversationDir(workspaceId, conversationId) {
8596
- return join12(dirFor(workspaceId), encodeURIComponent(conversationId));
8702
+ return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
8597
8703
  }
8598
8704
  function pathFor2(workspaceId, conversationId, agentId) {
8599
- return join12(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8705
+ return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8600
8706
  }
8601
8707
  function readPrepared(workspaceId, conversationId, agentId) {
8602
8708
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -8634,9 +8740,9 @@ var init_prepared = __esm({
8634
8740
 
8635
8741
  // src/transcript-writer.ts
8636
8742
  import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
8637
- import { basename, dirname as dirname6, join as join13 } from "path";
8743
+ import { basename, dirname as dirname6, join as join14 } from "path";
8638
8744
  function transcriptsDir() {
8639
- return join13(runnerStateDir(), "transcripts");
8745
+ return join14(runnerStateDir(), "transcripts");
8640
8746
  }
8641
8747
  function fileName(meta) {
8642
8748
  const d = new Date(meta.ts);
@@ -8662,7 +8768,7 @@ function pruneOld(dir2, retain) {
8662
8768
  const drop = files.sort().slice(0, files.length - retain);
8663
8769
  for (const f of drop) {
8664
8770
  try {
8665
- rmSync6(join13(dir2, f), { force: true });
8771
+ rmSync6(join14(dir2, f), { force: true });
8666
8772
  } catch {
8667
8773
  }
8668
8774
  }
@@ -8680,7 +8786,7 @@ var init_transcript_writer = __esm({
8680
8786
  onWarn;
8681
8787
  constructor(dir2, meta, onWarn) {
8682
8788
  this.onWarn = onWarn;
8683
- this.path = join13(dir2, fileName(meta));
8789
+ this.path = join14(dir2, fileName(meta));
8684
8790
  try {
8685
8791
  mkdirSync8(dir2, { recursive: true });
8686
8792
  try {
@@ -8709,10 +8815,10 @@ var init_transcript_writer = __esm({
8709
8815
  preserveAnomaly() {
8710
8816
  if (this.broken) return;
8711
8817
  try {
8712
- const dir2 = join13(dirname6(this.path), "anomalies");
8818
+ const dir2 = join14(dirname6(this.path), "anomalies");
8713
8819
  mkdirSync8(dir2, { recursive: true, mode: 448 });
8714
8820
  chmodSync4(dir2, 448);
8715
- const target = join13(dir2, basename(this.path));
8821
+ const target = join14(dir2, basename(this.path));
8716
8822
  copyFileSync(this.path, target);
8717
8823
  chmodSync4(target, 384);
8718
8824
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -9007,6 +9113,7 @@ var init_turn_execution = __esm({
9007
9113
  init_build_options();
9008
9114
  init_bundled_binaries();
9009
9115
  init_codex_instructions();
9116
+ init_codex_images();
9010
9117
  init_config();
9011
9118
  init_api_error_shape();
9012
9119
  init_errors();
@@ -9355,16 +9462,16 @@ var init_turn_execution = __esm({
9355
9462
  }
9356
9463
  gateTrigger() {
9357
9464
  const { payload, turnLog } = this;
9358
- const message = this.turnContext.message;
9359
- const isDispatchableTrigger = message.role === "user" || message.role === "agent" || message.role === "system";
9465
+ const message2 = this.turnContext.message;
9466
+ const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
9360
9467
  if (!isDispatchableTrigger) {
9361
- turnLog.debug({ role: message.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
9468
+ turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
9362
9469
  throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
9363
9470
  }
9364
9471
  this.supervisor.notifyStart({
9365
9472
  id: this.dispatchId,
9366
9473
  conversationId: payload.conversationId,
9367
- message: message.body
9474
+ message: message2.body
9368
9475
  });
9369
9476
  }
9370
9477
  async resolveSecrets() {
@@ -9715,6 +9822,10 @@ var init_turn_execution = __esm({
9715
9822
  enabled: true,
9716
9823
  onWarn,
9717
9824
  writeInstructionsFile: writeCodexInstructionsFile,
9825
+ prepareImages: createCodexImagePreparer({
9826
+ turnId: this.turnId,
9827
+ warn: (meta, msg) => turnLog.warn(meta, msg)
9828
+ }),
9718
9829
  ...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
9719
9830
  })
9720
9831
  );
@@ -10175,7 +10286,7 @@ var init_turn_execution = __esm({
10175
10286
  // src/dispatcher.ts
10176
10287
  import { randomUUID as randomUUID4 } from "crypto";
10177
10288
  import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
10178
- import { join as join14 } from "path";
10289
+ import { join as join15 } from "path";
10179
10290
  function checkoutState(cwd) {
10180
10291
  if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
10181
10292
  if (!existsSync10(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
@@ -10188,7 +10299,7 @@ function checkoutState(cwd) {
10188
10299
  if (entries.length === 0) {
10189
10300
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
10190
10301
  }
10191
- const gitPath = join14(cwd, ".git");
10302
+ const gitPath = join15(cwd, ".git");
10192
10303
  if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
10193
10304
  let stat;
10194
10305
  try {
@@ -10196,7 +10307,7 @@ function checkoutState(cwd) {
10196
10307
  } catch (error) {
10197
10308
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
10198
10309
  }
10199
- if (stat.isDirectory() && !existsSync10(join14(gitPath, "HEAD")))
10310
+ if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
10200
10311
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
10201
10312
  return { ok: true, reason: "usable" };
10202
10313
  }
@@ -10562,8 +10673,8 @@ import {
10562
10673
  } from "fs";
10563
10674
  import { createRequire as createRequire2 } from "module";
10564
10675
  import { isIP } from "net";
10565
- import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir3, userInfo as userInfo2 } from "os";
10566
- import { dirname as dirname8, join as join18, sep as sep2 } from "path";
10676
+ import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
10677
+ import { dirname as dirname8, join as join19, sep as sep2 } from "path";
10567
10678
  import { fileURLToPath as fileURLToPath4 } from "url";
10568
10679
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
10569
10680
  function accountHome() {
@@ -10597,7 +10708,7 @@ async function resolvedAddresses(baseUrl) {
10597
10708
  function companionPackageRoot(modulePath) {
10598
10709
  for (let dir2 = dirname8(modulePath); ; dir2 = dirname8(dir2)) {
10599
10710
  try {
10600
- const manifest = JSON.parse(readFileSync10(join18(dir2, "package.json"), "utf8"));
10711
+ const manifest = JSON.parse(readFileSync10(join19(dir2, "package.json"), "utf8"));
10601
10712
  if (manifest.name === "@cabane/companion") return dir2;
10602
10713
  } catch {
10603
10714
  }
@@ -10674,15 +10785,15 @@ function companionStoreDenies(input) {
10674
10785
  const always = [
10675
10786
  cabaneDir2,
10676
10787
  ...homes.flatMap((home) => [
10677
- join18(home, ".cabane"),
10678
- join18(home, "*", ".cabane"),
10679
- join18(home, "*", "*", ".cabane")
10788
+ join19(home, ".cabane"),
10789
+ join19(home, "*", ".cabane"),
10790
+ join19(home, "*", "*", ".cabane")
10680
10791
  ]),
10681
10792
  // A glob anchored at the root has no directory to start from.
10682
- ...dirname8(parent) === parent ? [] : [join18(parent, "*", ".cabane")]
10793
+ ...dirname8(parent) === parent ? [] : [join19(parent, "*", ".cabane")]
10683
10794
  ];
10684
10795
  const companionHome = dirname8(cabaneDir2);
10685
- const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [join18(home, ".claude"), join18(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
10796
+ const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [join19(home, ".claude"), join19(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
10686
10797
  return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
10687
10798
  }
10688
10799
  function companionStoreWatch(log) {
@@ -10699,7 +10810,7 @@ function isAtOrUnder(path, dir2) {
10699
10810
  }
10700
10811
  function expandHome(path, home) {
10701
10812
  if (path === "~") return home;
10702
- return path.startsWith("~/") ? join18(home, path.slice(2)) : path;
10813
+ return path.startsWith("~/") ? join19(home, path.slice(2)) : path;
10703
10814
  }
10704
10815
  function stripPort(pattern) {
10705
10816
  return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
@@ -10717,8 +10828,8 @@ function patternsOverlap(a, b) {
10717
10828
  }
10718
10829
  function harnessSandboxConfig(input) {
10719
10830
  const { policy } = input;
10720
- const claudeDir = input.claudeConfigDir ?? join18(input.home, ".claude");
10721
- const codexDir = input.codexHome ?? join18(input.home, ".codex");
10831
+ const claudeDir = input.claudeConfigDir ?? join19(input.home, ".claude");
10832
+ const codexDir = input.codexHome ?? join19(input.home, ".codex");
10722
10833
  const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
10723
10834
  const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
10724
10835
  const cabane = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
@@ -10735,7 +10846,7 @@ function harnessSandboxConfig(input) {
10735
10846
  foundStores: input.foundStores,
10736
10847
  ownState: [claudeDir, codexDir]
10737
10848
  }),
10738
- ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join18(home, p))),
10849
+ ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join19(home, p))),
10739
10850
  ...expand(policy.filesystem?.denyRead)
10740
10851
  ];
10741
10852
  const strict = policy.filesystem?.mode === "strict";
@@ -10743,7 +10854,7 @@ function harnessSandboxConfig(input) {
10743
10854
  const allowRead = strict ? [
10744
10855
  ...input.cwd ? [input.cwd] : [],
10745
10856
  claudeDir,
10746
- join18(input.home, ".claude.json"),
10857
+ join19(input.home, ".claude.json"),
10747
10858
  codexDir,
10748
10859
  ...input.runtimePaths ?? [],
10749
10860
  ...expand(policy.filesystem?.allowRead)
@@ -10783,7 +10894,7 @@ function harnessSandboxConfig(input) {
10783
10894
  ...input.cwd ? [input.cwd] : [],
10784
10895
  input.tmp,
10785
10896
  claudeDir,
10786
- join18(input.home, ".claude.json"),
10897
+ join19(input.home, ".claude.json"),
10787
10898
  codexDir,
10788
10899
  ...expand(policy.filesystem?.allowWrite)
10789
10900
  ],
@@ -10836,7 +10947,7 @@ async function prepareHarnessSandbox(input) {
10836
10947
  ownAddresses: ownAddresses(),
10837
10948
  home: homedir3(),
10838
10949
  accountHome: accountHome(),
10839
- tmp: tmpdir3(),
10950
+ tmp: tmpdir4(),
10840
10951
  cabaneDir: cabaneDir(),
10841
10952
  claudeConfigDir: process.env.CLAUDE_CONFIG_DIR,
10842
10953
  codexHome: process.env.CODEX_HOME,
@@ -10867,27 +10978,27 @@ async function prepareHarnessSandbox(input) {
10867
10978
  "export NO_PROXY= no_proxy=; " + gitCredentialExports(input.credentials) + 'eval "exec $CABANE_SANDBOXED_ARGV"'
10868
10979
  );
10869
10980
  } catch (err) {
10870
- const message = err instanceof Error ? err.message : String(err);
10981
+ const message2 = err instanceof Error ? err.message : String(err);
10871
10982
  await SandboxManager.reset().catch(() => {
10872
10983
  });
10873
10984
  if (held.length || Object.keys(input.policy).length) {
10874
10985
  input.log.warn(
10875
- { err: message, masked: held },
10986
+ { err: message2, masked: held },
10876
10987
  "runners: the OS sandbox could not start under this device's runners.policy; refusing the turn rather than running it unsandboxed"
10877
10988
  );
10878
- throw new HarnessSandboxRefused(`the OS sandbox could not start: ${message}`);
10989
+ throw new HarnessSandboxRefused(`the OS sandbox could not start: ${message2}`);
10879
10990
  }
10880
10991
  input.log.warn(
10881
- { err: message },
10992
+ { err: message2 },
10882
10993
  "runners: the OS sandbox could not start; running the turn unsandboxed"
10883
10994
  );
10884
10995
  return null;
10885
10996
  } finally {
10886
10997
  for (const name of held) delete process.env[name];
10887
10998
  }
10888
- mkdirSync11(join18(cabaneDir(), "sandbox"), { recursive: true });
10889
- const dir2 = mkdtempSync(join18(cabaneDir(), "sandbox", "turn-"));
10890
- const script = join18(dir2, "exec-sandboxed");
10999
+ mkdirSync11(join19(cabaneDir(), "sandbox"), { recursive: true });
11000
+ const dir2 = mkdtempSync(join19(cabaneDir(), "sandbox", "turn-"));
11001
+ const script = join19(dir2, "exec-sandboxed");
10891
11002
  writeFileSync7(
10892
11003
  script,
10893
11004
  [
@@ -10903,7 +11014,7 @@ async function prepareHarnessSandbox(input) {
10903
11014
  return {
10904
11015
  wrap: (command, args) => ({ command: script, args: [command, ...args] }),
10905
11016
  executableFor: (real) => {
10906
- const shim = join18(dir2, `exec-${++shims}`);
11017
+ const shim = join19(dir2, `exec-${++shims}`);
10907
11018
  writeFileSync7(shim, `#!/bin/bash
10908
11019
  exec ${shellQuote(script)} ${shellQuote(real)} "$@"
10909
11020
  `);
@@ -10996,7 +11107,7 @@ import {
10996
11107
  rmSync as rmSync11,
10997
11108
  writeFileSync as writeFileSync8
10998
11109
  } from "fs";
10999
- import { join as join19 } from "path";
11110
+ import { join as join20 } from "path";
11000
11111
  var MAX_ENTRIES, MAX_AGE_MS, Outbox;
11001
11112
  var init_outbox = __esm({
11002
11113
  "src/outbox.ts"() {
@@ -11014,10 +11125,10 @@ var init_outbox = __esm({
11014
11125
  // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
11015
11126
  // cases route writes at the right tmpdir.
11016
11127
  dir() {
11017
- return join19(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11128
+ return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11018
11129
  }
11019
11130
  fileFor(turnId, seq) {
11020
- return join19(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11131
+ return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11021
11132
  }
11022
11133
  // Persist a commit for later draining. Atomic (temp file + rename) so a
11023
11134
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -11059,7 +11170,7 @@ var init_outbox = __esm({
11059
11170
  const entries = [];
11060
11171
  for (const name of names) {
11061
11172
  if (!name.endsWith(".json")) continue;
11062
- const full = join19(dir2, name);
11173
+ const full = join20(dir2, name);
11063
11174
  try {
11064
11175
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
11065
11176
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -11199,7 +11310,7 @@ var init_image_manifest = __esm({
11199
11310
 
11200
11311
  // src/runner/exec-shim.ts
11201
11312
  import { chmodSync as chmodSync7, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "fs";
11202
- import { join as join22 } from "path";
11313
+ import { join as join23 } from "path";
11203
11314
  function shellQuote2(value) {
11204
11315
  return `'${value.replace(/'/g, `'\\''`)}'`;
11205
11316
  }
@@ -11209,10 +11320,10 @@ function createShimDir() {
11209
11320
  return {
11210
11321
  write(argv) {
11211
11322
  if (!dir2) {
11212
- mkdirSync15(join22(cabaneDir(), "runner"), { recursive: true });
11213
- dir2 = mkdtempSync2(join22(cabaneDir(), "runner", "turn-"));
11323
+ mkdirSync15(join23(cabaneDir(), "runner"), { recursive: true });
11324
+ dir2 = mkdtempSync2(join23(cabaneDir(), "runner", "turn-"));
11214
11325
  }
11215
- const path = join22(dir2, `exec-${++count}`);
11326
+ const path = join23(dir2, `exec-${++count}`);
11216
11327
  writeFileSync10(path, `#!/bin/sh
11217
11328
  exec ${argv.map(shellQuote2).join(" ")} "$@"
11218
11329
  `);
@@ -11248,9 +11359,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
11248
11359
  resolveFirst(line);
11249
11360
  return;
11250
11361
  }
11251
- const message = decodeLauncherMessage(line);
11252
- if (message?.cabaneLauncher === "granted") waiting.get(message.id)?.();
11253
- else if (message) replies.get(message.id)?.(message);
11362
+ const message2 = decodeLauncherMessage(line);
11363
+ if (message2?.cabaneLauncher === "granted") waiting.get(message2.id)?.();
11364
+ else if (message2) replies.get(message2.id)?.(message2);
11254
11365
  });
11255
11366
  let gone = false;
11256
11367
  lines2.once("close", () => {
@@ -11263,9 +11374,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
11263
11374
  if (gone) return Promise.resolve(null);
11264
11375
  const id = String(++next);
11265
11376
  return new Promise((resolve2) => {
11266
- replies.set(id, (message) => {
11377
+ replies.set(id, (message2) => {
11267
11378
  replies.delete(id);
11268
- resolve2(message);
11379
+ resolve2(message2);
11269
11380
  });
11270
11381
  emit(event(id));
11271
11382
  });
@@ -12475,8 +12586,8 @@ async function logout(opts = {}) {
12475
12586
  return;
12476
12587
  }
12477
12588
  if (!opts.yes) {
12478
- const message = opts.purge ? "Purge the entire local companion config (device + agent overrides + settings)?" : "Log out this device (removes the device token + cached agent credentials; keeps your config)?";
12479
- const ok = await confirm2({ message, default: false });
12589
+ const message2 = opts.purge ? "Purge the entire local companion config (device + agent overrides + settings)?" : "Log out this device (removes the device token + cached agent credentials; keeps your config)?";
12590
+ const ok = await confirm2({ message: message2, default: false });
12480
12591
  if (!ok) {
12481
12592
  process.stdout.write("cancelled\n");
12482
12593
  return;
@@ -12732,8 +12843,8 @@ function formatHumanLine(record) {
12732
12843
  let line = `${time} ${word ? `${word} ` : ""}${context ? `${context} ` : ""}${humanText(record.msg)}`;
12733
12844
  const err = record.err;
12734
12845
  const errorRecord = err !== null && typeof err === "object" ? err : null;
12735
- const message = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
12736
- const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message}` : message;
12846
+ const message2 = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
12847
+ const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message2}` : message2;
12737
12848
  if (detail && !line.includes(detail)) line += ` (${detail})`;
12738
12849
  if (level < 30) {
12739
12850
  const hidden = /* @__PURE__ */ new Set([
@@ -12967,9 +13078,9 @@ var CompanionStateHub = class {
12967
13078
  if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
12968
13079
  this.emitStatus();
12969
13080
  }
12970
- setDeviceError(message) {
12971
- if (this.deviceError === message) return;
12972
- this.deviceError = message;
13081
+ setDeviceError(message2) {
13082
+ if (this.deviceError === message2) return;
13083
+ this.deviceError = message2;
12973
13084
  this.emitStatus();
12974
13085
  }
12975
13086
  clearDeviceError() {
@@ -13421,7 +13532,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
13421
13532
  const probeClaude = deps.probeClaude ?? claudeOnPath;
13422
13533
  const probeCodex = deps.probeCodex ?? codexOnPath;
13423
13534
  const resolveBundled = deps.resolveBundled ?? ((runtime) => resolveBundledBinary(runtime));
13424
- const warn = deps.warn ?? ((message) => process.stderr.write(`${message}
13535
+ const warn = deps.warn ?? ((message2) => process.stderr.write(`${message2}
13425
13536
  `));
13426
13537
  const connected = [
13427
13538
  ...isClaudeCodeConnected(cfg) ? ["Claude Code"] : [],
@@ -13913,7 +14024,7 @@ function createLocalProcessProvider(opts = {}) {
13913
14024
 
13914
14025
  // src/providers/docker.ts
13915
14026
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
13916
- import { constants as constants2, tmpdir as tmpdir2 } from "os";
14027
+ import { constants as constants2, tmpdir as tmpdir3 } from "os";
13917
14028
  import {
13918
14029
  createReadStream,
13919
14030
  createWriteStream as createWriteStream2,
@@ -13926,7 +14037,7 @@ import {
13926
14037
  statSync as statSync3,
13927
14038
  writeFileSync as writeFileSync6
13928
14039
  } from "fs";
13929
- import { join as join17 } from "path";
14040
+ import { join as join18 } from "path";
13930
14041
  import { Writable } from "stream";
13931
14042
 
13932
14043
  // src/providers/docker-cli.ts
@@ -13999,7 +14110,7 @@ import {
13999
14110
  renameSync as renameSync4,
14000
14111
  rmSync as rmSync8
14001
14112
  } from "fs";
14002
- import { join as join15 } from "path";
14113
+ import { join as join16 } from "path";
14003
14114
  function routes(t) {
14004
14115
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
14005
14116
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -14045,7 +14156,7 @@ function publishStaged(from, to, move = atomicMove) {
14045
14156
  const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
14046
14157
  let entry = {};
14047
14158
  try {
14048
- entry = JSON.parse(readFileSync8(join15(from, name), "utf8"));
14159
+ entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
14049
14160
  } catch {
14050
14161
  }
14051
14162
  return { name, entry };
@@ -14058,7 +14169,7 @@ function publishStaged(from, to, move = atomicMove) {
14058
14169
  let published = 0;
14059
14170
  for (const { name } of staged) {
14060
14171
  try {
14061
- move(join15(from, name), join15(to, name));
14172
+ move(join16(from, name), join16(to, name));
14062
14173
  } catch {
14063
14174
  return { published, complete: false };
14064
14175
  }
@@ -14071,7 +14182,7 @@ function publishStaged(from, to, move = atomicMove) {
14071
14182
  // src/providers/runner-base.ts
14072
14183
  import { createHash as createHash3 } from "crypto";
14073
14184
  import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
14074
- import { dirname as dirname7, join as join16, sep } from "path";
14185
+ import { dirname as dirname7, join as join17, sep } from "path";
14075
14186
  import { fileURLToPath as fileURLToPath3 } from "url";
14076
14187
  var COMPANION_MOUNT = "/opt/cabane/companion";
14077
14188
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -14119,11 +14230,11 @@ async function ensureBaseImage(docker, image, log) {
14119
14230
  function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
14120
14231
  let dir2 = dirname7(moduleFile);
14121
14232
  for (let i = 0; i < 4; i++) {
14122
- if (existsSync12(join16(dir2, "dist", "cli.js")) && existsSync12(join16(dir2, "package.json"))) break;
14233
+ if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
14123
14234
  dir2 = dirname7(dir2);
14124
14235
  }
14125
- if (!existsSync12(join16(dir2, "dist", "cli.js"))) return null;
14126
- const sdk = join16(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
14236
+ if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
14237
+ const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
14127
14238
  if (!existsSync12(sdk)) return null;
14128
14239
  const root = realpathSync2(dir2);
14129
14240
  return realpathSync2(sdk).startsWith(root + sep) ? root : null;
@@ -14262,21 +14373,21 @@ function networkCreateError(stderr) {
14262
14373
  }
14263
14374
  function sessionArchivePath(deviceDir, attemptId) {
14264
14375
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
14265
- return join17(deviceDir, "attempts", attemptId, "session.tar");
14376
+ return join18(deviceDir, "attempts", attemptId, "session.tar");
14266
14377
  }
14267
14378
  function pruneOtherAttempts(deviceDir, pair2, keep) {
14268
- const root = join17(deviceDir, "attempts");
14379
+ const root = join18(deviceDir, "attempts");
14269
14380
  if (!existsSync13(root)) return;
14270
14381
  for (const id of readdirSync4(root)) {
14271
14382
  if (id === keep || !UUID_RE.test(id)) continue;
14272
14383
  let pairOf = "";
14273
14384
  try {
14274
- pairOf = readFileSync9(join17(root, id, "pair"), "utf8");
14385
+ pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
14275
14386
  } catch {
14276
14387
  continue;
14277
14388
  }
14278
14389
  if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
14279
- rmSync9(join17(root, id), { recursive: true, force: true });
14390
+ rmSync9(join18(root, id), { recursive: true, force: true });
14280
14391
  }
14281
14392
  }
14282
14393
  }
@@ -14476,11 +14587,11 @@ function createDockerProvider(opts) {
14476
14587
  await must(["network", "disconnect", "-f", other, name], "moving the container");
14477
14588
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
14478
14589
  };
14479
- const socketRoot = opts.socketRoot ?? join17(
14480
- process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir2(),
14590
+ const socketRoot = opts.socketRoot ?? join18(
14591
+ process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
14481
14592
  `cabane-runners-${opts.deviceKey}`
14482
14593
  );
14483
- const socketDir = (name) => join17(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
14594
+ const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
14484
14595
  const create = async (name, attempt) => {
14485
14596
  mkdirSync10(socketRoot, { recursive: true, mode: 448 });
14486
14597
  mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
@@ -14622,7 +14733,7 @@ function createDockerProvider(opts) {
14622
14733
  }
14623
14734
  };
14624
14735
  const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
14625
- const pendingPath = (name) => join17(opts.deviceDir, "runners-pending", `${name}.json`);
14736
+ const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
14626
14737
  const readPending = (name) => {
14627
14738
  try {
14628
14739
  const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
@@ -14636,12 +14747,12 @@ function createDockerProvider(opts) {
14636
14747
  rmSync9(pendingPath(name), { force: true });
14637
14748
  return;
14638
14749
  }
14639
- mkdirSync10(join17(opts.deviceDir, "runners-pending"), { recursive: true });
14750
+ mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
14640
14751
  writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
14641
14752
  };
14642
14753
  const keepSession = async (name, attempt, attemptId) => {
14643
14754
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
14644
- mkdirSync10(join17(archive, ".."), { recursive: true });
14755
+ mkdirSync10(join18(archive, ".."), { recursive: true });
14645
14756
  const partial = `${archive}.${process.pid}.partial`;
14646
14757
  const file = new CappedFile(partial, sessionCap);
14647
14758
  const r = await execIn(
@@ -14658,7 +14769,7 @@ function createDockerProvider(opts) {
14658
14769
  );
14659
14770
  if (r.code === 0 && !file.over) {
14660
14771
  renameSync5(partial, archive);
14661
- writeFileSync6(join17(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
14772
+ writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
14662
14773
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
14663
14774
  await writeMarker(name, attemptId);
14664
14775
  log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -14672,7 +14783,7 @@ function createDockerProvider(opts) {
14672
14783
  file.over ? "runners: the session files are over the size cap and weren't kept; the next new container starts the session over" : "runners: couldn't keep the session files; the next new container starts the session over"
14673
14784
  );
14674
14785
  };
14675
- const stagingDir = (name) => join17(opts.deviceDir, "runners-pending", `${name}.outbox`);
14786
+ const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
14676
14787
  const ensureRunning = async (name) => {
14677
14788
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
14678
14789
  if (seen.code !== 0) return false;
@@ -14680,7 +14791,7 @@ function createDockerProvider(opts) {
14680
14791
  return (await docker.run(["start", name])).code === 0;
14681
14792
  };
14682
14793
  const dropArchive = (attemptId) => {
14683
- rmSync9(join17(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
14794
+ rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
14684
14795
  recursive: true,
14685
14796
  force: true
14686
14797
  });
@@ -14749,7 +14860,7 @@ function createDockerProvider(opts) {
14749
14860
  if (seen.stdout.trim().split(" ")[0] !== "true")
14750
14861
  await must(["start", name], "starting the container");
14751
14862
  const git = await serveGitCredentials({
14752
- socketPath: join17(socketDir(name), "git.sock"),
14863
+ socketPath: join18(socketDir(name), "git.sock"),
14753
14864
  hosts: opts.gitHosts,
14754
14865
  fill: opts.gitFill,
14755
14866
  log
@@ -14898,15 +15009,15 @@ function createDockerProvider(opts) {
14898
15009
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
14899
15010
  if (transcripts) {
14900
15011
  const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
14901
- if (kept.length) mkdirSync10(join17(opts.hostStateDir, "transcripts"), { recursive: true });
14902
- for (const f of kept) writeFileSync6(join17(opts.hostStateDir, "transcripts", f.name), f.data);
15012
+ if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
15013
+ for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
14903
15014
  await removeIn(
14904
15015
  name,
14905
15016
  `${CONTAINER_STATE}/transcripts`,
14906
15017
  kept.map((f) => f.name)
14907
15018
  );
14908
15019
  }
14909
- const outboxRel = join17("outbox", encodeURIComponent(attempt.agentId));
15020
+ const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
14910
15021
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
14911
15022
  let unpublished = false;
14912
15023
  if (outbox) {
@@ -14932,7 +15043,7 @@ function createDockerProvider(opts) {
14932
15043
  const staged = stagingDir(name);
14933
15044
  mkdirSync10(staged, { recursive: true });
14934
15045
  writeFileSync6(
14935
- join17(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
15046
+ join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
14936
15047
  `${JSON.stringify(entry)}
14937
15048
  `
14938
15049
  );
@@ -14945,7 +15056,7 @@ function createDockerProvider(opts) {
14945
15056
  if (outbox.complete) {
14946
15057
  const published = publishStaged(
14947
15058
  stagingDir(name),
14948
- join17(opts.hostStateDir, outboxRel),
15059
+ join18(opts.hostStateDir, outboxRel),
14949
15060
  opts.moveFile
14950
15061
  );
14951
15062
  queued = published.published;
@@ -16775,8 +16886,8 @@ function errorCode(err) {
16775
16886
  function isRecoverableSocketError(err) {
16776
16887
  const code = errorCode(err);
16777
16888
  if (code && RECOVERABLE_CODES.has(code)) return true;
16778
- const message = err instanceof Error ? err.message : String(err);
16779
- return /\bEPIPE\b|\bECONNRESET\b/.test(message);
16889
+ const message2 = err instanceof Error ? err.message : String(err);
16890
+ return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
16780
16891
  }
16781
16892
  function installProcessSafetyNet(log, opts = {}) {
16782
16893
  const proc = opts.proc ?? process;
@@ -16791,17 +16902,17 @@ function installProcessSafetyNet(log, opts = {}) {
16791
16902
  );
16792
16903
  }
16793
16904
  function handleUncaught(log, err, origin) {
16794
- const message = err instanceof Error ? err.message : String(err);
16905
+ const message2 = err instanceof Error ? err.message : String(err);
16795
16906
  const code = errorCode(err);
16796
16907
  if (isRecoverableSocketError(err)) {
16797
16908
  log.debug(
16798
- { origin, code, err: message },
16909
+ { origin, code, err: message2 },
16799
16910
  "companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
16800
16911
  );
16801
16912
  return;
16802
16913
  }
16803
16914
  log.error(
16804
- { origin, code, err: message, stack: err instanceof Error ? err.stack : void 0 },
16915
+ { origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
16805
16916
  "An unexpected error occurred; the companion is still running."
16806
16917
  );
16807
16918
  }
@@ -16809,9 +16920,9 @@ function handleUncaught(log, err, origin) {
16809
16920
  // src/crash-marker.ts
16810
16921
  init_config();
16811
16922
  import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
16812
- import { join as join20 } from "path";
16923
+ import { join as join21 } from "path";
16813
16924
  function crashMarkerPath() {
16814
- return join20(cabaneDir(), "last-error.json");
16925
+ return join21(cabaneDir(), "last-error.json");
16815
16926
  }
16816
16927
  function recordCrash(rec2) {
16817
16928
  try {
@@ -17162,7 +17273,7 @@ async function startScript(opts, interactive) {
17162
17273
  const result = await createCompanionRuntime({
17163
17274
  // The script says what's connectable in its own words below; a stderr warning
17164
17275
  // in the middle of it would be the same news, worse.
17165
- onReadinessWarning: (message) => log.warn(message)
17276
+ onReadinessWarning: (message2) => log.warn(message2)
17166
17277
  });
17167
17278
  if (!result.ok) {
17168
17279
  reportAlreadyRunning(result.existing?.pid);
@@ -17567,7 +17678,7 @@ init_errors();
17567
17678
  init_src();
17568
17679
  init_transcript_writer();
17569
17680
  import { existsSync as existsSync17, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
17570
- import { isAbsolute, join as join21 } from "path";
17681
+ import { isAbsolute, join as join22 } from "path";
17571
17682
  async function transcript(opts = {}) {
17572
17683
  const dir2 = transcriptsDir();
17573
17684
  if (opts.follow) {
@@ -17584,7 +17695,7 @@ async function transcript(opts = {}) {
17584
17695
  process.stdout.write(emptyMessage(dir2));
17585
17696
  return;
17586
17697
  }
17587
- process.stdout.write(renderFile(join21(dir2, newest)) + "\n");
17698
+ process.stdout.write(renderFile(join22(dir2, newest)) + "\n");
17588
17699
  return;
17589
17700
  }
17590
17701
  printList(dir2);
@@ -17667,7 +17778,7 @@ function isComplete(content) {
17667
17778
  async function followTranscripts(dir2) {
17668
17779
  const follower = new TranscriptFollower({
17669
17780
  listFiles: () => listFiles(dir2),
17670
- read: (f) => readFileSync13(join21(dir2, f), "utf8"),
17781
+ read: (f) => readFileSync13(join22(dir2, f), "utf8"),
17671
17782
  write: (s) => process.stdout.write(s),
17672
17783
  // CSI: cursor up `n` lines, then erase from cursor to end of screen.
17673
17784
  clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
@@ -17707,7 +17818,7 @@ function printList(dir2) {
17707
17818
 
17708
17819
  `);
17709
17820
  for (const f of files.slice(0, 20)) {
17710
- const { meta, outcome } = peek(join21(dir2, f));
17821
+ const { meta, outcome } = peek(join22(dir2, f));
17711
17822
  const when = fmtTime(rec(meta)?.ts);
17712
17823
  const ws = str2(rec(meta)?.workspaceSlug);
17713
17824
  const o = rec(outcome);
@@ -17744,10 +17855,10 @@ function resolveTarget(dir2, target) {
17744
17855
  if (existsSync17(target)) return target;
17745
17856
  throw new CompanionError(`no transcript at ${target}.`);
17746
17857
  }
17747
- const exact = join21(dir2, target);
17858
+ const exact = join22(dir2, target);
17748
17859
  if (existsSync17(exact)) return exact;
17749
17860
  const matches = listFiles(dir2).filter((f) => f.includes(target));
17750
- if (matches.length === 1) return join21(dir2, matches[0]);
17861
+ if (matches.length === 1) return join22(dir2, matches[0]);
17751
17862
  if (matches.length === 0) {
17752
17863
  throw new CompanionError(
17753
17864
  `no transcript matching "${target}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`