@cabane/companion 0.6.142 → 0.6.144

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 +383 -181
  2. package/dist/runtime.js +336 -141
  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;
@@ -3309,14 +3309,11 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3309
3309
  // claude-code/codex it's the runtime's CUMULATIVE total summed across every model
3310
3310
  // request in the agentic turn, so it grows with the tool-call count and is NOT
3311
3311
  // "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
3312
- // read: the input the model saw on its FINAL request of the turn (uncached +
3313
- // cache, since cached tokens still occupy the window) — the number the composer
3314
- // gauge wants. `contextWindow` is the model's true window in tokens when the
3315
- // runtime reports it (claude-code's SDK does, per model) — a real denominator so
3316
- // the gauge can show a fraction. Both optional: a runtime that can't source a
3317
- // clean final-request figure (codex's cumulative-only usage) omits `contextTokens`
3318
- // and the gauge falls back to the raw count; `contextWindow` falls back to the
3319
- // model catalog.
3312
+ // read and `contextWindow` the window it is measured against, both as the
3313
+ // harness reported them (claude-code's `getContextUsage`, or its final
3314
+ // request's input and `modelUsage` window when that call gives nothing). A
3315
+ // runtime that can't source them (codex's cumulative-only usage) omits both,
3316
+ // and no surface shows a reading for that turn.
3320
3317
  //
3321
3318
  // CT601: two more optional carry-homes on the terminal result, alongside
3322
3319
  // `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
@@ -3339,7 +3336,25 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3339
3336
  cacheReadTokens: z6.number().optional(),
3340
3337
  cacheCreationTokens: z6.number().optional(),
3341
3338
  contextTokens: z6.number().optional(),
3342
- contextWindow: z6.number().optional()
3339
+ contextWindow: z6.number().optional(),
3340
+ // CT1751: the rest of the harness's own context reading, when it gave
3341
+ // one: where it will compact, its `/context` breakdown, and every
3342
+ // compaction it reported during the turn. Claude Code only.
3343
+ contextCompactAt: z6.number().optional(),
3344
+ contextCategories: z6.array(
3345
+ z6.object({
3346
+ name: z6.string(),
3347
+ tokens: z6.number(),
3348
+ kind: z6.enum(["used", "free", "buffer", "deferred"])
3349
+ })
3350
+ ).optional(),
3351
+ contextCompactions: z6.array(
3352
+ z6.object({
3353
+ trigger: z6.enum(["manual", "auto"]),
3354
+ preTokens: z6.number(),
3355
+ postTokens: z6.number().optional()
3356
+ })
3357
+ ).optional()
3343
3358
  }).optional(),
3344
3359
  resolvedModel: z6.string().optional(),
3345
3360
  resolvedConfig: z6.object({
@@ -4294,6 +4309,7 @@ async function* decodeSdkStream(iter, ctx) {
4294
4309
  let resultReason;
4295
4310
  let sawResult = false;
4296
4311
  let usage;
4312
+ const compactions = [];
4297
4313
  let lastRequestContextTokens;
4298
4314
  let resolvedModel;
4299
4315
  const signals = { sawRejectedLimit: false };
@@ -4319,6 +4335,9 @@ async function* decodeSdkStream(iter, ctx) {
4319
4335
  ...ctx.degraded ? { degraded: true } : {}
4320
4336
  };
4321
4337
  }
4338
+ } else if (msg.type === "system" && msg.subtype === "compact_boundary") {
4339
+ const compaction = readCompaction(msg);
4340
+ if (compaction) compactions.push(compaction);
4322
4341
  } else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
4323
4342
  if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
4324
4343
  const failure = classifyPreInitFrame(msg, signals);
@@ -4363,6 +4382,9 @@ async function* decodeSdkStream(iter, ctx) {
4363
4382
  usage.contextTokens = lastRequestContextTokens;
4364
4383
  const window = readContextWindow(msg, resolvedModel);
4365
4384
  if (window !== void 0) usage.contextWindow = window;
4385
+ if (compactions.length > 0) usage.contextCompactions = compactions;
4386
+ const reading = await readHarnessContext(ctx.readContextUsage);
4387
+ if (reading) Object.assign(usage, reading);
4366
4388
  }
4367
4389
  const isError = msg.is_error === true;
4368
4390
  if (msg.subtype === "success" && !isError) {
@@ -4378,15 +4400,15 @@ async function* decodeSdkStream(iter, ctx) {
4378
4400
  }
4379
4401
  } catch (err) {
4380
4402
  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 });
4403
+ const message2 = err instanceof Error ? err.message : String(err);
4404
+ const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
4383
4405
  if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
4384
4406
  ok = false;
4385
4407
  if (failure) {
4386
4408
  resultReason = encodeFailureReason(failure);
4387
4409
  } else {
4388
4410
  resultReason = "session_start_failed";
4389
- if (ctx.startEvidence) ctx.startEvidence.thrown = message.slice(0, 200);
4411
+ if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
4390
4412
  }
4391
4413
  sawResult = true;
4392
4414
  }
@@ -4514,6 +4536,58 @@ function readRequestContextTokens(msg) {
4514
4536
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
4515
4537
  return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
4516
4538
  }
