@cabane/companion 0.6.168 → 0.6.170

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 +355 -166
  2. package/dist/runtime.js +326 -152
  3. package/package.json +1 -1
package/dist/runtime.js CHANGED
@@ -3056,9 +3056,15 @@ var resolvedMcpServerSchema = z8.union([
3056
3056
  var resolvedMcpServersSchema = z8.record(z8.string(), resolvedMcpServerSchema);
3057
3057
  var hostInjectedServersSchema = z8.record(z8.string(), z8.unknown());
3058
3058
  var turnRequestSchema = z8.object({
3059
- // Server-composed system prompt (core + capability prose + adapter addendum +
3060
- // charter). One string to the adapter.
3059
+ // Server-composed standing instructions: the core segment (body, tool naming, identity, agent
3060
+ // charter) and, after it, the conversation segment (workspace and room charters, skill indexes,
3061
+ // roster). One string to the adapter, current as of this dispatch.
3061
3062
  systemPrompt: z8.string(),
3063
+ // The same text as its two ordered segments, so an adapter that writes standing text into the
3064
+ // conversation (Codex's input preamble) can tell which half changed. `systemPrompt` is
3065
+ // `joinStanding(standing)`. Optional: absent from an API older than the split, where
3066
+ // `systemPrompt` is the core alone and the conversation material rides `prompt`.
3067
+ standing: z8.object({ core: z8.string(), conversation: z8.string() }).optional(),
3062
3068
  // Server-composed per-turn user text (anchor reminder + the triggering message).
3063
3069
  prompt: z8.string(),
3064
3070
  // The multi-block user-message body (text + vision).
@@ -3106,6 +3112,16 @@ var turnRequestSchema = z8.object({
3106
3112
  })
3107
3113
  });
3108
3114
 
3115
+ // packages/agent-runtime/src/standing.ts
3116
+ var STANDING_SEPARATOR = "\n\n---\n\n";
3117
+ function joinStanding(segments) {
3118
+ const conversation = segments.conversation.trim();
3119
+ return conversation.length > 0 ? `${segments.core}${STANDING_SEPARATOR}${conversation}` : segments.core;
3120
+ }
3121
+ function standingSegments(req) {
3122
+ return req.standing ?? { core: req.systemPrompt, conversation: "" };
3123
+ }
3124
+
3109
3125
  // packages/agent-runtime/src/conformance.ts
3110
3126
  import { z as z9 } from "zod";
3111
3127
  var conformanceFixtureSchema = z9.object({
@@ -4988,7 +5004,6 @@ async function* decodeOpencodeStream(events, ctx) {
4988
5004
  let reason;
4989
5005
  let settled = false;
4990
5006
  const userMessageIds = /* @__PURE__ */ new Set();
4991
- if (ctx.skipMessageId) userMessageIds.add(ctx.skipMessageId);
4992
5007
  for await (const ev of events) {
4993
5008
  if (ctx.signal.aborted) return;
4994
5009
  if (ev.type === "message.updated") {
@@ -5148,8 +5163,6 @@ function buildRunSpec(req, resumeSessionId) {
5148
5163
  parts: buildParts(req),
5149
5164
  tools,
5150
5165
  allowAllHostTools,
5151
- // Fresh session → mint + send a client id; resumed → let opencode mint it.
5152
- sendClientMessageId: resumeSessionId == null,
5153
5166
  ...dialect.agent ? { agent: dialect.agent } : {},
5154
5167
  mcp: buildMcp(req),
5155
5168
  // Every server this turn installs with a credential resolved into it: a remote one's
@@ -5308,14 +5321,12 @@ function createHttpOpencodeTransport(opts) {
5308
5321
  created = true;
5309
5322
  }
5310
5323
  await installMcp(doFetch, base, spec, signal);
5311
- const userMessageId = spec.sendClientMessageId ? newOpencodeMessageId() : null;
5312
5324
  const promptRes = await doFetch(
5313
5325
  `${base}/session/${encodeURIComponent(sessionId)}/prompt_async`,
5314
5326
  {
5315
5327
  method: "POST",
5316
5328
  headers: { "content-type": "application/json" },
5317
5329
  body: JSON.stringify({
5318
- ...userMessageId ? { messageID: userMessageId } : {},
5319
5330
  ...spec.model ? { model: spec.model } : {},
5320
5331
  system: spec.systemPrompt,
5321
5332
  parts: spec.parts,
@@ -5341,7 +5352,6 @@ function createHttpOpencodeTransport(opts) {
5341
5352
  sessionId,
5342
5353
  created,
5343
5354
  directory: spec.directory,
5344
- userMessageId,
5345
5355
  // The lock is released when this stream finishes draining.
5346
5356
  events: releaseAfter(parseSseStream(eventRes.body, sessionId, signal), release)
5347
5357
  };
@@ -5449,9 +5459,6 @@ function belongsToSession(ev, sessionId) {
5449
5459
  if (typeof infoSid === "string") return infoSid === sessionId;
5450
5460
  return true;
5451
5461
  }
5452
- function newOpencodeMessageId() {
5453
- return `msg_${crypto.randomUUID().replace(/-/g, "")}`;
5454
- }
5455
5462
  function trimSlash(s) {
5456
5463
  return s.endsWith("/") ? s.slice(0, -1) : s;
5457
5464
  }
@@ -5503,8 +5510,7 @@ function createOpencodeAdapter(deps = {}) {
5503
5510
  }
5504
5511
  yield* decodeOpencodeStream(result.events, {
5505
5512
  signal,
5506
- sessionId: result.sessionId,
5507
- skipMessageId: result.userMessageId
5513
+ sessionId: result.sessionId
5508
5514
  });
5509
5515
  }
5510
5516
  };
@@ -5903,8 +5909,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
5903
5909
  ]
5904
5910
  },
5905
5911
  {
5906
- // On a resumed turn the transport mints no client message id (doing so no-ops the turn), so the
5907
- // decoder gets no `skipMessageId`. It learns the user-message id from the stream — the
5912
+ // On resume, as on a fresh turn, the decoder learns the user-message id from the stream:
5908
5913
  // `message.updated` user frame precedes the echoed part — and drops the echo, so the injected
5909
5914
  // per-turn context can't leak into Steps. Only the reply surfaces; no session event.
5910
5915
  name: "session resume \u2014 prompt echo filtered via stream-derived user id",
@@ -6125,7 +6130,14 @@ function fingerprintPrompt(prompt) {
6125
6130
  return `${prompt.length}.${(fnv >>> 0).toString(36)}.${(djb >>> 0).toString(36)}`;
6126
6131
  }
6127
6132
  function encodeSession3(state) {
6128
- return JSON.stringify(state);
6133
+ return JSON.stringify({
6134
+ threadId: state.threadId,
6135
+ cwd: state.cwd,
6136
+ promptFingerprint: state.promptFingerprint,
6137
+ conversationFingerprint: state.conversationFingerprint ?? null,
6138
+ compactions: state.compactions ?? null,
6139
+ usageBaseline: state.usageBaseline
6140
+ });
6129
6141
  }
6130
6142
  function decideResume3(stored, currentCwd) {
6131
6143
  if (!stored) return { fresh: true, reason: "no_session" };
@@ -6146,6 +6158,8 @@ function decideResume3(stored, currentCwd) {
6146
6158
  return {
6147
6159
  resume: state.threadId,
6148
6160
  promptFingerprint: typeof state.promptFingerprint === "string" ? state.promptFingerprint : null,
6161
+ conversationFingerprint: typeof state.conversationFingerprint === "string" ? state.conversationFingerprint : null,
6162
+ compactions: typeof state.compactions === "number" && Number.isInteger(state.compactions) && state.compactions >= 0 ? state.compactions : null,
6149
6163
  usageBaseline: readUsageBaseline(state.usageBaseline)
6150
6164
  };
6151
6165
  }
@@ -6178,6 +6192,7 @@ async function* decodeCodexStream(events, ctx) {
6178
6192
  let usage;
6179
6193
  let cumulativeUsage;
6180
6194
  let threadIdForSession = ctx.resumedThreadId;
6195
+ let lastState = ctx.storedState ?? null;
6181
6196
  let settled = false;
6182
6197
  function* flushInterim() {
6183
6198
  const sealed = sealHeld2(held, false);
@@ -6193,14 +6208,14 @@ async function* decodeCodexStream(events, ctx) {
6193
6208
  const state = encodeSession3({
6194
6209
  threadId,
6195
6210
  cwd: ctx.cwd ?? "",
6196
- promptFingerprint: ctx.promptFingerprint ?? null,
6197
- // The baseline this turn inherited, carried through unchanged. This early write records the
6198
- // thread id even if the turn dies mid-stream; it can't yet know what the turn will spend. The
6199
- // closing write records the new baseline.
6211
+ promptFingerprint: ctx.heldStanding?.promptFingerprint ?? null,
6212
+ conversationFingerprint: ctx.heldStanding?.conversationFingerprint ?? null,
6213
+ compactions: ctx.heldStanding?.compactions ?? null,
6200
6214
  usageBaseline: ctx.usageBaseline ?? null
6201
6215
  });
6202
6216
  if (threadId !== ctx.resumedThreadId || state !== ctx.storedState) {
6203
6217
  sessionEmitted = true;
6218
+ lastState = state;
6204
6219
  yield {
6205
6220
  type: "session",
6206
6221
  state,
@@ -6299,14 +6314,16 @@ async function* decodeCodexStream(events, ctx) {
6299
6314
  if (sealed) yield sealed;
6300
6315
  reason = "no_terminal";
6301
6316
  }
6302
- if (ok && cumulativeUsage && threadIdForSession) {
6317
+ if (ok && threadIdForSession) {
6303
6318
  const closing = encodeSession3({
6304
6319
  threadId: threadIdForSession,
6305
6320
  cwd: ctx.cwd ?? "",
6306
6321
  promptFingerprint: ctx.promptFingerprint ?? null,
6307
- usageBaseline: cumulativeUsage
6322
+ conversationFingerprint: ctx.conversationFingerprint ?? null,
6323
+ compactions: ctx.compactions ?? null,
6324
+ usageBaseline: cumulativeUsage ?? ctx.usageBaseline ?? null
6308
6325
  });
6309
- yield { type: "session", state: closing };
6326
+ if (closing !== lastState) yield { type: "session", state: closing };
6310
6327
  }
6311
6328
  const resolvedConfig = ctx.resolvedReasoningEffort ? { reasoningEffort: ctx.resolvedReasoningEffort } : void 0;
6312
6329
  yield {
@@ -6376,15 +6393,27 @@ var ENV_ENVELOPE_KEYS = [
6376
6393
  "CABANE_CONVERSATION_TITLE",
6377
6394
  "CABANE_HOST_ACCESS"
6378
6395
  ];
6379
- function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
6396
+ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadStanding = null, imagePaths = []) {
6380
6397
  const { policy, config } = req;
6381
6398
  const directory = req.local.cwd ?? "";
6382
6399
  const dialect = readCodexDialect(config.runtimeOptions);
6383
6400
  const baseInstructionsFile = !policy.hostFs && instructionsFile ? instructionsFile : null;
6384
6401
  const model = config.model ? parseCodexModel(config.model) : null;
6385
- const promptFingerprint = fingerprintPrompt(req.systemPrompt);
6386
- const threadHasThisPrompt = resumeThreadId !== null && threadPromptFingerprint !== null && threadPromptFingerprint === promptFingerprint;
6387
- const promptRidesInput = baseInstructionsFile === null && !threadHasThisPrompt;
6402
+ const segments = standingSegments(req);
6403
+ const conversationText = segments.conversation.trim();
6404
+ const fingerprints = {
6405
+ core: fingerprintPrompt(segments.core),
6406
+ conversation: conversationText.length > 0 ? fingerprintPrompt(conversationText) : null
6407
+ };
6408
+ const resumed = resumeThreadId !== null;
6409
+ const held = resumed ? threadStanding : null;
6410
+ const viaInput = baseInstructionsFile === null;
6411
+ const preamble = {
6412
+ core: viaInput && (held?.core ?? null) !== fingerprints.core,
6413
+ // Compared both ways: a held segment that is now empty differs from it, and nothing held
6414
+ // equals nothing to send.
6415
+ conversation: viaInput && (held?.conversation ?? null) !== fingerprints.conversation
6416
+ };
6388
6417
  return {
6389
6418
  resumeThreadId,
6390
6419
  directory,
@@ -6393,9 +6422,9 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
6393
6422
  skipGitRepoCheck: true,
6394
6423
  ...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
6395
6424
  baseInstructionsFile,
6396
- input: withImages(buildInput(req, promptRidesInput), imagePaths),
6397
- promptRidesInput,
6398
- promptFingerprint,
6425
+ input: withImages(buildInput(req, segments, preamble, resumed), imagePaths),
6426
+ preamble,
6427
+ fingerprints,
6399
6428
  config: buildConfig(req, baseInstructionsFile),
6400
6429
  secretEnv: codexSecretEnv(req)
6401
6430
  };
@@ -6411,12 +6440,26 @@ function codexSecretEnv(req) {
6411
6440
  }
6412
6441
  return Object.keys(env).length > 0 ? env : null;
6413
6442
  }
6414
- function buildInput(req, promptRidesInput) {
6443
+ var CORE_REPLACES = "Your standing instructions changed since an earlier copy in this conversation, or that copy may have been compacted away. This copy replaces every earlier one: where they differ, this one holds, and anything an earlier copy said that this one does not no longer applies.";
6444
+ var CONVERSATION_REPLACES = "This conversation's standing context \u2014 its charters, skill indexes and roster \u2014 changed since an earlier copy in this conversation, or that copy may have been compacted away. This copy replaces every earlier one, including charters and skills an earlier turn carried in its own text: where they differ, this one holds, and anything an earlier copy said that this one does not no longer applies.";
6445
+ function buildInput(req, segments, preamble, resumed) {
6415
6446
  const userText = req.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
6416
6447
  const body = userText.trim().length > 0 ? userText : req.prompt;
6417
- if (!promptRidesInput) return body;
6418
- const system = req.systemPrompt.trim();
6419
- return system.length > 0 ? `${system}
6448
+ const standing = [];
6449
+ if (!resumed) {
6450
+ if (preamble.core) standing.push(joinStanding(segments).trim());
6451
+ } else {
6452
+ if (preamble.core) standing.push(`${CORE_REPLACES}
6453
+
6454
+ ${segments.core.trim()}`);
6455
+ if (preamble.conversation) {
6456
+ standing.push(`${CONVERSATION_REPLACES}
6457
+
6458
+ ${segments.conversation.trim()}`.trim());
6459
+ }
6460
+ }
6461
+ const lead = standing.filter((s) => s.length > 0);
6462
+ return lead.length > 0 ? `${lead.join("\n\n---\n\n")}
6420
6463
 
6421
6464
  ${body}` : body;
6422
6465
  }
@@ -6628,7 +6671,6 @@ function createCodexAdapter(deps = {}) {
6628
6671
  const directory = req.local.cwd ?? "";
6629
6672
  const decision = decideResume3(req.session, directory);
6630
6673
  const resumeThreadId = "resume" in decision ? decision.resume : null;
6631
- const threadPromptFingerprint = "resume" in decision ? decision.promptFingerprint : null;
6632
6674
  const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
6633
6675
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
6634
6676
  deps.onWarn?.(
@@ -6645,6 +6687,32 @@ function createCodexAdapter(deps = {}) {
6645
6687
  degraded = false;
6646
6688
  }
6647
6689
  }
6690
+ let compactions = 0;
6691
+ let threadStanding = null;
6692
+ if ("resume" in decision) {
6693
+ compactions = deps.countCompactions ? await deps.countCompactions(decision.resume) : null;
6694
+ if (signal.aborted) return;
6695
+ if (compactions === null) {
6696
+ deps.onWarn?.(
6697
+ "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions (ENG210)",
6698
+ { threadId: decision.resume }
6699
+ );
6700
+ } else if (decision.compactions === compactions) {
6701
+ threadStanding = {
6702
+ core: decision.promptFingerprint,
6703
+ conversation: decision.conversationFingerprint
6704
+ };
6705
+ } else if (decision.compactions !== null && compactions > decision.compactions) {
6706
+ deps.onWarn?.(
6707
+ "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions (ENG210)",
6708
+ {
6709
+ threadId: decision.resume,
6710
+ compactions,
6711
+ compactionsSince: compactions - decision.compactions
6712
+ }
6713
+ );
6714
+ }
6715
+ }
6648
6716
  let instructions = null;
6649
6717
  if (!req.policy.hostFs) {
6650
6718
  if (!deps.writeInstructionsFile) {
@@ -6689,7 +6757,7 @@ function createCodexAdapter(deps = {}) {
6689
6757
  req,
6690
6758
  resumeThreadId,
6691
6759
  instructions?.path ?? null,
6692
- threadPromptFingerprint,
6760
+ threadStanding,
6693
6761
  images?.paths ?? []
6694
6762
  );
6695
6763
  if (spec.model === null) {
@@ -6704,10 +6772,18 @@ function createCodexAdapter(deps = {}) {
6704
6772
  cwd: req.local.cwd,
6705
6773
  resumedThreadId: resumeThreadId,
6706
6774
  degraded,
6707
- // Which prompt the thread carries once this turn lands. If we sent one, it's that one — the
6708
- // later copy is what the model reads. If we withheld it, the thread carries what it did. Read
6709
- // off the spec, so the record can't disagree with what was sent.
6710
- promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
6775
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6776
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6777
+ // the spec, so the record can't disagree with what was sent.
6778
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6779
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6780
+ compactions,
6781
+ // What the thread held before this turn, for the write at `thread.started`.
6782
+ heldStanding: {
6783
+ promptFingerprint: threadStanding?.core ?? null,
6784
+ conversationFingerprint: threadStanding?.conversation ?? null,
6785
+ compactions: threadStanding ? compactions : null
6786
+ },
6711
6787
  storedState: req.session,
6712
6788
  usageBaseline: threadUsageBaseline,
6713
6789
  // Codex reports no model in-stream, so record what the thread was started with (null on the
@@ -6807,11 +6883,21 @@ var OP_FAILURE = {
6807
6883
  message: "files.read returned more than the result ceiling"
6808
6884
  }
6809
6885
  };
6810
- var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint = fingerprintPrompt(PROMPT), usageBaseline = null) => ({
6886
+ var startedSession = (threadId, cwd = DIR2, degraded = false) => ({
6811
6887
  type: "session",
6812
- state: encodeSession3({ threadId, cwd, promptFingerprint, usageBaseline }),
6888
+ state: encodeSession3({ threadId, cwd, promptFingerprint: null, usageBaseline: null }),
6813
6889
  ...degraded ? { degraded: true } : {}
6814
6890
  });
6891
+ var closingSession = (threadId, usageBaseline = null) => ({
6892
+ type: "session",
6893
+ state: encodeSession3({
6894
+ threadId,
6895
+ cwd: DIR2,
6896
+ promptFingerprint: fingerprintPrompt(PROMPT),
6897
+ usageBaseline,
6898
+ compactions: 0
6899
+ })
6900
+ });
6815
6901
  var CODEX_CONFORMANCE_FIXTURES = [
6816
6902
  {
6817
6903
  // A plain text reply: the held agent message seals as the terminal final at `turn.completed`;
@@ -6834,11 +6920,11 @@ var CODEX_CONFORMANCE_FIXTURES = [
6834
6920
  })
6835
6921
  ],
6836
6922
  expected: [
6837
- sessionEvent3(NEW_THREAD_ID),
6923
+ startedSession(NEW_THREAD_ID),
6838
6924
  { type: "text", body: "Hello there.", terminal: true },
6839
6925
  // The closing session write — the thread's ending totals, recorded as the baseline the next turn
6840
6926
  // subtracts. A fresh thread, so the `result` usage below is the frame verbatim.
6841
- sessionEvent3(NEW_THREAD_ID, DIR2, false, fingerprintPrompt(PROMPT), {
6927
+ closingSession(NEW_THREAD_ID, {
6842
6928
  inputTokens: 100,
6843
6929
  outputTokens: 50,
6844
6930
  cacheReadTokens: 20,
@@ -6885,7 +6971,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6885
6971
  turnCompleted()
6886
6972
  ],
6887
6973
  expected: [
6888
- sessionEvent3(NEW_THREAD_ID),
6974
+ startedSession(NEW_THREAD_ID),
6889
6975
  { type: "text", body: "Reading your notes.", terminal: false },
6890
6976
  {
6891
6977
  type: "tool",
@@ -6905,6 +6991,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6905
6991
  result: [{ type: "text", text: "sleep early" }]
6906
6992
  },
6907
6993
  { type: "text", body: "Here's the summary.", terminal: true },
6994
+ closingSession(NEW_THREAD_ID),
6908
6995
  { type: "result", ok: true }
6909
6996
  ]
6910
6997
  },
@@ -6938,7 +7025,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6938
7025
  turnCompleted()
6939
7026
  ],
6940
7027
  expected: [
6941
- sessionEvent3(NEW_THREAD_ID),
7028
+ startedSession(NEW_THREAD_ID),
6942
7029
  {
6943
7030
  type: "tool",
6944
7031
  id: "query-success",
@@ -6956,6 +7043,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6956
7043
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
6957
7044
  result: OP_SUCCESS
6958
7045
  },
7046
+ closingSession(NEW_THREAD_ID),
6959
7047
  { type: "result", ok: true }
6960
7048
  ]
6961
7049
  },
@@ -6991,7 +7079,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
6991
7079
  turnCompleted()
6992
7080
  ],
6993
7081
  expected: [
6994
- sessionEvent3(NEW_THREAD_ID),
7082
+ startedSession(NEW_THREAD_ID),
6995
7083
  {
6996
7084
  type: "tool",
6997
7085
  id: "query-failed",
@@ -7009,6 +7097,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7009
7097
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
7010
7098
  result: OP_FAILURE
7011
7099
  },
7100
+ closingSession(NEW_THREAD_ID),
7012
7101
  { type: "result", ok: true }
7013
7102
  ]
7014
7103
  },
@@ -7032,7 +7121,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7032
7121
  turnCompleted()
7033
7122
  ],
7034
7123
  expected: [
7035
- sessionEvent3(NEW_THREAD_ID),
7124
+ startedSession(NEW_THREAD_ID),
7036
7125
  {
7037
7126
  type: "tool",
7038
7127
  id: "mcp-failed",
@@ -7050,6 +7139,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7050
7139
  input: { q: "receipt" },
7051
7140
  result: "connection closed"
7052
7141
  },
7142
+ closingSession(NEW_THREAD_ID),
7053
7143
  { type: "result", ok: true }
7054
7144
  ]
7055
7145
  },
@@ -7075,7 +7165,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7075
7165
  turnCompleted()
7076
7166
  ],
7077
7167
  expected: [
7078
- sessionEvent3(NEW_THREAD_ID),
7168
+ startedSession(NEW_THREAD_ID),
7079
7169
  {
7080
7170
  type: "tool",
7081
7171
  id: "mcp-image",
@@ -7093,6 +7183,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7093
7183
  input: {},
7094
7184
  result: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }]
7095
7185
  },
7186
+ closingSession(NEW_THREAD_ID),
7096
7187
  { type: "result", ok: true }
7097
7188
  ]
7098
7189
  },
@@ -7133,7 +7224,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7133
7224
  turnCompleted()
7134
7225
  ],
7135
7226
  expected: [
7136
- sessionEvent3(NEW_THREAD_ID),
7227
+ startedSession(NEW_THREAD_ID),
7137
7228
  { type: "tool", id: "t1", name: "list", phase: "start", summary: "", input: {} },
7138
7229
  {
7139
7230
  type: "tool",
@@ -7144,6 +7235,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7144
7235
  input: { path: "/product/tasks" },
7145
7236
  result: [{ type: "text", text: "CT481.md" }]
7146
7237
  },
7238
+ closingSession(NEW_THREAD_ID),
7147
7239
  { type: "result", ok: true }
7148
7240
  ]
7149
7241
  },
@@ -7161,7 +7253,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7161
7253
  turnCompleted()
7162
7254
  ],
7163
7255
  expected: [
7164
- sessionEvent3(NEW_THREAD_ID),
7256
+ startedSession(NEW_THREAD_ID),
7165
7257
  {
7166
7258
  type: "tool",
7167
7259
  id: "ws1",
@@ -7178,6 +7270,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7178
7270
  summary: "best pizza in nyc",
7179
7271
  input: { query: "best pizza in nyc" }
7180
7272
  },
7273
+ closingSession(NEW_THREAD_ID),
7181
7274
  { type: "result", ok: true }
7182
7275
  ]
7183
7276
  },
@@ -7193,7 +7286,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7193
7286
  turnCompleted()
7194
7287
  ],
7195
7288
  expected: [
7196
- sessionEvent3(NEW_THREAD_ID),
7289
+ startedSession(NEW_THREAD_ID),
7197
7290
  {
7198
7291
  type: "tool",
7199
7292
  id: "ws2",
@@ -7210,6 +7303,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7210
7303
  summary: "",
7211
7304
  input: { query: "" }
7212
7305
  },
7306
+ closingSession(NEW_THREAD_ID),
7213
7307
  { type: "result", ok: true }
7214
7308
  ]
7215
7309
  },
@@ -7240,7 +7334,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7240
7334
  turnCompleted()
7241
7335
  ],
7242
7336
  expected: [
7243
- sessionEvent3(NEW_THREAD_ID),
7337
+ startedSession(NEW_THREAD_ID),
7244
7338
  { type: "text", body: "Working on it.", terminal: false },
7245
7339
  {
7246
7340
  type: "tool",
@@ -7272,7 +7366,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7272
7366
  turnFailed("provider exploded")
7273
7367
  ],
7274
7368
  expected: [
7275
- sessionEvent3(NEW_THREAD_ID),
7369
+ startedSession(NEW_THREAD_ID),
7276
7370
  { type: "text", body: "Partial work.", terminal: false },
7277
7371
  { type: "runtime_notice", body: "provider exploded" },
7278
7372
  { type: "result", ok: false, reason: "error:provider exploded" }
@@ -7289,7 +7383,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7289
7383
  fatalError("stream died")
7290
7384
  ],
7291
7385
  expected: [
7292
- sessionEvent3(NEW_THREAD_ID),
7386
+ startedSession(NEW_THREAD_ID),
7293
7387
  { type: "text", body: "Starting.", terminal: false },
7294
7388
  { type: "runtime_notice", body: "stream died" },
7295
7389
  { type: "result", ok: false, reason: "error:stream died" }
@@ -7302,7 +7396,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7302
7396
  request: makeRequest3(),
7303
7397
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
7304
7398
  expected: [
7305
- sessionEvent3(NEW_THREAD_ID),
7399
+ startedSession(NEW_THREAD_ID),
7306
7400
  { type: "runtime_notice", body: "429 Too Many Requests" },
7307
7401
  { type: "result", ok: false, reason: "rate_limited" }
7308
7402
  ]
@@ -7312,7 +7406,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7312
7406
  request: makeRequest3(),
7313
7407
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("503 Service Unavailable")],
7314
7408
  expected: [
7315
- sessionEvent3(NEW_THREAD_ID),
7409
+ startedSession(NEW_THREAD_ID),
7316
7410
  { type: "runtime_notice", body: "503 Service Unavailable" },
7317
7411
  { type: "result", ok: false, reason: "server_error" }
7318
7412
  ]
@@ -7325,7 +7419,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7325
7419
  turnFailed("You've hit your session limit \xB7 resets 8:20pm (UTC)")
7326
7420
  ],
7327
7421
  expected: [
7328
- sessionEvent3(NEW_THREAD_ID),
7422
+ startedSession(NEW_THREAD_ID),
7329
7423
  {
7330
7424
  type: "runtime_notice",
7331
7425
  body: "You've hit your session limit \xB7 resets 8:20pm (UTC)"
@@ -7338,7 +7432,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7338
7432
  request: makeRequest3(),
7339
7433
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("401 Unauthorized: invalid api key")],
7340
7434
  expected: [
7341
- sessionEvent3(NEW_THREAD_ID),
7435
+ startedSession(NEW_THREAD_ID),
7342
7436
  { type: "runtime_notice", body: "401 Unauthorized: invalid api key" },
7343
7437
  { type: "result", ok: false, reason: "auth_expired" }
7344
7438
  ]
@@ -7350,7 +7444,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7350
7444
  request: makeRequest3(),
7351
7445
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
7352
7446
  expected: [
7353
- sessionEvent3(NEW_THREAD_ID),
7447
+ startedSession(NEW_THREAD_ID),
7354
7448
  { type: "runtime_notice", body: "something local broke" },
7355
7449
  { type: "result", ok: false, reason: "error:something local broke" }
7356
7450
  ]
@@ -7363,7 +7457,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7363
7457
  turnFailed(` ${BOUNDED_NOTICE2}${"y".repeat(20)} `)
7364
7458
  ],
7365
7459
  expected: [
7366
- sessionEvent3(NEW_THREAD_ID),
7460
+ startedSession(NEW_THREAD_ID),
7367
7461
  { type: "runtime_notice", body: BOUNDED_NOTICE2 },
7368
7462
  { type: "result", ok: false, reason: "usage_capped" }
7369
7463
  ]
@@ -7374,26 +7468,32 @@ var CODEX_CONFORMANCE_FIXTURES = [
7374
7468
  name: "empty failure text emits no runtime notice",
7375
7469
  request: makeRequest3(),
7376
7470
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
7377
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error:unknown" }]
7471
+ expected: [
7472
+ startedSession(NEW_THREAD_ID),
7473
+ { type: "result", ok: false, reason: "error:unknown" }
7474
+ ]
7378
7475
  },
7379
7476
  {
7380
7477
  name: "whitespace-only failure text emits no runtime notice",
7381
7478
  request: makeRequest3(),
7382
7479
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed(" ")],
7383
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
7480
+ expected: [startedSession(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
7384
7481
  },
7385
7482
  {
7386
7483
  name: "bare status token emits no runtime notice",
7387
7484
  request: makeRequest3(),
7388
7485
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429")],
7389
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "rate_limited" }]
7486
+ expected: [
7487
+ startedSession(NEW_THREAD_ID),
7488
+ { type: "result", ok: false, reason: "rate_limited" }
7489
+ ]
7390
7490
  },
7391
7491
  {
7392
7492
  name: "single-word provider sentence emits a runtime notice",
7393
7493
  request: makeRequest3(),
7394
7494
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("Unavailable.")],
7395
7495
  expected: [
7396
- sessionEvent3(NEW_THREAD_ID),
7496
+ startedSession(NEW_THREAD_ID),
7397
7497
  { type: "runtime_notice", body: "Unavailable." },
7398
7498
  { type: "result", ok: false, reason: "error:Unavailable." }
7399
7499
  ]
@@ -7421,7 +7521,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7421
7521
  turnCompleted()
7422
7522
  ],
7423
7523
  expected: [
7424
- sessionEvent3(NEW_THREAD_ID),
7524
+ startedSession(NEW_THREAD_ID),
7425
7525
  {
7426
7526
  type: "result",
7427
7527
  ok: false,
@@ -7441,8 +7541,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7441
7541
  turnCompleted()
7442
7542
  ],
7443
7543
  expected: [
7444
- sessionEvent3(NEW_THREAD_ID),
7544
+ startedSession(NEW_THREAD_ID),
7445
7545
  { type: "text", body: "All good now.", terminal: true },
7546
+ closingSession(NEW_THREAD_ID),
7446
7547
  { type: "result", ok: true }
7447
7548
  ]
7448
7549
  },
@@ -7454,7 +7555,11 @@ var CODEX_CONFORMANCE_FIXTURES = [
7454
7555
  name: "zero-content success (empty runtime result)",
7455
7556
  request: makeRequest3(),
7456
7557
  nativeStream: [threadStarted(NEW_THREAD_ID), turnCompleted()],
7457
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: true }]
7558
+ expected: [
7559
+ startedSession(NEW_THREAD_ID),
7560
+ closingSession(NEW_THREAD_ID),
7561
+ { type: "result", ok: true }
7562
+ ]
7458
7563
  },
7459
7564
  {
7460
7565
  // Empty-final: a clean turn that ended on a tool call with no closing message.
@@ -7479,7 +7584,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7479
7584
  turnCompleted()
7480
7585
  ],
7481
7586
  expected: [
7482
- sessionEvent3(NEW_THREAD_ID),
7587
+ startedSession(NEW_THREAD_ID),
7483
7588
  {
7484
7589
  type: "tool",
7485
7590
  id: "t1",
@@ -7497,6 +7602,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
7497
7602
  input: { changes: [{ path: "a.md", kind: "add" }] },
7498
7603
  result: [{ path: "a.md", kind: "add" }]
7499
7604
  },
7605
+ closingSession(NEW_THREAD_ID),
7500
7606
  { type: "result", ok: true }
7501
7607
  ]
7502
7608
  },
@@ -7512,9 +7618,10 @@ var CODEX_CONFORMANCE_FIXTURES = [
7512
7618
  turnCompleted()
7513
7619
  ],
7514
7620
  expected: [
7515
- sessionEvent3(NEW_THREAD_ID),
7621
+ startedSession(NEW_THREAD_ID),
7516
7622
  { type: "thinking", text: "Let me check the notes." },
7517
7623
  { type: "text", body: "All set.", terminal: true },
7624
+ closingSession(NEW_THREAD_ID),
7518
7625
  { type: "result", ok: true }
7519
7626
  ]
7520
7627
  },
@@ -7532,8 +7639,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7532
7639
  }),
7533
7640
  nativeStream: [threadStarted(NEW_THREAD_ID), agentMessage("m1", "Done."), turnCompleted()],
7534
7641
  expected: [
7535
- sessionEvent3(NEW_THREAD_ID),
7642
+ startedSession(NEW_THREAD_ID),
7536
7643
  { type: "text", body: "Done.", terminal: true },
7644
+ closingSession(NEW_THREAD_ID),
7537
7645
  {
7538
7646
  type: "result",
7539
7647
  ok: true,
@@ -7555,7 +7663,8 @@ var CODEX_CONFORMANCE_FIXTURES = [
7555
7663
  threadId: "th1",
7556
7664
  cwd: DIR2,
7557
7665
  promptFingerprint: fingerprintPrompt(PROMPT),
7558
- usageBaseline: null
7666
+ usageBaseline: null,
7667
+ compactions: 0
7559
7668
  })
7560
7669
  }),
7561
7670
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
@@ -7577,8 +7686,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7577
7686
  }),
7578
7687
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
7579
7688
  expected: [
7580
- sessionEvent3("th1"),
7689
+ startedSession("th1"),
7581
7690
  { type: "text", body: "Back again.", terminal: true },
7691
+ closingSession("th1"),
7582
7692
  { type: "result", ok: true }
7583
7693
  ]
7584
7694
  },
@@ -7600,8 +7710,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7600
7710
  }),
7601
7711
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
7602
7712
  expected: [
7603
- sessionEvent3("th1"),
7713
+ startedSession("th1"),
7604
7714
  { type: "text", body: "Back again.", terminal: true },
7715
+ closingSession("th1"),
7605
7716
  { type: "result", ok: true }
7606
7717
  ]
7607
7718
  },
@@ -7627,8 +7738,9 @@ var CODEX_CONFORMANCE_FIXTURES = [
7627
7738
  turnCompleted()
7628
7739
  ],
7629
7740
  expected: [
7630
- sessionEvent3(NEW_THREAD_ID, DIR2, true),
7741
+ startedSession(NEW_THREAD_ID, DIR2, true),
7631
7742
  { type: "text", body: "Fresh start.", terminal: true },
7743
+ closingSession(NEW_THREAD_ID),
7632
7744
  { type: "result", ok: true }
7633
7745
  ]
7634
7746
  }
@@ -7710,7 +7822,7 @@ var ConnectorHealthStore = class {
7710
7822
  // src/dispatcher.ts
7711
7823
  import { randomUUID as randomUUID4 } from "crypto";
7712
7824
  import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
7713
- import { join as join18 } from "path";
7825
+ import { join as join19 } from "path";
7714
7826
 
7715
7827
  // src/runner/launch.ts
7716
7828
  import { createInterface } from "readline";
@@ -9108,6 +9220,7 @@ function buildCompanionTurnRequest(params) {
9108
9220
  const { turnContext: t } = params;
9109
9221
  return {
9110
9222
  systemPrompt: t.systemPrompt,
9223
+ ...t.standing ? { standing: t.standing } : {},
9111
9224
  prompt: t.prompt,
9112
9225
  content: t.content,
9113
9226
  config: t.config,
@@ -9219,6 +9332,62 @@ function message(err) {
9219
9332
  return err instanceof Error ? err.message : String(err);
9220
9333
  }
9221
9334
 
9335
+ // src/codex-compactions.ts
9336
+ import { createReadStream } from "fs";
9337
+ import { readdir } from "fs/promises";
9338
+ import { homedir as homedir6 } from "os";
9339
+ import { join as join16 } from "path";
9340
+ import { createInterface as createInterface2 } from "readline";
9341
+ var DAY_MS = 864e5;
9342
+ var UUID_V7 = /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9343
+ var RECORD_HEAD_CHARS = 200;
9344
+ function createCodexCompactionCounter(codexHome = process.env.CODEX_HOME || join16(homedir6(), ".codex")) {
9345
+ return async (threadId) => {
9346
+ try {
9347
+ const rollout = await findRollout(codexHome, threadId);
9348
+ return rollout ? await countCompactions(rollout) : null;
9349
+ } catch {
9350
+ return null;
9351
+ }
9352
+ };
9353
+ }
9354
+ async function findRollout(codexHome, threadId) {
9355
+ const id = UUID_V7.exec(threadId);
9356
+ if (!id) return null;
9357
+ const createdAt = parseInt(`${id[1]}${id[2]}`, 16);
9358
+ const suffix = `-${threadId}.jsonl`;
9359
+ for (const offset of [0, -DAY_MS, DAY_MS]) {
9360
+ const day = new Date(createdAt + offset);
9361
+ const dir2 = join16(
9362
+ codexHome,
9363
+ "sessions",
9364
+ String(day.getUTCFullYear()),
9365
+ String(day.getUTCMonth() + 1).padStart(2, "0"),
9366
+ String(day.getUTCDate()).padStart(2, "0")
9367
+ );
9368
+ let names;
9369
+ try {
9370
+ names = await readdir(dir2);
9371
+ } catch {
9372
+ continue;
9373
+ }
9374
+ const name = names.find((n) => n.startsWith("rollout-") && n.endsWith(suffix));
9375
+ if (name) return join16(dir2, name);
9376
+ }
9377
+ return null;
9378
+ }
9379
+ async function countCompactions(rollout) {
9380
+ const lines2 = createInterface2({
9381
+ input: createReadStream(rollout, { encoding: "utf8" }),
9382
+ crlfDelay: Infinity
9383
+ });
9384
+ let count = 0;
9385
+ for await (const line of lines2) {
9386
+ if (line.slice(0, RECORD_HEAD_CHARS).includes('"type":"compacted"')) count += 1;
9387
+ }
9388
+ return count;
9389
+ }
9390
+
9222
9391
  // src/turn-seq-floor.ts
9223
9392
  var SeqFloorUnavailable = class extends Error {
9224
9393
  constructor(detail) {
@@ -9316,15 +9485,15 @@ function reportFailure(config, body) {
9316
9485
 
9317
9486
  // src/prepared.ts
9318
9487
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
9319
- import { join as join16 } from "path";
9488
+ import { join as join17 } from "path";
9320
9489
  function dirFor(workspaceId) {
9321
- return join16(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9490
+ return join17(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9322
9491
  }
9323
9492
  function conversationDir(workspaceId, conversationId) {
9324
- return join16(dirFor(workspaceId), encodeURIComponent(conversationId));
9493
+ return join17(dirFor(workspaceId), encodeURIComponent(conversationId));
9325
9494
  }
9326
9495
  function pathFor2(workspaceId, conversationId, agentId) {
9327
- return join16(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9496
+ return join17(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
9328
9497
  }
9329
9498
  function readPrepared(workspaceId, conversationId, agentId) {
9330
9499
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -9356,9 +9525,9 @@ function clearPrepared(workspaceId, conversationId, agentId) {
9356
9525
 
9357
9526
  // src/transcript-writer.ts
9358
9527
  import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
9359
- import { basename as basename3, dirname as dirname7, join as join17 } from "path";
9528
+ import { basename as basename3, dirname as dirname7, join as join18 } from "path";
9360
9529
  function transcriptsDir() {
9361
- return join17(runnerStateDir(), "transcripts");
9530
+ return join18(runnerStateDir(), "transcripts");
9362
9531
  }
9363
9532
  var RETAIN = 200;
9364
9533
  var ANOMALY_RETAIN = 50;
@@ -9368,7 +9537,7 @@ var TranscriptWriter = class {
9368
9537
  onWarn;
9369
9538
  constructor(dir2, meta, onWarn) {
9370
9539
  this.onWarn = onWarn;
9371
- this.path = join17(dir2, fileName(meta));
9540
+ this.path = join18(dir2, fileName(meta));
9372
9541
  try {
9373
9542
  mkdirSync8(dir2, { recursive: true });
9374
9543
  try {
@@ -9397,10 +9566,10 @@ var TranscriptWriter = class {
9397
9566
  preserveAnomaly() {
9398
9567
  if (this.broken) return;
9399
9568
  try {
9400
- const dir2 = join17(dirname7(this.path), "anomalies");
9569
+ const dir2 = join18(dirname7(this.path), "anomalies");
9401
9570
  mkdirSync8(dir2, { recursive: true, mode: 448 });
9402
9571
  chmodSync4(dir2, 448);
9403
- const target = join17(dir2, basename3(this.path));
9572
+ const target = join18(dir2, basename3(this.path));
9404
9573
  copyFileSync(this.path, target);
9405
9574
  chmodSync4(target, 384);
9406
9575
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -9446,7 +9615,7 @@ function pruneOld(dir2, retain) {
9446
9615
  const drop = files.sort().slice(0, files.length - retain);
9447
9616
  for (const f of drop) {
9448
9617
  try {
9449
- rmSync6(join17(dir2, f), { force: true });
9618
+ rmSync6(join18(dir2, f), { force: true });
9450
9619
  } catch {
9451
9620
  }
9452
9621
  }
@@ -10355,6 +10524,8 @@ var TurnExecution = class {
10355
10524
  turnId: this.turnId,
10356
10525
  warn: (meta, msg) => turnLog.warn(meta, msg)
10357
10526
  }),
10527
+ // Whether a resumed thread compacted since its standing preamble was sent.
10528
+ countCompactions: createCodexCompactionCounter(),
10358
10529
  ...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
10359
10530
  })
10360
10531
  );
@@ -10415,6 +10586,9 @@ var TurnExecution = class {
10415
10586
  );
10416
10587
  return {
10417
10588
  systemPrompt: context.systemPrompt,
10589
+ // Replaced even when absent, so the segments can't outlive the
10590
+ // `systemPrompt` they were split from.
10591
+ standing: context.standing,
10418
10592
  prompt: context.prompt,
10419
10593
  content: context.content
10420
10594
  };
@@ -10826,7 +11000,7 @@ function checkoutState(cwd) {
10826
11000
  if (entries.length === 0) {
10827
11001
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
10828
11002
  }
10829
- const gitPath = join18(cwd, ".git");
11003
+ const gitPath = join19(cwd, ".git");
10830
11004
  if (!existsSync12(gitPath)) return { ok: true, reason: "usable" };
10831
11005
  let stat;
10832
11006
  try {
@@ -10834,7 +11008,7 @@ function checkoutState(cwd) {
10834
11008
  } catch (error) {
10835
11009
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
10836
11010
  }
10837
- if (stat.isDirectory() && !existsSync12(join18(gitPath, "HEAD")))
11011
+ if (stat.isDirectory() && !existsSync12(join19(gitPath, "HEAD")))
10838
11012
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
10839
11013
  return { ok: true, reason: "usable" };
10840
11014
  }
@@ -11041,7 +11215,7 @@ function createLocalProcessProvider(opts = {}) {
11041
11215
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11042
11216
  import { constants as constants3, tmpdir as tmpdir3 } from "os";
11043
11217
  import {
11044
- createReadStream,
11218
+ createReadStream as createReadStream2,
11045
11219
  createWriteStream as createWriteStream2,
11046
11220
  existsSync as existsSync15,
11047
11221
  mkdirSync as mkdirSync10,
@@ -11052,7 +11226,7 @@ import {
11052
11226
  statSync as statSync4,
11053
11227
  writeFileSync as writeFileSync6
11054
11228
  } from "fs";
11055
- import { join as join21 } from "path";
11229
+ import { join as join22 } from "path";
11056
11230
  import { Writable } from "stream";
11057
11231
 
11058
11232
  // src/providers/docker-cli.ts
@@ -11246,7 +11420,7 @@ import {
11246
11420
  renameSync as renameSync5,
11247
11421
  rmSync as rmSync8
11248
11422
  } from "fs";
11249
- import { join as join19 } from "path";
11423
+ import { join as join20 } from "path";
11250
11424
  function routes(t) {
11251
11425
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
11252
11426
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -11292,7 +11466,7 @@ function publishStaged(from, to, move = atomicMove) {
11292
11466
  const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
11293
11467
  let entry = {};
11294
11468
  try {
11295
- entry = JSON.parse(readFileSync9(join19(from, name), "utf8"));
11469
+ entry = JSON.parse(readFileSync9(join20(from, name), "utf8"));
11296
11470
  } catch {
11297
11471
  }
11298
11472
  return { name, entry };
@@ -11305,7 +11479,7 @@ function publishStaged(from, to, move = atomicMove) {
11305
11479
  let published = 0;
11306
11480
  for (const { name } of staged) {
11307
11481
  try {
11308
- move(join19(from, name), join19(to, name));
11482
+ move(join20(from, name), join20(to, name));
11309
11483
  } catch {
11310
11484
  return { published, complete: false };
11311
11485
  }
@@ -11318,7 +11492,7 @@ function publishStaged(from, to, move = atomicMove) {
11318
11492
  // src/providers/runner-base.ts
11319
11493
  import { createHash as createHash3 } from "crypto";
11320
11494
  import { existsSync as existsSync14, realpathSync as realpathSync4 } from "fs";
11321
- import { dirname as dirname8, join as join20, sep as sep3 } from "path";
11495
+ import { dirname as dirname8, join as join21, sep as sep3 } from "path";
11322
11496
  import { fileURLToPath as fileURLToPath5 } from "url";
11323
11497
  var COMPANION_MOUNT = "/opt/cabane/companion";
11324
11498
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -11366,11 +11540,11 @@ async function ensureBaseImage(docker, image, log) {
11366
11540
  function ownInstall(moduleFile = fileURLToPath5(import.meta.url)) {
11367
11541
  let dir2 = dirname8(moduleFile);
11368
11542
  for (let i = 0; i < 4; i++) {
11369
- if (existsSync14(join20(dir2, "dist", "cli.js")) && existsSync14(join20(dir2, "package.json"))) break;
11543
+ if (existsSync14(join21(dir2, "dist", "cli.js")) && existsSync14(join21(dir2, "package.json"))) break;
11370
11544
  dir2 = dirname8(dir2);
11371
11545
  }
11372
- if (!existsSync14(join20(dir2, "dist", "cli.js"))) return null;
11373
- const sdk = join20(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11546
+ if (!existsSync14(join21(dir2, "dist", "cli.js"))) return null;
11547
+ const sdk = join21(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11374
11548
  if (!existsSync14(sdk)) return null;
11375
11549
  const root = realpathSync4(dir2);
11376
11550
  return realpathSync4(sdk).startsWith(root + sep3) ? root : null;
@@ -11509,39 +11683,39 @@ function networkCreateError(stderr) {
11509
11683
  }
11510
11684
  function sessionArchivePath(deviceDir, attemptId) {
11511
11685
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
11512
- return join21(deviceDir, "attempts", attemptId, "session.tar");
11686
+ return join22(deviceDir, "attempts", attemptId, "session.tar");
11513
11687
  }
11514
11688
  function pruneOtherAttempts(deviceDir, pair, keep) {
11515
- const root = join21(deviceDir, "attempts");
11689
+ const root = join22(deviceDir, "attempts");
11516
11690
  if (!existsSync15(root)) return;
11517
11691
  for (const id of readdirSync4(root)) {
11518
11692
  if (id === keep || !UUID_RE.test(id)) continue;
11519
11693
  let pairOf = "";
11520
11694
  try {
11521
- pairOf = readFileSync10(join21(root, id, "pair"), "utf8");
11695
+ pairOf = readFileSync10(join22(root, id, "pair"), "utf8");
11522
11696
  } catch {
11523
11697
  continue;
11524
11698
  }
11525
11699
  if (pairOf === `${pair.conversationId}/${pair.agentId}`) {
11526
- rmSync9(join21(root, id), { recursive: true, force: true });
11700
+ rmSync9(join22(root, id), { recursive: true, force: true });
11527
11701
  }
11528
11702
  }
11529
11703
  }
11530
11704
  var IDLE_SESSION_ARCHIVE_MS = 30 * 24 * 60 * 6e4;
11531
11705
  function pruneIdleSessionArchives(deviceDir, now = Date.now(), maxAgeMs = IDLE_SESSION_ARCHIVE_MS) {
11532
- const root = join21(deviceDir, "attempts");
11706
+ const root = join22(deviceDir, "attempts");
11533
11707
  if (!existsSync15(root)) return [];
11534
11708
  const removed = [];
11535
11709
  for (const id of readdirSync4(root)) {
11536
11710
  if (!UUID_RE.test(id)) continue;
11537
11711
  let kept;
11538
11712
  try {
11539
- kept = statSync4(join21(root, id)).mtimeMs;
11713
+ kept = statSync4(join22(root, id)).mtimeMs;
11540
11714
  } catch {
11541
11715
  continue;
11542
11716
  }
11543
11717
  if (now - kept < maxAgeMs) continue;
11544
- rmSync9(join21(root, id), { recursive: true, force: true });
11718
+ rmSync9(join22(root, id), { recursive: true, force: true });
11545
11719
  removed.push(id);
11546
11720
  }
11547
11721
  return removed;
@@ -11742,11 +11916,11 @@ function createDockerProvider(opts) {
11742
11916
  await must(["network", "disconnect", "-f", other, name], "moving the container");
11743
11917
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
11744
11918
  };
11745
- const socketRoot = opts.socketRoot ?? join21(
11919
+ const socketRoot = opts.socketRoot ?? join22(
11746
11920
  process.env.XDG_RUNTIME_DIR && existsSync15(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
11747
11921
  `cabane-runners-${opts.deviceKey}`
11748
11922
  );
11749
- const socketDir = (name) => join21(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
11923
+ const socketDir = (name) => join22(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
11750
11924
  const create = async (name, attempt) => {
11751
11925
  mkdirSync10(socketRoot, { recursive: true, mode: 448 });
11752
11926
  mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
@@ -11888,7 +12062,7 @@ function createDockerProvider(opts) {
11888
12062
  }
11889
12063
  };
11890
12064
  const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
11891
- const pendingPath = (name) => join21(opts.deviceDir, "runners-pending", `${name}.json`);
12065
+ const pendingPath = (name) => join22(opts.deviceDir, "runners-pending", `${name}.json`);
11892
12066
  const readPending = (name) => {
11893
12067
  try {
11894
12068
  const ids = JSON.parse(readFileSync10(pendingPath(name), "utf8"));
@@ -11902,12 +12076,12 @@ function createDockerProvider(opts) {
11902
12076
  rmSync9(pendingPath(name), { force: true });
11903
12077
  return;
11904
12078
  }
11905
- mkdirSync10(join21(opts.deviceDir, "runners-pending"), { recursive: true });
12079
+ mkdirSync10(join22(opts.deviceDir, "runners-pending"), { recursive: true });
11906
12080
  writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
11907
12081
  };
11908
12082
  const keepSession = async (name, attempt, attemptId) => {
11909
12083
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
11910
- mkdirSync10(join21(archive, ".."), { recursive: true });
12084
+ mkdirSync10(join22(archive, ".."), { recursive: true });
11911
12085
  const partial = `${archive}.${process.pid}.partial`;
11912
12086
  const file = new CappedFile(partial, sessionCap);
11913
12087
  const r = await execIn(
@@ -11924,7 +12098,7 @@ function createDockerProvider(opts) {
11924
12098
  );
11925
12099
  if (r.code === 0 && !file.over) {
11926
12100
  renameSync6(partial, archive);
11927
- writeFileSync6(join21(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
12101
+ writeFileSync6(join22(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
11928
12102
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
11929
12103
  await writeMarker(name, attemptId);
11930
12104
  log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -11938,7 +12112,7 @@ function createDockerProvider(opts) {
11938
12112
  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"
11939
12113
  );
11940
12114
  };
11941
- const stagingDir = (name) => join21(opts.deviceDir, "runners-pending", `${name}.outbox`);
12115
+ const stagingDir = (name) => join22(opts.deviceDir, "runners-pending", `${name}.outbox`);
11942
12116
  const ensureRunning = async (name) => {
11943
12117
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
11944
12118
  if (seen.code !== 0) return false;
@@ -11946,7 +12120,7 @@ function createDockerProvider(opts) {
11946
12120
  return (await docker.run(["start", name])).code === 0;
11947
12121
  };
11948
12122
  const dropArchive = (attemptId) => {
11949
- rmSync9(join21(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
12123
+ rmSync9(join22(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
11950
12124
  recursive: true,
11951
12125
  force: true
11952
12126
  });
@@ -12015,7 +12189,7 @@ function createDockerProvider(opts) {
12015
12189
  if (seen.stdout.trim().split(" ")[0] !== "true")
12016
12190
  await must(["start", name], "starting the container");
12017
12191
  const git = await serveGitCredentials({
12018
- socketPath: join21(socketDir(name), "git.sock"),
12192
+ socketPath: join22(socketDir(name), "git.sock"),
12019
12193
  hosts: opts.gitHosts,
12020
12194
  fill: opts.gitFill,
12021
12195
  log
@@ -12051,7 +12225,7 @@ function createDockerProvider(opts) {
12051
12225
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
12052
12226
  if (existsSync15(archive)) {
12053
12227
  const r = await execIn(name, ["tar", "-x", "-C", CONTAINER_HOME, "-f", "-"], {
12054
- input: createReadStream(archive)
12228
+ input: createReadStream2(archive)
12055
12229
  });
12056
12230
  if (r.code !== 0) {
12057
12231
  log.warn(
@@ -12164,15 +12338,15 @@ function createDockerProvider(opts) {
12164
12338
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
12165
12339
  if (transcripts) {
12166
12340
  const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
12167
- if (kept.length) mkdirSync10(join21(opts.hostStateDir, "transcripts"), { recursive: true });
12168
- for (const f of kept) writeFileSync6(join21(opts.hostStateDir, "transcripts", f.name), f.data);
12341
+ if (kept.length) mkdirSync10(join22(opts.hostStateDir, "transcripts"), { recursive: true });
12342
+ for (const f of kept) writeFileSync6(join22(opts.hostStateDir, "transcripts", f.name), f.data);
12169
12343
  await removeIn(
12170
12344
  name,
12171
12345
  `${CONTAINER_STATE}/transcripts`,
12172
12346
  kept.map((f) => f.name)
12173
12347
  );
12174
12348
  }
12175
- const outboxRel = join21("outbox", encodeURIComponent(attempt.agentId));
12349
+ const outboxRel = join22("outbox", encodeURIComponent(attempt.agentId));
12176
12350
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
12177
12351
  let unpublished = false;
12178
12352
  if (outbox) {
@@ -12198,7 +12372,7 @@ function createDockerProvider(opts) {
12198
12372
  const staged = stagingDir(name);
12199
12373
  mkdirSync10(staged, { recursive: true });
12200
12374
  writeFileSync6(
12201
- join21(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12375
+ join22(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
12202
12376
  `${JSON.stringify(entry)}
12203
12377
  `
12204
12378
  );
@@ -12211,7 +12385,7 @@ function createDockerProvider(opts) {
12211
12385
  if (outbox.complete) {
12212
12386
  const published = publishStaged(
12213
12387
  stagingDir(name),
12214
- join21(opts.hostStateDir, outboxRel),
12388
+ join22(opts.hostStateDir, outboxRel),
12215
12389
  opts.moveFile
12216
12390
  );
12217
12391
  queued = published.published;
@@ -12426,8 +12600,8 @@ import {
12426
12600
  } from "fs";
12427
12601
  import { createRequire as createRequire2 } from "module";
12428
12602
  import { isIP } from "net";
12429
- import { homedir as homedir6, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12430
- import { dirname as dirname9, join as join22, sep as sep4 } from "path";
12603
+ import { homedir as homedir7, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12604
+ import { dirname as dirname9, join as join23, sep as sep4 } from "path";
12431
12605
  import { fileURLToPath as fileURLToPath6 } from "url";
12432
12606
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
12433
12607
  function accountHome() {
@@ -12524,7 +12698,7 @@ function companionStoreWatch(log) {
12524
12698
  return new CompanionStoreWatch({
12525
12699
  log,
12526
12700
  roots: () => {
12527
- const home = homedir6();
12701
+ const home = homedir7();
12528
12702
  return storeWalkRoots([.../* @__PURE__ */ new Set([home, accountHome() ?? home])], cabaneDir());
12529
12703
  }
12530
12704
  });
@@ -12623,8 +12797,8 @@ function emptyHarnessVersions() {
12623
12797
  }
12624
12798
 
12625
12799
  // src/harness-copy.ts
12626
- import { homedir as homedir7 } from "os";
12627
- import { join as join23 } from "path";
12800
+ import { homedir as homedir8 } from "os";
12801
+ import { join as join24 } from "path";
12628
12802
  var HARNESS_LABELS = {
12629
12803
  "claude-code": "Claude Code",
12630
12804
  codex: "Codex",
@@ -12638,7 +12812,7 @@ function failureText(runtime, reason) {
12638
12812
  case "spawn":
12639
12813
  return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
12640
12814
  case "config":
12641
- return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join23(homedir7(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
12815
+ return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join24(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
12642
12816
  case "unreadable":
12643
12817
  return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
12644
12818
  }
@@ -12978,7 +13152,7 @@ import {
12978
13152
  rmSync as rmSync11,
12979
13153
  writeFileSync as writeFileSync8
12980
13154
  } from "fs";
12981
- import { join as join24 } from "path";
13155
+ import { join as join25 } from "path";
12982
13156
  var MAX_ENTRIES = 2e3;
12983
13157
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
12984
13158
  var Outbox = class {
@@ -12991,10 +13165,10 @@ var Outbox = class {
12991
13165
  // Resolved lazily (per call) so tests that swap HOME between
12992
13166
  // cases route writes at the right tmpdir.
12993
13167
  dir() {
12994
- return join24(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
13168
+ return join25(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
12995
13169
  }
12996
13170
  fileFor(turnId, seq) {
12997
- return join24(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
13171
+ return join25(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
12998
13172
  }
12999
13173
  // Persist a commit for later draining. Atomic (temp file + rename) so a
13000
13174
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -13036,7 +13210,7 @@ var Outbox = class {
13036
13210
  const entries = [];
13037
13211
  for (const name of names) {
13038
13212
  if (!name.endsWith(".json")) continue;
13039
- const full = join24(dir2, name);
13213
+ const full = join25(dir2, name);
13040
13214
  try {
13041
13215
  const parsed = JSON.parse(readFileSync12(full, "utf8"));
13042
13216
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -13260,7 +13434,7 @@ var SseSubscriber = class {
13260
13434
  // src/updater.ts
13261
13435
  import { spawn as spawn8 } from "child_process";
13262
13436
  import { copyFileSync as copyFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
13263
- import { dirname as dirname13, join as join28 } from "path";
13437
+ import { dirname as dirname13, join as join29 } from "path";
13264
13438
 
13265
13439
  // src/boot-rule.ts
13266
13440
  import {
@@ -13272,7 +13446,7 @@ import {
13272
13446
  symlinkSync,
13273
13447
  writeFileSync as writeFileSync9
13274
13448
  } from "fs";
13275
- import { dirname as dirname10, join as join25 } from "path";
13449
+ import { dirname as dirname10, join as join26 } from "path";
13276
13450
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
13277
13451
  var MAX_STAGE_ATTEMPTS = 3;
13278
13452
  function superseded(pending, ...running) {
@@ -13305,26 +13479,26 @@ import {
13305
13479
  rmSync as rmSync13,
13306
13480
  symlinkSync as symlinkSync2
13307
13481
  } from "fs";
13308
- import { dirname as dirname11, join as join26 } from "path";
13482
+ import { dirname as dirname11, join as join27 } from "path";
13309
13483
  import { fileURLToPath as fileURLToPath7 } from "url";
13310
13484
  function versionsDir() {
13311
- return join26(cabaneDir(), "versions");
13485
+ return join27(cabaneDir(), "versions");
13312
13486
  }
13313
13487
  function currentLink() {
13314
- return join26(cabaneDir(), "current");
13488
+ return join27(cabaneDir(), "current");
13315
13489
  }
13316
13490
  function bootGuardPath() {
13317
- return join26(cabaneDir(), "boot-guard.mjs");
13491
+ return join27(cabaneDir(), "boot-guard.mjs");
13318
13492
  }
13319
13493
  function versionPackageRoot(version) {
13320
- return join26(versionsDir(), version, "node_modules", "@cabane", "companion");
13494
+ return join27(versionsDir(), version, "node_modules", "@cabane", "companion");
13321
13495
  }
13322
13496
  function ownPackageRoot(moduleUrl = import.meta.url) {
13323
13497
  return dirname11(dirname11(fileURLToPath7(moduleUrl)));
13324
13498
  }
13325
13499
  function packageVersion(root) {
13326
13500
  try {
13327
- const pkg2 = JSON.parse(readFileSync14(join26(root, "package.json"), "utf8"));
13501
+ const pkg2 = JSON.parse(readFileSync14(join27(root, "package.json"), "utf8"));
13328
13502
  return typeof pkg2.version === "string" ? pkg2.version : null;
13329
13503
  } catch {
13330
13504
  return null;
@@ -13333,7 +13507,7 @@ function packageVersion(root) {
13333
13507
  function currentRoot() {
13334
13508
  try {
13335
13509
  const root = realpathSync7(currentLink());
13336
- return existsSync17(join26(root, "dist", "cli.js")) ? root : null;
13510
+ return existsSync17(join27(root, "dist", "cli.js")) ? root : null;
13337
13511
  } catch {
13338
13512
  return null;
13339
13513
  }
@@ -13356,7 +13530,7 @@ function pruneVersions(keep) {
13356
13530
  const removed = [];
13357
13531
  for (const name of readdirSync6(dir2)) {
13358
13532
  if (keep.includes(name)) continue;
13359
- rmSync13(join26(dir2, name), { recursive: true, force: true });
13533
+ rmSync13(join27(dir2, name), { recursive: true, force: true });
13360
13534
  removed.push(name);
13361
13535
  }
13362
13536
  return removed;
@@ -13375,7 +13549,7 @@ function compareVersions(a, b) {
13375
13549
 
13376
13550
  // src/update-state.ts
13377
13551
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync15, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
13378
- import { dirname as dirname12, join as join27 } from "path";
13552
+ import { dirname as dirname12, join as join28 } from "path";
13379
13553
  import { z as z18 } from "zod";
13380
13554
  var resultSchema = z18.object({
13381
13555
  kind: z18.enum(["updated", "rolled_back", "failed"]),
@@ -13399,7 +13573,7 @@ var stateSchema = z18.object({
13399
13573
  failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
13400
13574
  });
13401
13575
  function updateStatePath(dir2 = cabaneDir()) {
13402
- return join27(dir2, "update.json");
13576
+ return join28(dir2, "update.json");
13403
13577
  }
13404
13578
  function readUpdateState(path = updateStatePath()) {
13405
13579
  try {
@@ -13448,7 +13622,7 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
13448
13622
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
13449
13623
  }
13450
13624
  function writeBootGuard(root = ownPackageRoot()) {
13451
- const built = join28(root, "dist", "boot-guard.js");
13625
+ const built = join29(root, "dist", "boot-guard.js");
13452
13626
  if (!existsSync18(built)) return false;
13453
13627
  const target = bootGuardPath();
13454
13628
  mkdirSync16(dirname13(target), { recursive: true });
@@ -13488,17 +13662,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve2) => {
13488
13662
  async function stageVersion(version, harnesses, deps = {}) {
13489
13663
  const run = deps.run ?? runProcess;
13490
13664
  const node = deps.execPath ?? process.execPath;
13491
- const target = join28(versionsDir(), version);
13665
+ const target = join29(versionsDir(), version);
13492
13666
  const placed = versionPackageRoot(version);
13493
13667
  if (existsSync18(placed)) {
13494
13668
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
13495
13669
  return { ok: true, root: placed };
13496
13670
  }
13497
- const npm = join28(dirname13(node), "npm");
13671
+ const npm = join29(dirname13(node), "npm");
13498
13672
  if (!existsSync18(npm))
13499
13673
  return { ok: false, reason: `there is no npm beside Node at ${dirname13(node)}` };
13500
13674
  mkdirSync16(versionsDir(), { recursive: true });
13501
- const staging = join28(versionsDir(), `.${version}.staging-${process.pid}`);
13675
+ const staging = join29(versionsDir(), `.${version}.staging-${process.pid}`);
13502
13676
  rmSync14(staging, { recursive: true, force: true });
13503
13677
  const installed = await run(
13504
13678
  npm,
@@ -13527,7 +13701,7 @@ async function stageVersion(version, harnesses, deps = {}) {
13527
13701
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
13528
13702
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
13529
13703
  }
13530
- const root = join28(staging, "node_modules", "@cabane", "companion");
13704
+ const root = join29(staging, "node_modules", "@cabane", "companion");
13531
13705
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
13532
13706
  if (verdict) {
13533
13707
  rmSync14(staging, { recursive: true, force: true });
@@ -13539,7 +13713,7 @@ async function stageVersion(version, harnesses, deps = {}) {
13539
13713
  }
13540
13714
  async function verifyInstall(root, version, harnesses, deps) {
13541
13715
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
13542
- const answered = await deps.run(deps.execPath, [join28(root, "dist", "bin.js"), "--version"], {
13716
+ const answered = await deps.run(deps.execPath, [join29(root, "dist", "bin.js"), "--version"], {
13543
13717
  timeoutMs: 6e4,
13544
13718
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
13545
13719
  });
@@ -13547,7 +13721,7 @@ async function verifyInstall(root, version, harnesses, deps) {
13547
13721
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
13548
13722
  }
13549
13723
  if (harnesses.length === 0) return null;
13550
- const probed = await deps.run(deps.execPath, [join28(root, "dist", "postinstall.js"), "--json"], {
13724
+ const probed = await deps.run(deps.execPath, [join29(root, "dist", "postinstall.js"), "--json"], {
13551
13725
  timeoutMs: 6e4
13552
13726
  });
13553
13727
  let resolutions = {};
@@ -15348,9 +15522,9 @@ function handleUncaught(log, err, origin) {
15348
15522
 
15349
15523
  // src/crash-marker.ts
15350
15524
  import { existsSync as existsSync19, mkdirSync as mkdirSync17, readFileSync as readFileSync16, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "fs";
15351
- import { join as join29 } from "path";
15525
+ import { join as join30 } from "path";
15352
15526
  function crashMarkerPath() {
15353
- return join29(cabaneDir(), "last-error.json");
15527
+ return join30(cabaneDir(), "last-error.json");
15354
15528
  }
15355
15529
  function recordCrash(rec) {
15356
15530
  try {
@@ -15370,7 +15544,7 @@ function clearCrash() {
15370
15544
  // src/stray-companion.ts
15371
15545
  import { spawnSync } from "child_process";
15372
15546
  import { readFileSync as readFileSync17 } from "fs";
15373
- import { join as join30 } from "path";
15547
+ import { join as join31 } from "path";
15374
15548
  var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
15375
15549
  var START = /\sstart(\s|$)/;
15376
15550
  function companionStarts(psOutput) {
@@ -15387,7 +15561,7 @@ function companionStarts(psOutput) {
15387
15561
  }
15388
15562
  function stateDirOf(env) {
15389
15563
  const home = env.CABANE_COMPANION_HOME || env.HOME;
15390
- return home ? join30(home, ".cabane") : null;
15564
+ return home ? join31(home, ".cabane") : null;
15391
15565
  }
15392
15566
  function envFromPsE(output, command) {
15393
15567
  const line = output.replace(/\n$/, "");