@cabane/companion 0.6.142 → 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 +272 -160
  2. package/dist/runtime.js +226 -121
  3. package/package.json +1 -1
package/dist/runtime.js CHANGED
@@ -17,14 +17,14 @@ import { z as z2 } from "zod";
17
17
 
18
18
  // src/errors.ts
19
19
  var CompanionError = class extends Error {
20
- constructor(message) {
21
- super(message);
20
+ constructor(message2) {
21
+ super(message2);
22
22
  this.name = "CompanionError";
23
23
  }
24
24
  };
25
25
  var ApiError = class extends CompanionError {
26
- constructor(status, message, body) {
27
- super(message);
26
+ constructor(status, message2, body) {
27
+ super(message2);
28
28
  this.status = status;
29
29
  this.body = body;
30
30
  this.name = "ApiError";
@@ -33,14 +33,14 @@ var ApiError = class extends CompanionError {
33
33
  body;
34
34
  };
35
35
  var ConfigError = class extends CompanionError {
36
- constructor(message) {
37
- super(message);
36
+ constructor(message2) {
37
+ super(message2);
38
38
  this.name = "ConfigError";
39
39
  }
40
40
  };
41
41
  var PrepareHookError = class extends CompanionError {
42
- constructor(message) {
43
- super(message);
42
+ constructor(message2) {
43
+ super(message2);
44
44
  this.name = "PrepareHookError";
45
45
  }
46
46
  };
@@ -1202,8 +1202,8 @@ function formatHumanLine(record) {
1202
1202
  let line = `${time} ${word ? `${word} ` : ""}${context ? `${context} ` : ""}${humanText(record.msg)}`;
1203
1203
  const err = record.err;
1204
1204
  const errorRecord = err !== null && typeof err === "object" ? err : null;
1205
- const message = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
1206
- const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message}` : message;
1205
+ const message2 = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
1206
+ const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message2}` : message2;
1207
1207
  if (detail && !line.includes(detail)) line += ` (${detail})`;
1208
1208
  if (level < 30) {
1209
1209
  const hidden = /* @__PURE__ */ new Set([
@@ -1337,9 +1337,9 @@ var CompanionStateHub = class {
1337
1337
  if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
1338
1338
  this.emitStatus();
1339
1339
  }
1340
- setDeviceError(message) {
1341
- if (this.deviceError === message) return;
1342
- this.deviceError = message;
1340
+ setDeviceError(message2) {
1341
+ if (this.deviceError === message2) return;
1342
+ this.deviceError = message2;
1343
1343
  this.emitStatus();
1344
1344
  }
1345
1345
  clearDeviceError() {
@@ -1960,7 +1960,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
1960
1960
  const probeClaude = deps.probeClaude ?? claudeOnPath;
1961
1961
  const probeCodex = deps.probeCodex ?? codexOnPath;
1962
1962
  const resolveBundled = deps.resolveBundled ?? ((runtime) => resolveBundledBinary(runtime));
1963
- const warn = deps.warn ?? ((message) => process.stderr.write(`${message}
1963
+ const warn = deps.warn ?? ((message2) => process.stderr.write(`${message2}
1964
1964
  `));
1965
1965
  const connected = [
1966
1966
  ...isClaudeCodeConnected(cfg) ? ["Claude Code"] : [],
@@ -2305,8 +2305,8 @@ function apiErrorCode(err) {
2305
2305
  function apiErrorMessage(err) {
2306
2306
  const body = err instanceof ApiError ? err.body : void 0;
2307
2307
  if (body !== null && typeof body === "object") {
2308
- const message = body.message;
2309
- if (typeof message === "string" && message.length > 0) return message;
2308
+ const message2 = body.message;
2309
+ if (typeof message2 === "string" && message2.length > 0) return message2;
2310
2310
  }
2311
2311
  return err instanceof Error ? err.message : String(err);
2312
2312
  }
@@ -2341,9 +2341,9 @@ function describeErrorBody(body) {
2341
2341
  }
2342
2342
  var CAUSE_TEXT_CAP = 200;
2343
2343
  function describeError(err) {
2344
- const message = err instanceof Error ? err.message : String(err);
2344
+ const message2 = err instanceof Error ? err.message : String(err);
2345
2345
  const cause = describeCause(err);
2346
- return cause === void 0 ? message : `${message}: ${cause}`;
2346
+ return cause === void 0 ? message2 : `${message2}: ${cause}`;
2347
2347
  }
2348
2348
  function describeCause(err) {
2349
2349
  if (!(err instanceof Error)) return void 0;
@@ -4378,15 +4378,15 @@ async function* decodeSdkStream(iter, ctx) {
4378
4378
  }
4379
4379
  } catch (err) {
4380
4380
  if (ctx.signal.aborted) throw err;
4381
- const message = err instanceof Error ? err.message : String(err);
4382
- const failure = mcpInventory.initReceived ? classifyErrorText(message) : classifyFailure(signals, { text: message });
4381
+ const message2 = err instanceof Error ? err.message : String(err);
4382
+ const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
4383
4383
  if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
4384
4384
  ok = false;
4385
4385
  if (failure) {
4386
4386
  resultReason = encodeFailureReason(failure);
4387
4387
  } else {
4388
4388
  resultReason = "session_start_failed";
4389
- if (ctx.startEvidence) ctx.startEvidence.thrown = message.slice(0, 200);
4389
+ if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
4390
4390
  }
4391
4391
  sawResult = true;
4392
4392
  }
@@ -5315,9 +5315,9 @@ function readSessionError(properties) {
5315
5315
  if (typeof err === "string") return err;
5316
5316
  const rec = asRecord(err);
5317
5317
  if (!rec) return "unknown";
5318
- const { name, message } = deepestError(rec);
5319
- if (message && name && !isGenericErrorName(name)) return `${name}: ${message}`;
5320
- if (message) return message;
5318
+ const { name, message: message2 } = deepestError(rec);
5319
+ if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
5320
+ if (message2) return message2;
5321
5321
  if (name) return name;
5322
5322
  if (typeof rec.name === "string") return rec.name;
5323
5323
  return "unknown";
@@ -5330,10 +5330,10 @@ function deepestError(node) {
5330
5330
  const best = {};
5331
5331
  let current = node;
5332
5332
  for (let depth = 0; current && depth < 8; depth++) {
5333
- const message = typeof current.message === "string" ? current.message : void 0;
5333
+ const message2 = typeof current.message === "string" ? current.message : void 0;
5334
5334
  const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
5335
- if (message) {
5336
- best.message = message;
5335
+ if (message2) {
5336
+ best.message = message2;
5337
5337
  if (name) best.name = name;
5338
5338
  } else if (name && !best.name) {
5339
5339
  best.name = name;
@@ -5924,9 +5924,9 @@ var echoedUserPart = (partId, messageID, text) => ({
5924
5924
  properties: { part: { type: "text", id: partId, messageID, text } }
5925
5925
  });
5926
5926
  var idle = () => ({ type: "session.idle", properties: {} });
5927
- var errored = (message) => ({
5927
+ var errored = (message2) => ({
5928
5928
  type: "session.error",
5929
- properties: { error: { message } }
5929
+ properties: { error: { message: message2 } }
5930
5930
  });
5931
5931
  var sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
5932
5932
  type: "session",
@@ -6361,8 +6361,8 @@ function readItemText(item) {
6361
6361
  function readItemMessage(item) {
6362
6362
  return str(item.message) ?? "";
6363
6363
  }
6364
- function isModelMetadataError(message) {
6365
- return message.includes("Defaulting to fallback metadata");
6364
+ function isModelMetadataError(message2) {
6365
+ return message2.includes("Defaulting to fallback metadata");
6366
6366
  }
6367
6367
  function readToolItem(item, eventType) {
6368
6368
  const type = str(item.type);
@@ -6597,11 +6597,11 @@ async function* decodeCodexStream(events, ctx) {
6597
6597
  if (!item) continue;
6598
6598
  const itemType = readItemType(item);
6599
6599
  if (itemType === "error") {
6600
- const message = readItemMessage(item);
6601
- if (isModelMetadataError(message)) {
6600
+ const message2 = readItemMessage(item);
6601
+ if (isModelMetadataError(message2)) {
6602
6602
  yield* flushInterim();
6603
6603
  ok = false;
6604
- reason = `model_unavailable:${message.slice(0, 200)}`;
6604
+ reason = `model_unavailable:${message2.slice(0, 200)}`;
6605
6605
  settled = true;
6606
6606
  break;
6607
6607
  }
@@ -6758,7 +6758,7 @@ var ENV_ENVELOPE_KEYS = [
6758
6758
  "CABANE_CONVERSATION_TITLE",
6759
6759
  "CABANE_HOST_ACCESS"
6760
6760
  ];
6761
- function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
6761
+ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
6762
6762
  const { policy, config } = req;
6763
6763
  const directory = req.local.cwd ?? "";
6764
6764
  const dialect = readCodexDialect(config.runtimeOptions);
@@ -6775,7 +6775,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
6775
6775
  skipGitRepoCheck: true,
6776
6776
  ...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
6777
6777
  baseInstructionsFile,
6778
- input: buildInput(req, promptRidesInput),
6778
+ input: withImages(buildInput(req, promptRidesInput), imagePaths),
6779
6779
  promptRidesInput,
6780
6780
  promptFingerprint,
6781
6781
  config: buildConfig(req, baseInstructionsFile),
@@ -6791,6 +6791,13 @@ function buildInput(req, promptRidesInput) {
6791
6791
 
6792
6792
  ${body}` : body;
6793
6793
  }
6794
+ function withImages(text, imagePaths) {
6795
+ if (imagePaths.length === 0) return text;
6796
+ return [
6797
+ { type: "text", text },
6798
+ ...imagePaths.map((path) => ({ type: "local_image", path }))
6799
+ ];
6800
+ }
6794
6801
  function buildConfig(req, baseInstructionsFile) {
6795
6802
  const mcp_servers = {};
6796
6803
  for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
@@ -7032,12 +7039,35 @@ function createCodexAdapter(deps = {}) {
7032
7039
  }
7033
7040
  }
7034
7041
  }
7042
+ let images = null;
7035
7043
  try {
7044
+ const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
7045
+ if (imageUrls.length > 0) {
7046
+ if (!deps.prepareImages) {
7047
+ deps.onWarn?.(
7048
+ "codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
7049
+ { images: imageUrls.length }
7050
+ );
7051
+ } else {
7052
+ try {
7053
+ images = await deps.prepareImages(imageUrls, signal);
7054
+ } catch (err) {
7055
+ if (!signal.aborted) {
7056
+ deps.onWarn?.(
7057
+ "codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
7058
+ { err: err instanceof Error ? err.message : String(err) }
7059
+ );
7060
+ }
7061
+ }
7062
+ }
7063
+ if (signal.aborted) return;
7064
+ }
7036
7065
  const spec = buildRunSpec2(
7037
7066
  req,
7038
7067
  resumeThreadId,
7039
7068
  instructions?.path ?? null,
7040
- threadPromptFingerprint
7069
+ threadPromptFingerprint,
7070
+ images?.paths ?? []
7041
7071
  );
7042
7072
  if (spec.model === null) {
7043
7073
  deps.onWarn?.("Codex is using its default model because no model was resolved", {
@@ -7066,6 +7096,15 @@ function createCodexAdapter(deps = {}) {
7066
7096
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
7067
7097
  });
7068
7098
  } finally {
7099
+ if (images) {
7100
+ try {
7101
+ await images.cleanup();
7102
+ } catch (err) {
7103
+ deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
7104
+ err: err instanceof Error ? err.message : String(err)
7105
+ });
7106
+ }
7107
+ }
7069
7108
  if (instructions) {
7070
7109
  try {
7071
7110
  await instructions.cleanup();
@@ -7132,14 +7171,14 @@ var turnCompleted = (usage) => ({
7132
7171
  type: "turn.completed",
7133
7172
  ...usage ? { usage } : {}
7134
7173
  });
7135
- var turnFailed = (message) => ({
7174
+ var turnFailed = (message2) => ({
7136
7175
  type: "turn.failed",
7137
- error: { message }
7176
+ error: { message: message2 }
7138
7177
  });
7139
- var fatalError = (message) => ({ type: "error", message });
7140
- var errorItem = (id, message) => ({
7178
+ var fatalError = (message2) => ({ type: "error", message: message2 });
7179
+ var errorItem = (id, message2) => ({
7141
7180
  type: "item.completed",
7142
- item: { id, type: "error", message }
7181
+ item: { id, type: "error", message: message2 }
7143
7182
  });
7144
7183
  var OP_SUCCESS = { ok: true, result: { source: "native" } };
7145
7184
  var OP_FAILURE = {
@@ -8058,7 +8097,7 @@ var ConnectorHealthStore = class {
8058
8097
  // src/dispatcher.ts
8059
8098
  import { randomUUID as randomUUID4 } from "crypto";
8060
8099
  import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
8061
- import { join as join14 } from "path";
8100
+ import { join as join15 } from "path";
8062
8101
 
8063
8102
  // src/runner/launch.ts
8064
8103
  import { createInterface } from "readline";
@@ -8124,8 +8163,8 @@ function unenforceable(policy) {
8124
8163
  deniedDomains: as === "deniedDomains" ? [host] : []
8125
8164
  });
8126
8165
  if (!check.success) {
8127
- const message = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
8128
- return `runners.policy.${field}.${i}: ${message}`;
8166
+ const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
8167
+ return `runners.policy.${field}.${i}: ${message2}`;
8129
8168
  }
8130
8169
  }
8131
8170
  }
@@ -8139,8 +8178,8 @@ function unenforceable(policy) {
8139
8178
  try {
8140
8179
  new RegExp(globToRegex(path));
8141
8180
  } catch (err) {
8142
- const message = err instanceof Error ? err.message : String(err);
8143
- return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message})`;
8181
+ const message2 = err instanceof Error ? err.message : String(err);
8182
+ return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
8144
8183
  }
8145
8184
  }
8146
8185
  }
@@ -8259,8 +8298,8 @@ async function reapTurnScopes(turnId) {
8259
8298
  });
8260
8299
  return { failures: [] };
8261
8300
  } catch (err) {
8262
- const message = err instanceof Error ? err.message : String(err);
8263
- return classifyStopError(message) === "absent" ? { failures: [] } : { failures: [message.slice(0, 200)] };
8301
+ const message2 = err instanceof Error ? err.message : String(err);
8302
+ return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
8264
8303
  }
8265
8304
  }
8266
8305
  function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
@@ -8276,13 +8315,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
8276
8315
  return false;
8277
8316
  }
8278
8317
  }
8279
- function classifyStopError(message) {
8318
+ function classifyStopError(message2) {
8280
8319
  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(
8281
- message
8320
+ message2
8282
8321
  )) {
8283
8322
  return "failure";
8284
8323
  }
8285
- if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message))
8324
+ if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
8286
8325
  return "absent";
8287
8326
  return "failure";
8288
8327
  }
@@ -8394,9 +8433,9 @@ function createTurnContainment(turnId, deps = {}) {
8394
8433
  timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
8395
8434
  });
8396
8435
  } catch (err) {
8397
- const message = err instanceof Error ? err.message : String(err);
8398
- if (classifyStopError(message) === "failure") {
8399
- result.failures.push({ target: unit, error: message.slice(0, 200) });
8436
+ const message2 = err instanceof Error ? err.message : String(err);
8437
+ if (classifyStopError(message2) === "failure") {
8438
+ result.failures.push({ target: unit, error: message2.slice(0, 200) });
8400
8439
  continue;
8401
8440
  }
8402
8441
  result.reaped.push(unit);
@@ -8801,8 +8840,8 @@ function runnerStdin(input) {
8801
8840
  })}
8802
8841
  `;
8803
8842
  }
8804
- function encodeLauncherMessage(message) {
8805
- return `${JSON.stringify(message)}
8843
+ function encodeLauncherMessage(message2) {
8844
+ return `${JSON.stringify(message2)}
8806
8845
  `;
8807
8846
  }
8808
8847
  function decodeRunnerEvent(line) {
@@ -9534,6 +9573,68 @@ async function writeCodexInstructionsFile(contents) {
9534
9573
  };
9535
9574
  }
9536
9575
 
9576
+ // src/codex-images.ts
9577
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
9578
+ import { tmpdir as tmpdir2 } from "os";
9579
+ import { join as join12 } from "path";
9580
+ var FETCH_TIMEOUT_MS = 3e4;
9581
+ var EXTENSIONS = {
9582
+ "image/png": "png",
9583
+ "image/jpeg": "jpg",
9584
+ "image/gif": "gif",
9585
+ "image/webp": "webp"
9586
+ };
9587
+ function createCodexImagePreparer(opts) {
9588
+ const doFetch = opts.fetch ?? fetch;
9589
+ const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
9590
+ return async (urls, signal) => {
9591
+ signal.throwIfAborted();
9592
+ const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
9593
+ const cleanup = async () => {
9594
+ try {
9595
+ await rm2(dir2, { recursive: true, force: true });
9596
+ } catch (err) {
9597
+ opts.warn(
9598
+ { dir: dir2, err: message(err) },
9599
+ "codex images: could not remove the scratch directory"
9600
+ );
9601
+ }
9602
+ };
9603
+ const prepareOne = async (url, index) => {
9604
+ try {
9605
+ const res = await doFetch(url, {
9606
+ signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
9607
+ });
9608
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
9609
+ const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
9610
+ const ext = EXTENSIONS[type];
9611
+ if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
9612
+ const bytes = new Uint8Array(await res.arrayBuffer());
9613
+ const path = join12(dir2, `image-${index + 1}.${ext}`);
9614
+ await writeFile2(path, bytes, { mode: 384 });
9615
+ return path;
9616
+ } catch (err) {
9617
+ if (!signal.aborted) {
9618
+ opts.warn(
9619
+ { image: index + 1, of: urls.length, err: message(err) },
9620
+ "codex images: an image could not be prepared; the turn runs without it"
9621
+ );
9622
+ }
9623
+ return null;
9624
+ }
9625
+ };
9626
+ const paths = await Promise.all(urls.map(prepareOne));
9627
+ if (signal.aborted) {
9628
+ await cleanup();
9629
+ signal.throwIfAborted();
9630
+ }
9631
+ return { paths: paths.filter((p) => p !== null), cleanup };
9632
+ };
9633
+ }
9634
+ function message(err) {
9635
+ return err instanceof Error ? err.message : String(err);
9636
+ }
9637
+
9537
9638
  // src/turn-seq-floor.ts
9538
9639
  var SeqFloorUnavailable = class extends Error {
9539
9640
  constructor(detail) {
@@ -9571,15 +9672,15 @@ function readOutboxFloor(read, turnId, log) {
9571
9672
  import { spawn as spawn5 } from "child_process";
9572
9673
  import { hostname } from "os";
9573
9674
  function prepareFailureCause(error) {
9574
- const message = error instanceof Error ? error.message : String(error);
9575
- if (/timed out/i.test(message)) return "prepare hook timed out";
9576
- if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message))
9675
+ const message2 = error instanceof Error ? error.message : String(error);
9676
+ if (/timed out/i.test(message2)) return "prepare hook timed out";
9677
+ if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
9577
9678
  return "prepare hook module missing; check the configured command and its installation for this device";
9578
- if (/not valid JSON|JSON must carry|produced no output/.test(message))
9679
+ if (/not valid JSON|JSON must carry|produced no output/.test(message2))
9579
9680
  return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
9580
- if (/ENOENT/.test(message)) return "prepare hook executable missing (ENOENT)";
9581
- if (/EACCES/.test(message)) return "prepare hook executable is not accessible (EACCES)";
9582
- const exit = message.match(/exited with code (\d+)/)?.[1];
9681
+ if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
9682
+ if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
9683
+ const exit = message2.match(/exited with code (\d+)/)?.[1];
9583
9684
  return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
9584
9685
  }
9585
9686
  async function runPrepareWithReporting(runner, hook, input, context, reportError) {
@@ -9631,15 +9732,15 @@ function reportFailure(config, body) {
9631
9732
 
9632
9733
  // src/prepared.ts
9633
9734
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
9634
- import { join as join12 } from "path";
9735
+ import { join as join13 } from "path";
9635
9736
  function dirFor(workspaceId) {
9636
- return join12(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9737
+ return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9637
9738
  }
9638
9739
  function conversationDir(workspaceId, conversationId) {
9639
- return join12(dirFor(workspaceId), encodeURIComponent(conversationId));
9740
+ return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
9640
9741
  }
9641
9742
  function pathFor2(workspaceId, conversationId, agentId) {
9642
- return join12(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9743
+ return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9643
9744
  }
9644
9745
  function readPrepared(workspaceId, conversationId, agentId) {
9645
9746
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -9671,9 +9772,9 @@ function clearPrepared(workspaceId, conversationId, agentId) {
9671
9772
 
9672
9773
  // src/transcript-writer.ts
9673
9774
  import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
9674
- import { basename, dirname as dirname6, join as join13 } from "path";
9775
+ import { basename, dirname as dirname6, join as join14 } from "path";
9675
9776
  function transcriptsDir() {
9676
- return join13(runnerStateDir(), "transcripts");
9777
+ return join14(runnerStateDir(), "transcripts");
9677
9778
  }
9678
9779
  var RETAIN = 200;
9679
9780
  var ANOMALY_RETAIN = 50;
@@ -9683,7 +9784,7 @@ var TranscriptWriter = class {
9683
9784
  onWarn;
9684
9785
  constructor(dir2, meta, onWarn) {
9685
9786
  this.onWarn = onWarn;
9686
- this.path = join13(dir2, fileName(meta));
9787
+ this.path = join14(dir2, fileName(meta));
9687
9788
  try {
9688
9789
  mkdirSync8(dir2, { recursive: true });
9689
9790
  try {
@@ -9712,10 +9813,10 @@ var TranscriptWriter = class {
9712
9813
  preserveAnomaly() {
9713
9814
  if (this.broken) return;
9714
9815
  try {
9715
- const dir2 = join13(dirname6(this.path), "anomalies");
9816
+ const dir2 = join14(dirname6(this.path), "anomalies");
9716
9817
  mkdirSync8(dir2, { recursive: true, mode: 448 });
9717
9818
  chmodSync4(dir2, 448);
9718
- const target = join13(dir2, basename(this.path));
9819
+ const target = join14(dir2, basename(this.path));
9719
9820
  copyFileSync(this.path, target);
9720
9821
  chmodSync4(target, 384);
9721
9822
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -9761,7 +9862,7 @@ function pruneOld(dir2, retain) {
9761
9862
  const drop = files.sort().slice(0, files.length - retain);
9762
9863
  for (const f of drop) {
9763
9864
  try {
9764
- rmSync6(join13(dir2, f), { force: true });
9865
+ rmSync6(join14(dir2, f), { force: true });
9765
9866
  } catch {
9766
9867
  }
9767
9868
  }
@@ -10332,16 +10433,16 @@ var TurnExecution = class {
10332
10433
  }
10333
10434
  gateTrigger() {
10334
10435
  const { payload, turnLog } = this;
10335
- const message = this.turnContext.message;
10336
- const isDispatchableTrigger = message.role === "user" || message.role === "agent" || message.role === "system";
10436
+ const message2 = this.turnContext.message;
10437
+ const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
10337
10438
  if (!isDispatchableTrigger) {
10338
- turnLog.debug({ role: message.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
10439
+ turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
10339
10440
  throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
10340
10441
  }
10341
10442
  this.supervisor.notifyStart({
10342
10443
  id: this.dispatchId,
10343
10444
  conversationId: payload.conversationId,
10344
- message: message.body
10445
+ message: message2.body
10345
10446
  });
10346
10447
  }
10347
10448
  async resolveSecrets() {
@@ -10692,6 +10793,10 @@ var TurnExecution = class {
10692
10793
  enabled: true,
10693
10794
  onWarn,
10694
10795
  writeInstructionsFile: writeCodexInstructionsFile,
10796
+ prepareImages: createCodexImagePreparer({
10797
+ turnId: this.turnId,
10798
+ warn: (meta, msg) => turnLog.warn(meta, msg)
10799
+ }),
10695
10800
  ...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
10696
10801
  })
10697
10802
  );
@@ -11171,7 +11276,7 @@ function checkoutState(cwd) {
11171
11276
  if (entries.length === 0) {
11172
11277
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
11173
11278
  }
11174
- const gitPath = join14(cwd, ".git");
11279
+ const gitPath = join15(cwd, ".git");
11175
11280
  if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
11176
11281
  let stat;
11177
11282
  try {
@@ -11179,7 +11284,7 @@ function checkoutState(cwd) {
11179
11284
  } catch (error) {
11180
11285
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
11181
11286
  }
11182
- if (stat.isDirectory() && !existsSync10(join14(gitPath, "HEAD")))
11287
+ if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
11183
11288
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
11184
11289
  return { ok: true, reason: "usable" };
11185
11290
  }
@@ -11372,7 +11477,7 @@ function createLocalProcessProvider(opts = {}) {
11372
11477
 
11373
11478
  // src/providers/docker.ts
11374
11479
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11375
- import { constants as constants2, tmpdir as tmpdir2 } from "os";
11480
+ import { constants as constants2, tmpdir as tmpdir3 } from "os";
11376
11481
  import {
11377
11482
  createReadStream,
11378
11483
  createWriteStream as createWriteStream2,
@@ -11385,7 +11490,7 @@ import {
11385
11490
  statSync as statSync3,
11386
11491
  writeFileSync as writeFileSync6
11387
11492
  } from "fs";
11388
- import { join as join17 } from "path";
11493
+ import { join as join18 } from "path";
11389
11494
  import { Writable } from "stream";
11390
11495
 
11391
11496
  // src/providers/docker-cli.ts
@@ -11579,7 +11684,7 @@ import {
11579
11684
  renameSync as renameSync4,
11580
11685
  rmSync as rmSync8
11581
11686
  } from "fs";
11582
- import { join as join15 } from "path";
11687
+ import { join as join16 } from "path";
11583
11688
  function routes(t) {
11584
11689
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
11585
11690
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -11625,7 +11730,7 @@ function publishStaged(from, to, move = atomicMove) {
11625
11730
  const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
11626
11731
  let entry = {};
11627
11732
  try {
11628
- entry = JSON.parse(readFileSync8(join15(from, name), "utf8"));
11733
+ entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
11629
11734
  } catch {
11630
11735
  }
11631
11736
  return { name, entry };
@@ -11638,7 +11743,7 @@ function publishStaged(from, to, move = atomicMove) {
11638
11743
  let published = 0;
11639
11744
  for (const { name } of staged) {
11640
11745
  try {
11641
- move(join15(from, name), join15(to, name));
11746
+ move(join16(from, name), join16(to, name));
11642
11747
  } catch {
11643
11748
  return { published, complete: false };
11644
11749
  }
@@ -11651,7 +11756,7 @@ function publishStaged(from, to, move = atomicMove) {
11651
11756
  // src/providers/runner-base.ts
11652
11757
  import { createHash as createHash3 } from "crypto";
11653
11758
  import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
11654
- import { dirname as dirname7, join as join16, sep } from "path";
11759
+ import { dirname as dirname7, join as join17, sep } from "path";
11655
11760
  import { fileURLToPath as fileURLToPath3 } from "url";
11656
11761
  var COMPANION_MOUNT = "/opt/cabane/companion";
11657
11762
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -11699,11 +11804,11 @@ async function ensureBaseImage(docker, image, log) {
11699
11804
  function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
11700
11805
  let dir2 = dirname7(moduleFile);
11701
11806
  for (let i = 0; i < 4; i++) {
11702
- if (existsSync12(join16(dir2, "dist", "cli.js")) && existsSync12(join16(dir2, "package.json"))) break;
11807
+ if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
11703
11808
  dir2 = dirname7(dir2);
11704
11809
  }
11705
- if (!existsSync12(join16(dir2, "dist", "cli.js"))) return null;
11706
- const sdk = join16(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11810
+ if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
11811
+ const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11707
11812
  if (!existsSync12(sdk)) return null;
11708
11813
  const root = realpathSync2(dir2);
11709
11814
  return realpathSync2(sdk).startsWith(root + sep) ? root : null;
@@ -11842,21 +11947,21 @@ function networkCreateError(stderr) {
11842
11947
  }
11843
11948
  function sessionArchivePath(deviceDir, attemptId) {
11844
11949
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
11845
- return join17(deviceDir, "attempts", attemptId, "session.tar");
11950
+ return join18(deviceDir, "attempts", attemptId, "session.tar");
11846
11951
  }
11847
11952
  function pruneOtherAttempts(deviceDir, pair, keep) {
11848
- const root = join17(deviceDir, "attempts");
11953
+ const root = join18(deviceDir, "attempts");
11849
11954
  if (!existsSync13(root)) return;
11850
11955
  for (const id of readdirSync4(root)) {
11851
11956
  if (id === keep || !UUID_RE.test(id)) continue;
11852
11957
  let pairOf = "";
11853
11958
  try {
11854
- pairOf = readFileSync9(join17(root, id, "pair"), "utf8");
11959
+ pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
11855
11960
  } catch {
11856
11961
  continue;
11857
11962
  }
11858
11963
  if (pairOf === `${pair.conversationId}/${pair.agentId}`) {
11859
- rmSync9(join17(root, id), { recursive: true, force: true });
11964
+ rmSync9(join18(root, id), { recursive: true, force: true });
11860
11965
  }
11861
11966
  }
11862
11967
  }
@@ -12056,11 +12161,11 @@ function createDockerProvider(opts) {
12056
12161
  await must(["network", "disconnect", "-f", other, name], "moving the container");
12057
12162
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
12058
12163
  };
12059
- const socketRoot = opts.socketRoot ?? join17(
12060
- process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir2(),
12164
+ const socketRoot = opts.socketRoot ?? join18(
12165
+ process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
12061
12166
  `cabane-runners-${opts.deviceKey}`
12062
12167
  );
12063
- const socketDir = (name) => join17(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
12168
+ const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
12064
12169
  const create = async (name, attempt) => {
12065
12170
  mkdirSync10(socketRoot, { recursive: true, mode: 448 });
12066
12171
  mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
@@ -12202,7 +12307,7 @@ function createDockerProvider(opts) {
12202
12307
  }
12203
12308
  };
12204
12309
  const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
12205
- const pendingPath = (name) => join17(opts.deviceDir, "runners-pending", `${name}.json`);
12310
+ const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
12206
12311
  const readPending = (name) => {
12207
12312
  try {
12208
12313
  const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
@@ -12216,12 +12321,12 @@ function createDockerProvider(opts) {
12216
12321
  rmSync9(pendingPath(name), { force: true });
12217
12322
  return;
12218
12323
  }
12219
- mkdirSync10(join17(opts.deviceDir, "runners-pending"), { recursive: true });
12324
+ mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
12220
12325
  writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
12221
12326
  };
12222
12327
  const keepSession = async (name, attempt, attemptId) => {
12223
12328
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
12224
- mkdirSync10(join17(archive, ".."), { recursive: true });
12329
+ mkdirSync10(join18(archive, ".."), { recursive: true });
12225
12330
  const partial = `${archive}.${process.pid}.partial`;
12226
12331
  const file = new CappedFile(partial, sessionCap);
12227
12332
  const r = await execIn(
@@ -12238,7 +12343,7 @@ function createDockerProvider(opts) {
12238
12343
  );
12239
12344
  if (r.code === 0 && !file.over) {
12240
12345
  renameSync5(partial, archive);
12241
- writeFileSync6(join17(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
12346
+ writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
12242
12347
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
12243
12348
  await writeMarker(name, attemptId);
12244
12349
  log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -12252,7 +12357,7 @@ function createDockerProvider(opts) {
12252
12357
  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"
12253
12358
  );
12254
12359
  };
12255
- const stagingDir = (name) => join17(opts.deviceDir, "runners-pending", `${name}.outbox`);
12360
+ const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
12256
12361
  const ensureRunning = async (name) => {
12257
12362
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
12258
12363
  if (seen.code !== 0) return false;
@@ -12260,7 +12365,7 @@ function createDockerProvider(opts) {
12260
12365
  return (await docker.run(["start", name])).code === 0;
12261
12366
  };
12262
12367
  const dropArchive = (attemptId) => {
12263
- rmSync9(join17(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
12368
+ rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
12264
12369
  recursive: true,
12265
12370
  force: true
12266
12371
  });
@@ -12329,7 +12434,7 @@ function createDockerProvider(opts) {
12329
12434
  if (seen.stdout.trim().split(" ")[0] !== "true")
12330
12435
  await must(["start", name], "starting the container");
12331
12436
  const git = await serveGitCredentials({
12332
- socketPath: join17(socketDir(name), "git.sock"),
12437
+ socketPath: join18(socketDir(name), "git.sock"),
12333
12438
  hosts: opts.gitHosts,
12334
12439
  fill: opts.gitFill,
12335
12440
  log
@@ -12478,15 +12583,15 @@ function createDockerProvider(opts) {
12478
12583
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
12479
12584
  if (transcripts) {
12480
12585
  const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
12481
- if (kept.length) mkdirSync10(join17(opts.hostStateDir, "transcripts"), { recursive: true });
12482
- for (const f of kept) writeFileSync6(join17(opts.hostStateDir, "transcripts", f.name), f.data);
12586
+ if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
12587
+ for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
12483
12588
  await removeIn(
12484
12589
  name,
12485
12590
  `${CONTAINER_STATE}/transcripts`,
12486
12591
  kept.map((f) => f.name)
12487
12592
  );
12488
12593
  }
12489
- const outboxRel = join17("outbox", encodeURIComponent(attempt.agentId));
12594
+ const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
12490
12595
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
12491
12596
  let unpublished = false;
12492
12597
  if (outbox) {
@@ -12512,7 +12617,7 @@ function createDockerProvider(opts) {
12512
12617
  const staged = stagingDir(name);
12513
12618
  mkdirSync10(staged, { recursive: true });
12514
12619
  writeFileSync6(
12515
- join17(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12620
+ join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12516
12621
  `${JSON.stringify(entry)}
12517
12622
  `
12518
12623
  );
@@ -12525,7 +12630,7 @@ function createDockerProvider(opts) {
12525
12630
  if (outbox.complete) {
12526
12631
  const published = publishStaged(
12527
12632
  stagingDir(name),
12528
- join17(opts.hostStateDir, outboxRel),
12633
+ join18(opts.hostStateDir, outboxRel),
12529
12634
  opts.moveFile
12530
12635
  );
12531
12636
  queued = published.published;
@@ -12740,8 +12845,8 @@ import {
12740
12845
  } from "fs";
12741
12846
  import { createRequire as createRequire2 } from "module";
12742
12847
  import { isIP } from "net";
12743
- import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir3, userInfo as userInfo2 } from "os";
12744
- import { dirname as dirname8, join as join18, sep as sep2 } from "path";
12848
+ import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12849
+ import { dirname as dirname8, join as join19, sep as sep2 } from "path";
12745
12850
  import { fileURLToPath as fileURLToPath4 } from "url";
12746
12851
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
12747
12852
  function accountHome() {
@@ -12951,7 +13056,7 @@ import {
12951
13056
  rmSync as rmSync11,
12952
13057
  writeFileSync as writeFileSync8
12953
13058
  } from "fs";
12954
- import { join as join19 } from "path";
13059
+ import { join as join20 } from "path";
12955
13060
  var MAX_ENTRIES = 2e3;
12956
13061
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
12957
13062
  var Outbox = class {
@@ -12964,10 +13069,10 @@ var Outbox = class {
12964
13069
  // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
12965
13070
  // cases route writes at the right tmpdir.
12966
13071
  dir() {
12967
- return join19(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
13072
+ return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
12968
13073
  }
12969
13074
  fileFor(turnId, seq) {
12970
- return join19(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
13075
+ return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
12971
13076
  }
12972
13077
  // Persist a commit for later draining. Atomic (temp file + rename) so a
12973
13078
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -13009,7 +13114,7 @@ var Outbox = class {
13009
13114
  const entries = [];
13010
13115
  for (const name of names) {
13011
13116
  if (!name.endsWith(".json")) continue;
13012
- const full = join19(dir2, name);
13117
+ const full = join20(dir2, name);
13013
13118
  try {
13014
13119
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
13015
13120
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -14650,8 +14755,8 @@ function errorCode(err) {
14650
14755
  function isRecoverableSocketError(err) {
14651
14756
  const code = errorCode(err);
14652
14757
  if (code && RECOVERABLE_CODES.has(code)) return true;
14653
- const message = err instanceof Error ? err.message : String(err);
14654
- return /\bEPIPE\b|\bECONNRESET\b/.test(message);
14758
+ const message2 = err instanceof Error ? err.message : String(err);
14759
+ return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
14655
14760
  }
14656
14761
  function installProcessSafetyNet(log, opts = {}) {
14657
14762
  const proc = opts.proc ?? process;
@@ -14666,26 +14771,26 @@ function installProcessSafetyNet(log, opts = {}) {
14666
14771
  );
14667
14772
  }
14668
14773
  function handleUncaught(log, err, origin) {
14669
- const message = err instanceof Error ? err.message : String(err);
14774
+ const message2 = err instanceof Error ? err.message : String(err);
14670
14775
  const code = errorCode(err);
14671
14776
  if (isRecoverableSocketError(err)) {
14672
14777
  log.debug(
14673
- { origin, code, err: message },
14778
+ { origin, code, err: message2 },
14674
14779
  "companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
14675
14780
  );
14676
14781
  return;
14677
14782
  }
14678
14783
  log.error(
14679
- { origin, code, err: message, stack: err instanceof Error ? err.stack : void 0 },
14784
+ { origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
14680
14785
  "An unexpected error occurred; the companion is still running."
14681
14786
  );
14682
14787
  }
14683
14788
 
14684
14789
  // src/crash-marker.ts
14685
14790
  import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
14686
- import { join as join20 } from "path";
14791
+ import { join as join21 } from "path";
14687
14792
  function crashMarkerPath() {
14688
- return join20(cabaneDir(), "last-error.json");
14793
+ return join21(cabaneDir(), "last-error.json");
14689
14794
  }
14690
14795
  function recordCrash(rec) {
14691
14796
  try {