4539
+ var CONTEXT_READING_TIMEOUT_MS = 2e3;
4540
+ async function readHarnessContext(read) {
4541
+ if (!read) return void 0;
4542
+ let timer;
4543
+ const timeout = new Promise((resolve2) => {
4544
+ timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
4545
+ });
4546
+ try {
4547
+ return parseContextUsage(await Promise.race([read(), timeout]));
4548
+ } catch {
4549
+ return void 0;
4550
+ } finally {
4551
+ clearTimeout(timer);
4552
+ }
4553
+ }
4554
+ var CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
4555
+ function parseContextUsage(response) {
4556
+ const r = response;
4557
+ if (!r || typeof r !== "object") return void 0;
4558
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
4559
+ const total = count(r.totalTokens);
4560
+ const window = count(r.maxTokens);
4561
+ if (total === void 0 || window === void 0 || window === 0) return void 0;
4562
+ const categories = [];
4563
+ for (const c of Array.isArray(r.categories) ? r.categories : []) {
4564
+ const row = c;
4565
+ const tokens = count(row?.tokens);
4566
+ if (typeof row?.name === "string" && tokens !== void 0 && typeof row.kind === "string" && CATEGORY_KINDS.has(row.kind)) {
4567
+ categories.push({ name: row.name, tokens, kind: row.kind });
4568
+ }
4569
+ }
4570
+ const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
4571
+ return {
4572
+ contextTokens: total,
4573
+ contextWindow: window,
4574
+ ...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
4575
+ ...categories.length > 0 ? { contextCategories: categories } : {}
4576
+ };
4577
+ }
4578
+ function readCompaction(msg) {
4579
+ const meta = msg.compact_metadata;
4580
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
4581
+ const preTokens = count(meta?.pre_tokens);
4582
+ if (!meta || preTokens === void 0) return void 0;
4583
+ if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
4584
+ const postTokens = count(meta.post_tokens);
4585
+ return {
4586
+ trigger: meta.trigger,
4587
+ preTokens,
4588
+ ...postTokens !== void 0 ? { postTokens } : {}
4589
+ };
4590
+ }
4517
4591
  function readContextWindow(msg, resolvedModel) {
4518
4592
  const modelUsage = msg.modelUsage;
4519
4593
  if (!modelUsage || typeof modelUsage !== "object") return void 0;
@@ -4531,7 +4605,7 @@ function readContextWindow(msg, resolvedModel) {
4531
4605
  }
4532
4606
 
4533
4607
  // packages/agent-runtime/src/claude-code/prompt-input.ts
4534
- function buildQueryPrompt(req) {
4608
+ function buildQueryPrompt(req, openUntil) {
4535
4609
  const content = req.content;
4536
4610
  return (async function* () {
4537
4611
  yield {
@@ -4540,6 +4614,7 @@ function buildQueryPrompt(req) {
4540
4614
  message: { role: "user", content },
4541
4615
  parent_tool_use_id: null
4542
4616
  };
4617
+ if (openUntil) await openUntil;
4543
4618
  })();
4544
4619
  }
4545
4620
 
@@ -4551,19 +4626,34 @@ function attachAbortController(options, signal) {
4551
4626
  else signal.addEventListener("abort", () => sdkAbort.abort(), { once: true });
4552
4627
  options.abortController = sdkAbort;
4553
4628
  }
4554
- function sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
4629
+ async function* sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
4555
4630
  attachAbortController(built.options, signal);
4556
- const prompt = buildQueryPrompt(req);
4557
- const iter = {
4558
- [Symbol.asyncIterator]: () => queryFn({ prompt, options: built.options })[Symbol.asyncIterator]()
4631
+ let release = () => {
4559
4632
  };
4560
- return decodeSdkStream(iter, {
4561
- signal,
4562
- cwd: req.local.cwd,
4563
- resumedSessionId: built.resume,
4564
- degraded,
4565
- ...startEvidence ? { startEvidence } : {}
4633
+ const held = new Promise((resolve2) => {
4634
+ release = resolve2;
4566
4635
  });
4636
+ const prompt = buildQueryPrompt(req, held);
4637
+ let active;
4638
+ const iter = {
4639
+ [Symbol.asyncIterator]: () => {
4640
+ const q = queryFn({ prompt, options: built.options });
4641
+ active = q;
4642
+ return q[Symbol.asyncIterator]();
4643
+ }
4644
+ };
4645
+ try {
4646
+ yield* decodeSdkStream(iter, {
4647
+ signal,
4648
+ readContextUsage: async () => typeof active?.getContextUsage === "function" ? active.getContextUsage({ detail: "summary" }) : void 0,
4649
+ cwd: req.local.cwd,
4650
+ resumedSessionId: built.resume,
4651
+ degraded,
4652
+ ...startEvidence ? { startEvidence } : {}
4653
+ });
4654
+ } finally {
4655
+ release();
4656
+ }
4567
4657
  }
4568
4658
  async function freshRequest(req, reason, hooks) {
4569
4659
  const recomposed = hooks?.fresh ? await hooks.fresh(reason) : null;
@@ -5315,9 +5405,9 @@ function readSessionError(properties) {
5315
5405
  if (typeof err === "string") return err;
5316
5406
  const rec = asRecord(err);
5317
5407
  if (!rec) return "unknown";
5318
- const { name, message } = deepestError(rec);
5319
- if (message && name && !isGenericErrorName(name)) return `${name}: ${message}`;
5320
- if (message) return message;
5408
+ const { name, message: message2 } = deepestError(rec);
5409
+ if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
5410
+ if (message2) return message2;
5321
5411
  if (name) return name;
5322
5412
  if (typeof rec.name === "string") return rec.name;
5323
5413
  return "unknown";
@@ -5330,10 +5420,10 @@ function deepestError(node) {
5330
5420
  const best = {};
5331
5421
  let current = node;
5332
5422
  for (let depth = 0; current && depth < 8; depth++) {
5333
- const message = typeof current.message === "string" ? current.message : void 0;
5423
+ const message2 = typeof current.message === "string" ? current.message : void 0;
5334
5424
  const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
5335
- if (message) {
5336
- best.message = message;
5425
+ if (message2) {
5426
+ best.message = message2;
5337
5427
  if (name) best.name = name;
5338
5428
  } else if (name && !best.name) {
5339
5429
  best.name = name;
@@ -5924,9 +6014,9 @@ var echoedUserPart = (partId, messageID, text) => ({
5924
6014
  properties: { part: { type: "text", id: partId, messageID, text } }
5925
6015
  });
5926
6016
  var idle = () => ({ type: "session.idle", properties: {} });
5927
- var errored = (message) => ({
6017
+ var errored = (message2) => ({
5928
6018
  type: "session.error",
5929
- properties: { error: { message } }
6019
+ properties: { error: { message: message2 } }
5930
6020
  });
5931
6021
  var sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
5932
6022
  type: "session",
@@ -6361,8 +6451,8 @@ function readItemText(item) {
6361
6451
  function readItemMessage(item) {
6362
6452
  return str(item.message) ?? "";
6363
6453
  }
6364
- function isModelMetadataError(message) {
6365
- return message.includes("Defaulting to fallback metadata");
6454
+ function isModelMetadataError(message2) {
6455
+ return message2.includes("Defaulting to fallback metadata");
6366
6456
  }
6367
6457
  function readToolItem(item, eventType) {
6368
6458
  const type = str(item.type);
@@ -6597,11 +6687,11 @@ async function* decodeCodexStream(events, ctx) {
6597
6687
  if (!item) continue;
6598
6688
  const itemType = readItemType(item);
6599
6689
  if (itemType === "error") {
6600
- const message = readItemMessage(item);
6601
- if (isModelMetadataError(message)) {
6690
+ const message2 = readItemMessage(item);
6691
+ if (isModelMetadataError(message2)) {
6602
6692
  yield* flushInterim();
6603
6693
  ok = false;
6604
- reason = `model_unavailable:${message.slice(0, 200)}`;
6694
+ reason = `model_unavailable:${message2.slice(0, 200)}`;
6605
6695
  settled = true;
6606
6696
  break;
6607
6697
  }
@@ -6758,7 +6848,7 @@ var ENV_ENVELOPE_KEYS = [
6758
6848
  "CABANE_CONVERSATION_TITLE",
6759
6849
  "CABANE_HOST_ACCESS"
6760
6850
  ];
6761
- function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
6851
+ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
6762
6852
  const { policy, config } = req;
6763
6853
  const directory = req.local.cwd ?? "";
6764
6854
  const dialect = readCodexDialect(config.runtimeOptions);
@@ -6775,7 +6865,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
6775
6865
  skipGitRepoCheck: true,
6776
6866
  ...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
6777
6867
  baseInstructionsFile,
6778
- input: buildInput(req, promptRidesInput),
6868
+ input: withImages(buildInput(req, promptRidesInput), imagePaths),
6779
6869
  promptRidesInput,
6780
6870
  promptFingerprint,
6781
6871
  config: buildConfig(req, baseInstructionsFile),
@@ -6791,6 +6881,13 @@ function buildInput(req, promptRidesInput) {
6791
6881
 
6792
6882
  ${body}` : body;
6793
6883
  }
6884
+ function withImages(text, imagePaths) {
6885
+ if (imagePaths.length === 0) return text;
6886
+ return [
6887
+ { type: "text", text },
6888
+ ...imagePaths.map((path) => ({ type: "local_image", path }))
6889
+ ];
6890
+ }
6794
6891
  function buildConfig(req, baseInstructionsFile) {
6795
6892
  const mcp_servers = {};
6796
6893
  for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
@@ -7032,12 +7129,35 @@ function createCodexAdapter(deps = {}) {
7032
7129
  }
7033
7130
  }
7034
7131
  }
7132
+ let images = null;
7035
7133
  try {
7134
+ const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
7135
+ if (imageUrls.length > 0) {
7136
+ if (!deps.prepareImages) {
7137
+ deps.onWarn?.(
7138
+ "codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
7139
+ { images: imageUrls.length }
7140
+ );
7141
+ } else {
7142
+ try {
7143
+ images = await deps.prepareImages(imageUrls, signal);
7144
+ } catch (err) {
7145
+ if (!signal.aborted) {
7146
+ deps.onWarn?.(
7147
+ "codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
7148
+ { err: err instanceof Error ? err.message : String(err) }
7149
+ );
7150
+ }
7151
+ }
7152
+ }
7153
+ if (signal.aborted) return;
7154
+ }
7036
7155
  const spec = buildRunSpec2(
7037
7156
  req,
7038
7157
  resumeThreadId,
7039
7158
  instructions?.path ?? null,
7040
- threadPromptFingerprint
7159
+ threadPromptFingerprint,
7160
+ images?.paths ?? []
7041
7161
  );
7042
7162
  if (spec.model === null) {
7043
7163
  deps.onWarn?.("Codex is using its default model because no model was resolved", {
@@ -7066,6 +7186,15 @@ function createCodexAdapter(deps = {}) {
7066
7186
  resolvedReasoningEffort: spec.modelReasoningEffort ?? null
7067
7187
  });
7068
7188
  } finally {
7189
+ if (images) {
7190
+ try {
7191
+ await images.cleanup();
7192
+ } catch (err) {
7193
+ deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
7194
+ err: err instanceof Error ? err.message : String(err)
7195
+ });
7196
+ }
7197
+ }
7069
7198
  if (instructions) {
7070
7199
  try {
7071
7200
  await instructions.cleanup();
@@ -7132,14 +7261,14 @@ var turnCompleted = (usage) => ({
7132
7261
  type: "turn.completed",
7133
7262
  ...usage ? { usage } : {}
7134
7263
  });
7135
- var turnFailed = (message) => ({
7264
+ var turnFailed = (message2) => ({
7136
7265
  type: "turn.failed",
7137
- error: { message }
7266
+ error: { message: message2 }
7138
7267
  });
7139
- var fatalError = (message) => ({ type: "error", message });
7140
- var errorItem = (id, message) => ({
7268
+ var fatalError = (message2) => ({ type: "error", message: message2 });
7269
+ var errorItem = (id, message2) => ({
7141
7270
  type: "item.completed",
7142
- item: { id, type: "error", message }
7271
+ item: { id, type: "error", message: message2 }
7143
7272
  });
7144
7273
  var OP_SUCCESS = { ok: true, result: { source: "native" } };
7145
7274
  var OP_FAILURE = {
@@ -8058,7 +8187,7 @@ var ConnectorHealthStore = class {
8058
8187
  // src/dispatcher.ts
8059
8188
  import { randomUUID as randomUUID4 } from "crypto";
8060
8189
  import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
8061
- import { join as join14 } from "path";
8190
+ import { join as join15 } from "path";
8062
8191
 
8063
8192
  // src/runner/launch.ts
8064
8193
  import { createInterface } from "readline";
@@ -8124,8 +8253,8 @@ function unenforceable(policy) {
8124
8253
  deniedDomains: as === "deniedDomains" ? [host] : []
8125
8254
  });
8126
8255
  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}`;
8256
+ const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
8257
+ return `runners.policy.${field}.${i}: ${message2}`;
8129
8258
  }
8130
8259
  }
8131
8260
  }
@@ -8139,8 +8268,8 @@ function unenforceable(policy) {
8139
8268
  try {
8140
8269
  new RegExp(globToRegex(path));
8141
8270
  } 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})`;
8271
+ const message2 = err instanceof Error ? err.message : String(err);
8272
+ return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
8144
8273
  }
8145
8274
  }
8146
8275
  }
@@ -8259,8 +8388,8 @@ async function reapTurnScopes(turnId) {
8259
8388
  });
8260
8389
  return { failures: [] };
8261
8390
  } catch (err) {
8262
- const message = err instanceof Error ? err.message : String(err);
8263
- return classifyStopError(message) === "absent" ? { failures: [] } : { failures: [message.slice(0, 200)] };
8391
+ const message2 = err instanceof Error ? err.message : String(err);
8392
+ return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
8264
8393
  }
8265
8394
  }
8266
8395
  function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
@@ -8276,13 +8405,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
8276
8405
  return false;
8277
8406
  }
8278
8407
  }
8279
- function classifyStopError(message) {
8408
+ function classifyStopError(message2) {
8280
8409
  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
8410
+ message2
8282
8411
  )) {
8283
8412
  return "failure";
8284
8413
  }
8285
- if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message))
8414
+ if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
8286
8415
  return "absent";
8287
8416
  return "failure";
8288
8417
  }
@@ -8394,9 +8523,9 @@ function createTurnContainment(turnId, deps = {}) {
8394
8523
  timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
8395
8524
  });
8396
8525
  } 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) });
8526
+ const message2 = err instanceof Error ? err.message : String(err);
8527
+ if (classifyStopError(message2) === "failure") {
8528
+ result.failures.push({ target: unit, error: message2.slice(0, 200) });
8400
8529
  continue;
8401
8530
  }
8402
8531
  result.reaped.push(unit);
@@ -8801,8 +8930,8 @@ function runnerStdin(input) {
8801
8930
  })}
8802
8931
  `;
8803
8932
  }
8804
- function encodeLauncherMessage(message) {
8805
- return `${JSON.stringify(message)}
8933
+ function encodeLauncherMessage(message2) {
8934
+ return `${JSON.stringify(message2)}
8806
8935
  `;
8807
8936
  }
8808
8937
  function decodeRunnerEvent(line) {
@@ -9534,6 +9663,68 @@ async function writeCodexInstructionsFile(contents) {
9534
9663
  };
9535
9664
  }
9536
9665
 
9666
+ // src/codex-images.ts
9667
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
9668
+ import { tmpdir as tmpdir2 } from "os";
9669
+ import { join as join12 } from "path";
9670
+ var FETCH_TIMEOUT_MS = 3e4;
9671
+ var EXTENSIONS = {
9672
+ "image/png": "png",
9673
+ "image/jpeg": "jpg",
9674
+ "image/gif": "gif",
9675
+ "image/webp": "webp"
9676
+ };
9677
+ function createCodexImagePreparer(opts) {
9678
+ const doFetch = opts.fetch ?? fetch;
9679
+ const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
9680
+ return async (urls, signal) => {
9681
+ signal.throwIfAborted();
9682
+ const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
9683
+ const cleanup = async () => {
9684
+ try {
9685
+ await rm2(dir2, { recursive: true, force: true });
9686
+ } catch (err) {
9687
+ opts.warn(
9688
+ { dir: dir2, err: message(err) },
9689
+ "codex images: could not remove the scratch directory"
9690
+ );
9691
+ }
9692
+ };
9693
+ const prepareOne = async (url, index) => {
9694
+ try {
9695
+ const res = await doFetch(url, {
9696
+ signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
9697
+ });
9698
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
9699
+ const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
9700
+ const ext = EXTENSIONS[type];
9701
+ if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
9702
+ const bytes = new Uint8Array(await res.arrayBuffer());
9703
+ const path = join12(dir2, `image-${index + 1}.${ext}`);
9704
+ await writeFile2(path, bytes, { mode: 384 });
9705
+ return path;
9706
+ } catch (err) {
9707
+ if (!signal.aborted) {
9708
+ opts.warn(
9709
+ { image: index + 1, of: urls.length, err: message(err) },
9710
+ "codex images: an image could not be prepared; the turn runs without it"
9711
+ );
9712
+ }
9713
+ return null;
9714
+ }
9715
+ };
9716
+ const paths = await Promise.all(urls.map(prepareOne));
9717
+ if (signal.aborted) {
9718
+ await cleanup();
9719
+ signal.throwIfAborted();
9720
+ }
9721
+ return { paths: paths.filter((p) => p !== null), cleanup };
9722
+ };
9723
+ }
9724
+ function message(err) {
9725
+ return err instanceof Error ? err.message : String(err);
9726
+ }
9727
+
9537
9728
  // src/turn-seq-floor.ts
9538
9729
  var SeqFloorUnavailable = class extends Error {
9539
9730
  constructor(detail) {
@@ -9571,15 +9762,15 @@ function readOutboxFloor(read, turnId, log) {
9571
9762
  import { spawn as spawn5 } from "child_process";
9572
9763
  import { hostname } from "os";
9573
9764
  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))
9765
+ const message2 = error instanceof Error ? error.message : String(error);
9766
+ if (/timed out/i.test(message2)) return "prepare hook timed out";
9767
+ if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
9577
9768
  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))
9769
+ if (/not valid JSON|JSON must carry|produced no output/.test(message2))
9579
9770
  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];
9771
+ if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
9772
+ if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
9773
+ const exit = message2.match(/exited with code (\d+)/)?.[1];
9583
9774
  return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
9584
9775
  }
9585
9776
  async function runPrepareWithReporting(runner, hook, input, context, reportError) {
@@ -9631,15 +9822,15 @@ function reportFailure(config, body) {
9631
9822
 
9632
9823
  // src/prepared.ts
9633
9824
  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";
9825
+ import { join as join13 } from "path";
9635
9826
  function dirFor(workspaceId) {
9636
- return join12(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9827
+ return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9637
9828
  }
9638
9829
  function conversationDir(workspaceId, conversationId) {
9639
- return join12(dirFor(workspaceId), encodeURIComponent(conversationId));
9830
+ return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
9640
9831
  }
9641
9832
  function pathFor2(workspaceId, conversationId, agentId) {
9642
- return join12(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9833
+ return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9643
9834
  }
9644
9835
  function readPrepared(workspaceId, conversationId, agentId) {
9645
9836
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -9671,9 +9862,9 @@ function clearPrepared(workspaceId, conversationId, agentId) {
9671
9862
 
9672
9863
  // src/transcript-writer.ts
9673
9864
  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";
9865
+ import { basename, dirname as dirname6, join as join14 } from "path";
9675
9866
  function transcriptsDir() {
9676
- return join13(runnerStateDir(), "transcripts");
9867
+ return join14(runnerStateDir(), "transcripts");
9677
9868
  }
9678
9869
  var RETAIN = 200;
9679
9870
  var ANOMALY_RETAIN = 50;
@@ -9683,7 +9874,7 @@ var TranscriptWriter = class {
9683
9874
  onWarn;
9684
9875
  constructor(dir2, meta, onWarn) {
9685
9876
  this.onWarn = onWarn;
9686
- this.path = join13(dir2, fileName(meta));
9877
+ this.path = join14(dir2, fileName(meta));
9687
9878
  try {
9688
9879
  mkdirSync8(dir2, { recursive: true });
9689
9880
  try {
@@ -9712,10 +9903,10 @@ var TranscriptWriter = class {
9712
9903
  preserveAnomaly() {
9713
9904
  if (this.broken) return;
9714
9905
  try {
9715
- const dir2 = join13(dirname6(this.path), "anomalies");
9906
+ const dir2 = join14(dirname6(this.path), "anomalies");
9716
9907
  mkdirSync8(dir2, { recursive: true, mode: 448 });
9717
9908
  chmodSync4(dir2, 448);
9718
- const target = join13(dir2, basename(this.path));
9909
+ const target = join14(dir2, basename(this.path));
9719
9910
  copyFileSync(this.path, target);
9720
9911
  chmodSync4(target, 384);
9721
9912
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -9761,7 +9952,7 @@ function pruneOld(dir2, retain) {
9761
9952
  const drop = files.sort().slice(0, files.length - retain);
9762
9953
  for (const f of drop) {
9763
9954
  try {
9764
- rmSync6(join13(dir2, f), { force: true });
9955
+ rmSync6(join14(dir2, f), { force: true });
9765
9956
  } catch {
9766
9957
  }
9767
9958
  }
@@ -10332,16 +10523,16 @@ var TurnExecution = class {
10332
10523
  }
10333
10524
  gateTrigger() {
10334
10525
  const { payload, turnLog } = this;
10335
- const message = this.turnContext.message;
10336
- const isDispatchableTrigger = message.role === "user" || message.role === "agent" || message.role === "system";
10526
+ const message2 = this.turnContext.message;
10527
+ const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
10337
10528
  if (!isDispatchableTrigger) {
10338
- turnLog.debug({ role: message.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
10529
+ turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
10339
10530
  throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
10340
10531
  }
10341
10532
  this.supervisor.notifyStart({
10342
10533
  id: this.dispatchId,
10343
10534
  conversationId: payload.conversationId,
10344
- message: message.body
10535
+ message: message2.body
10345
10536
  });
10346
10537
  }
10347
10538
  async resolveSecrets() {
@@ -10692,6 +10883,10 @@ var TurnExecution = class {
10692
10883
  enabled: true,
10693
10884
  onWarn,
10694
10885
  writeInstructionsFile: writeCodexInstructionsFile,
10886
+ prepareImages: createCodexImagePreparer({
10887
+ turnId: this.turnId,
10888
+ warn: (meta, msg) => turnLog.warn(meta, msg)
10889
+ }),
10695
10890
  ...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
10696
10891
  })
10697
10892
  );
@@ -11171,7 +11366,7 @@ function checkoutState(cwd) {
11171
11366
  if (entries.length === 0) {
11172
11367
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
11173
11368
  }
11174
- const gitPath = join14(cwd, ".git");
11369
+ const gitPath = join15(cwd, ".git");
11175
11370
  if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
11176
11371
  let stat;
11177
11372
  try {
@@ -11179,7 +11374,7 @@ function checkoutState(cwd) {
11179
11374
  } catch (error) {
11180
11375
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
11181
11376
  }
11182
- if (stat.isDirectory() && !existsSync10(join14(gitPath, "HEAD")))
11377
+ if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
11183
11378
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
11184
11379
  return { ok: true, reason: "usable" };
11185
11380
  }
@@ -11372,7 +11567,7 @@ function createLocalProcessProvider(opts = {}) {
11372
11567
 
11373
11568
  // src/providers/docker.ts
11374
11569
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11375
- import { constants as constants2, tmpdir as tmpdir2 } from "os";
11570
+ import { constants as constants2, tmpdir as tmpdir3 } from "os";
11376
11571
  import {
11377
11572
  createReadStream,
11378
11573
  createWriteStream as createWriteStream2,
@@ -11385,7 +11580,7 @@ import {
11385
11580
  statSync as statSync3,
11386
11581
  writeFileSync as writeFileSync6
11387
11582
  } from "fs";
11388
- import { join as join17 } from "path";
11583
+ import { join as join18 } from "path";
11389
11584
  import { Writable } from "stream";
11390
11585
 
11391
11586
  // src/providers/docker-cli.ts
@@ -11579,7 +11774,7 @@ import {
11579
11774
  renameSync as renameSync4,
11580
11775
  rmSync as rmSync8
11581
11776
  } from "fs";
11582
- import { join as join15 } from "path";
11777
+ import { join as join16 } from "path";
11583
11778
  function routes(t) {
11584
11779
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
11585
11780
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -11625,7 +11820,7 @@ function publishStaged(from, to, move = atomicMove) {
11625
11820
  const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
11626
11821
  let entry = {};
11627
11822
  try {
11628
- entry = JSON.parse(readFileSync8(join15(from, name), "utf8"));
11823
+ entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
11629
11824
  } catch {
11630
11825
  }
11631
11826
  return { name, entry };
@@ -11638,7 +11833,7 @@ function publishStaged(from, to, move = atomicMove) {
11638
11833
  let published = 0;
11639
11834
  for (const { name } of staged) {
11640
11835
  try {
11641
- move(join15(from, name), join15(to, name));
11836
+ move(join16(from, name), join16(to, name));
11642
11837
  } catch {
11643
11838
  return { published, complete: false };
11644
11839
  }
@@ -11651,7 +11846,7 @@ function publishStaged(from, to, move = atomicMove) {
11651
11846
  // src/providers/runner-base.ts
11652
11847
  import { createHash as createHash3 } from "crypto";
11653
11848
  import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
11654
- import { dirname as dirname7, join as join16, sep } from "path";
11849
+ import { dirname as dirname7, join as join17, sep } from "path";
11655
11850
  import { fileURLToPath as fileURLToPath3 } from "url";
11656
11851
  var COMPANION_MOUNT = "/opt/cabane/companion";
11657
11852
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -11699,11 +11894,11 @@ async function ensureBaseImage(docker, image, log) {
11699
11894
  function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
11700
11895
  let dir2 = dirname7(moduleFile);
11701
11896
  for (let i = 0; i < 4; i++) {
11702
- if (existsSync12(join16(dir2, "dist", "cli.js")) && existsSync12(join16(dir2, "package.json"))) break;
11897
+ if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
11703
11898
  dir2 = dirname7(dir2);
11704
11899
  }
11705
- if (!existsSync12(join16(dir2, "dist", "cli.js"))) return null;
11706
- const sdk = join16(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11900
+ if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
11901
+ const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11707
11902
  if (!existsSync12(sdk)) return null;
11708
11903
  const root = realpathSync2(dir2);
11709
11904
  return realpathSync2(sdk).startsWith(root + sep) ? root : null;
@@ -11842,21 +12037,21 @@ function networkCreateError(stderr) {
11842
12037
  }
11843
12038
  function sessionArchivePath(deviceDir, attemptId) {
11844
12039
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
11845
- return join17(deviceDir, "attempts", attemptId, "session.tar");
12040
+ return join18(deviceDir, "attempts", attemptId, "session.tar");
11846
12041
  }
11847
12042
  function pruneOtherAttempts(deviceDir, pair, keep) {
11848
- const root = join17(deviceDir, "attempts");
12043
+ const root = join18(deviceDir, "attempts");
11849
12044
  if (!existsSync13(root)) return;
11850
12045
  for (const id of readdirSync4(root)) {
11851
12046
  if (id === keep || !UUID_RE.test(id)) continue;
11852
12047
  let pairOf = "";
11853
12048
  try {
11854
- pairOf = readFileSync9(join17(root, id, "pair"), "utf8");
12049
+ pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
11855
12050
  } catch {
11856
12051
  continue;
11857
12052
  }
11858
12053
  if (pairOf === `${pair.conversationId}/${pair.agentId}`) {
11859
- rmSync9(join17(root, id), { recursive: true, force: true });
12054
+ rmSync9(join18(root, id), { recursive: true, force: true });
11860
12055
  }
11861
12056
  }
11862
12057
  }
@@ -12056,11 +12251,11 @@ function createDockerProvider(opts) {
12056
12251
  await must(["network", "disconnect", "-f", other, name], "moving the container");
12057
12252
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
12058
12253
  };
12059
- const socketRoot = opts.socketRoot ?? join17(
12060
- process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir2(),
12254
+ const socketRoot = opts.socketRoot ?? join18(
12255
+ process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
12061
12256
  `cabane-runners-${opts.deviceKey}`
12062
12257
  );
12063
- const socketDir = (name) => join17(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
12258
+ const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
12064
12259
  const create = async (name, attempt) => {
12065
12260
  mkdirSync10(socketRoot, { recursive: true, mode: 448 });
12066
12261
  mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
@@ -12202,7 +12397,7 @@ function createDockerProvider(opts) {
12202
12397
  }
12203
12398
  };
12204
12399
  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`);
12400
+ const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
12206
12401
  const readPending = (name) => {
12207
12402
  try {
12208
12403
  const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
@@ -12216,12 +12411,12 @@ function createDockerProvider(opts) {
12216
12411
  rmSync9(pendingPath(name), { force: true });
12217
12412
  return;
12218
12413
  }
12219
- mkdirSync10(join17(opts.deviceDir, "runners-pending"), { recursive: true });
12414
+ mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
12220
12415
  writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
12221
12416
  };
12222
12417
  const keepSession = async (name, attempt, attemptId) => {
12223
12418
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
12224
- mkdirSync10(join17(archive, ".."), { recursive: true });
12419
+ mkdirSync10(join18(archive, ".."), { recursive: true });
12225
12420
  const partial = `${archive}.${process.pid}.partial`;
12226
12421
  const file = new CappedFile(partial, sessionCap);
12227
12422
  const r = await execIn(
@@ -12238,7 +12433,7 @@ function createDockerProvider(opts) {
12238
12433
  );
12239
12434
  if (r.code === 0 && !file.over) {
12240
12435
  renameSync5(partial, archive);
12241
- writeFileSync6(join17(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
12436
+ writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
12242
12437
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
12243
12438
  await writeMarker(name, attemptId);
12244
12439
  log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -12252,7 +12447,7 @@ function createDockerProvider(opts) {
12252
12447
  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
12448
  );
12254
12449
  };
12255
- const stagingDir = (name) => join17(opts.deviceDir, "runners-pending", `${name}.outbox`);
12450
+ const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
12256
12451
  const ensureRunning = async (name) => {
12257
12452
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
12258
12453
  if (seen.code !== 0) return false;
@@ -12260,7 +12455,7 @@ function createDockerProvider(opts) {
12260
12455
  return (await docker.run(["start", name])).code === 0;
12261
12456
  };
12262
12457
  const dropArchive = (attemptId) => {
12263
- rmSync9(join17(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
12458
+ rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
12264
12459
  recursive: true,
12265
12460
  force: true
12266
12461
  });
@@ -12329,7 +12524,7 @@ function createDockerProvider(opts) {
12329
12524
  if (seen.stdout.trim().split(" ")[0] !== "true")
12330
12525
  await must(["start", name], "starting the container");
12331
12526
  const git = await serveGitCredentials({
12332
- socketPath: join17(socketDir(name), "git.sock"),
12527
+ socketPath: join18(socketDir(name), "git.sock"),
12333
12528
  hosts: opts.gitHosts,
12334
12529
  fill: opts.gitFill,
12335
12530
  log
@@ -12478,15 +12673,15 @@ function createDockerProvider(opts) {
12478
12673
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
12479
12674
  if (transcripts) {
12480
12675
  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);
12676
+ if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
12677
+ for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
12483
12678
  await removeIn(
12484
12679
  name,
12485
12680
  `${CONTAINER_STATE}/transcripts`,
12486
12681
  kept.map((f) => f.name)
12487
12682
  );
12488
12683
  }
12489
- const outboxRel = join17("outbox", encodeURIComponent(attempt.agentId));
12684
+ const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
12490
12685
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
12491
12686
  let unpublished = false;
12492
12687
  if (outbox) {
@@ -12512,7 +12707,7 @@ function createDockerProvider(opts) {
12512
12707
  const staged = stagingDir(name);
12513
12708
  mkdirSync10(staged, { recursive: true });
12514
12709
  writeFileSync6(
12515
- join17(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12710
+ join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12516
12711
  `${JSON.stringify(entry)}
12517
12712
  `
12518
12713
  );
@@ -12525,7 +12720,7 @@ function createDockerProvider(opts) {
12525
12720
  if (outbox.complete) {
12526
12721
  const published = publishStaged(
12527
12722
  stagingDir(name),
12528
- join17(opts.hostStateDir, outboxRel),
12723
+ join18(opts.hostStateDir, outboxRel),
12529
12724
  opts.moveFile
12530
12725
  );
12531
12726
  queued = published.published;
@@ -12740,8 +12935,8 @@ import {
12740
12935
  } from "fs";
12741
12936
  import { createRequire as createRequire2 } from "module";
12742
12937
  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";
12938
+ import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12939
+ import { dirname as dirname8, join as join19, sep as sep2 } from "path";
12745
12940
  import { fileURLToPath as fileURLToPath4 } from "url";
12746
12941
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
12747
12942
  function accountHome() {
@@ -12951,7 +13146,7 @@ import {
12951
13146
  rmSync as rmSync11,
12952
13147
  writeFileSync as writeFileSync8
12953
13148
  } from "fs";
12954
- import { join as join19 } from "path";
13149
+ import { join as join20 } from "path";
12955
13150
  var MAX_ENTRIES = 2e3;
12956
13151
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
12957
13152
  var Outbox = class {
@@ -12964,10 +13159,10 @@ var Outbox = class {
12964
13159
  // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
12965
13160
  // cases route writes at the right tmpdir.
12966
13161
  dir() {
12967
- return join19(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
13162
+ return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
12968
13163
  }
12969
13164
  fileFor(turnId, seq) {
12970
- return join19(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
13165
+ return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
12971
13166
  }
12972
13167
  // Persist a commit for later draining. Atomic (temp file + rename) so a
12973
13168
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -13009,7 +13204,7 @@ var Outbox = class {
13009
13204
  const entries = [];
13010
13205
  for (const name of names) {
13011
13206
  if (!name.endsWith(".json")) continue;
13012
- const full = join19(dir2, name);
13207
+ const full = join20(dir2, name);
13013
13208
  try {
13014
13209
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
13015
13210
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -14650,8 +14845,8 @@ function errorCode(err) {
14650
14845
  function isRecoverableSocketError(err) {
14651
14846
  const code = errorCode(err);
14652
14847
  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);
14848
+ const message2 = err instanceof Error ? err.message : String(err);
14849
+ return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
14655
14850
  }
14656
14851
  function installProcessSafetyNet(log, opts = {}) {
14657
14852
  const proc = opts.proc ?? process;
@@ -14666,26 +14861,26 @@ function installProcessSafetyNet(log, opts = {}) {
14666
14861
  );
14667
14862
  }
14668
14863
  function handleUncaught(log, err, origin) {
14669
- const message = err instanceof Error ? err.message : String(err);
14864
+ const message2 = err instanceof Error ? err.message : String(err);
14670
14865
  const code = errorCode(err);
14671
14866
  if (isRecoverableSocketError(err)) {
14672
14867
  log.debug(
14673
- { origin, code, err: message },
14868
+ { origin, code, err: message2 },
14674
14869
  "companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
14675
14870
  );
14676
14871
  return;
14677
14872
  }
14678
14873
  log.error(
14679
- { origin, code, err: message, stack: err instanceof Error ? err.stack : void 0 },
14874
+ { origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
14680
14875
  "An unexpected error occurred; the companion is still running."
14681
14876
  );
14682
14877
  }
14683
14878
 
14684
14879
  // src/crash-marker.ts
14685
14880
  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";
14881
+ import { join as join21 } from "path";
14687
14882
  function crashMarkerPath() {
14688
- return join20(cabaneDir(), "last-error.json");
14883
+ return join21(cabaneDir(), "last-error.json");
14689
14884
  }
14690
14885
  function recordCrash(rec) {
14691
14886
  try {