@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/cli.js CHANGED
@@ -2084,9 +2084,15 @@ var init_turn_request = __esm({
2084
2084
  resolvedMcpServersSchema = z9.record(z9.string(), resolvedMcpServerSchema);
2085
2085
  hostInjectedServersSchema = z9.record(z9.string(), z9.unknown());
2086
2086
  turnRequestSchema = z9.object({
2087
- // Server-composed system prompt (core + capability prose + adapter addendum +
2088
- // charter). One string to the adapter.
2087
+ // Server-composed standing instructions: the core segment (body, tool naming, identity, agent
2088
+ // charter) and, after it, the conversation segment (workspace and room charters, skill indexes,
2089
+ // roster). One string to the adapter, current as of this dispatch.
2089
2090
  systemPrompt: z9.string(),
2091
+ // The same text as its two ordered segments, so an adapter that writes standing text into the
2092
+ // conversation (Codex's input preamble) can tell which half changed. `systemPrompt` is
2093
+ // `joinStanding(standing)`. Optional: absent from an API older than the split, where
2094
+ // `systemPrompt` is the core alone and the conversation material rides `prompt`.
2095
+ standing: z9.object({ core: z9.string(), conversation: z9.string() }).optional(),
2090
2096
  // Server-composed per-turn user text (anchor reminder + the triggering message).
2091
2097
  prompt: z9.string(),
2092
2098
  // The multi-block user-message body (text + vision).
@@ -2136,6 +2142,22 @@ var init_turn_request = __esm({
2136
2142
  }
2137
2143
  });
2138
2144
 
2145
+ // packages/agent-runtime/src/standing.ts
2146
+ function joinStanding(segments) {
2147
+ const conversation = segments.conversation.trim();
2148
+ return conversation.length > 0 ? `${segments.core}${STANDING_SEPARATOR}${conversation}` : segments.core;
2149
+ }
2150
+ function standingSegments(req) {
2151
+ return req.standing ?? { core: req.systemPrompt, conversation: "" };
2152
+ }
2153
+ var STANDING_SEPARATOR;
2154
+ var init_standing = __esm({
2155
+ "packages/agent-runtime/src/standing.ts"() {
2156
+ "use strict";
2157
+ STANDING_SEPARATOR = "\n\n---\n\n";
2158
+ }
2159
+ });
2160
+
2139
2161
  // packages/agent-runtime/src/conformance.ts
2140
2162
  import { z as z10 } from "zod";
2141
2163
  var conformanceFixtureSchema;
@@ -4169,7 +4191,6 @@ async function* decodeOpencodeStream(events, ctx2) {
4169
4191
  let reason;
4170
4192
  let settled = false;
4171
4193
  const userMessageIds = /* @__PURE__ */ new Set();
4172
- if (ctx2.skipMessageId) userMessageIds.add(ctx2.skipMessageId);
4173
4194
  for await (const ev of events) {
4174
4195
  if (ctx2.signal.aborted) return;
4175
4196
  if (ev.type === "message.updated") {
@@ -4345,8 +4366,6 @@ function buildRunSpec(req, resumeSessionId) {
4345
4366
  parts: buildParts(req),
4346
4367
  tools,
4347
4368
  allowAllHostTools,
4348
- // Fresh session → mint + send a client id; resumed → let opencode mint it.
4349
- sendClientMessageId: resumeSessionId == null,
4350
4369
  ...dialect.agent ? { agent: dialect.agent } : {},
4351
4370
  mcp: buildMcp(req),
4352
4371
  // Every server this turn installs with a credential resolved into it: a remote one's
@@ -4521,14 +4540,12 @@ function createHttpOpencodeTransport(opts) {
4521
4540
  created = true;
4522
4541
  }
4523
4542
  await installMcp(doFetch, base, spec, signal);
4524
- const userMessageId = spec.sendClientMessageId ? newOpencodeMessageId() : null;
4525
4543
  const promptRes = await doFetch(
4526
4544
  `${base}/session/${encodeURIComponent(sessionId)}/prompt_async`,
4527
4545
  {
4528
4546
  method: "POST",
4529
4547
  headers: { "content-type": "application/json" },
4530
4548
  body: JSON.stringify({
4531
- ...userMessageId ? { messageID: userMessageId } : {},
4532
4549
  ...spec.model ? { model: spec.model } : {},
4533
4550
  system: spec.systemPrompt,
4534
4551
  parts: spec.parts,
@@ -4554,7 +4571,6 @@ function createHttpOpencodeTransport(opts) {
4554
4571
  sessionId,
4555
4572
  created,
4556
4573
  directory: spec.directory,
4557
- userMessageId,
4558
4574
  // The lock is released when this stream finishes draining.
4559
4575
  events: releaseAfter(parseSseStream(eventRes.body, sessionId, signal), release)
4560
4576
  };
@@ -4657,9 +4673,6 @@ function belongsToSession(ev, sessionId) {
4657
4673
  if (typeof infoSid === "string") return infoSid === sessionId;
4658
4674
  return true;
4659
4675
  }
4660
- function newOpencodeMessageId() {
4661
- return `msg_${crypto.randomUUID().replace(/-/g, "")}`;
4662
- }
4663
4676
  function trimSlash(s) {
4664
4677
  return s.endsWith("/") ? s.slice(0, -1) : s;
4665
4678
  }
@@ -4723,8 +4736,7 @@ function createOpencodeAdapter(deps = {}) {
4723
4736
  }
4724
4737
  yield* decodeOpencodeStream(result.events, {
4725
4738
  signal,
4726
- sessionId: result.sessionId,
4727
- skipMessageId: result.userMessageId
4739
+ sessionId: result.sessionId
4728
4740
  });
4729
4741
  }
4730
4742
  };
@@ -5149,8 +5161,7 @@ var init_conformance3 = __esm({
5149
5161
  ]
5150
5162
  },
5151
5163
  {
5152
- // On a resumed turn the transport mints no client message id (doing so no-ops the turn), so the
5153
- // decoder gets no `skipMessageId`. It learns the user-message id from the stream — the
5164
+ // On resume, as on a fresh turn, the decoder learns the user-message id from the stream:
5154
5165
  // `message.updated` user frame precedes the echoed part — and drops the echo, so the injected
5155
5166
  // per-turn context can't leak into Steps. Only the reply surfaces; no session event.
5156
5167
  name: "session resume \u2014 prompt echo filtered via stream-derived user id",
@@ -5385,7 +5396,14 @@ function fingerprintPrompt(prompt) {
5385
5396
  return `${prompt.length}.${(fnv >>> 0).toString(36)}.${(djb >>> 0).toString(36)}`;
5386
5397
  }
5387
5398
  function encodeSession3(state) {
5388
- return JSON.stringify(state);
5399
+ return JSON.stringify({
5400
+ threadId: state.threadId,
5401
+ cwd: state.cwd,
5402
+ promptFingerprint: state.promptFingerprint,
5403
+ conversationFingerprint: state.conversationFingerprint ?? null,
5404
+ compactions: state.compactions ?? null,
5405
+ usageBaseline: state.usageBaseline
5406
+ });
5389
5407
  }
5390
5408
  function decideResume3(stored, currentCwd) {
5391
5409
  if (!stored) return { fresh: true, reason: "no_session" };
@@ -5406,6 +5424,8 @@ function decideResume3(stored, currentCwd) {
5406
5424
  return {
5407
5425
  resume: state.threadId,
5408
5426
  promptFingerprint: typeof state.promptFingerprint === "string" ? state.promptFingerprint : null,
5427
+ conversationFingerprint: typeof state.conversationFingerprint === "string" ? state.conversationFingerprint : null,
5428
+ compactions: typeof state.compactions === "number" && Number.isInteger(state.compactions) && state.compactions >= 0 ? state.compactions : null,
5409
5429
  usageBaseline: readUsageBaseline(state.usageBaseline)
5410
5430
  };
5411
5431
  }
@@ -5443,6 +5463,7 @@ async function* decodeCodexStream(events, ctx2) {
5443
5463
  let usage;
5444
5464
  let cumulativeUsage;
5445
5465
  let threadIdForSession = ctx2.resumedThreadId;
5466
+ let lastState = ctx2.storedState ?? null;
5446
5467
  let settled = false;
5447
5468
  function* flushInterim() {
5448
5469
  const sealed = sealHeld2(held, false);
@@ -5458,14 +5479,14 @@ async function* decodeCodexStream(events, ctx2) {
5458
5479
  const state = encodeSession3({
5459
5480
  threadId,
5460
5481
  cwd: ctx2.cwd ?? "",
5461
- promptFingerprint: ctx2.promptFingerprint ?? null,
5462
- // The baseline this turn inherited, carried through unchanged. This early write records the
5463
- // thread id even if the turn dies mid-stream; it can't yet know what the turn will spend. The
5464
- // closing write records the new baseline.
5482
+ promptFingerprint: ctx2.heldStanding?.promptFingerprint ?? null,
5483
+ conversationFingerprint: ctx2.heldStanding?.conversationFingerprint ?? null,
5484
+ compactions: ctx2.heldStanding?.compactions ?? null,
5465
5485
  usageBaseline: ctx2.usageBaseline ?? null
5466
5486
  });
5467
5487
  if (threadId !== ctx2.resumedThreadId || state !== ctx2.storedState) {
5468
5488
  sessionEmitted = true;
5489
+ lastState = state;
5469
5490
  yield {
5470
5491
  type: "session",
5471
5492
  state,
@@ -5564,14 +5585,16 @@ async function* decodeCodexStream(events, ctx2) {
5564
5585
  if (sealed) yield sealed;
5565
5586
  reason = "no_terminal";
5566
5587
  }
5567
- if (ok && cumulativeUsage && threadIdForSession) {
5588
+ if (ok && threadIdForSession) {
5568
5589
  const closing = encodeSession3({
5569
5590
  threadId: threadIdForSession,
5570
5591
  cwd: ctx2.cwd ?? "",
5571
5592
  promptFingerprint: ctx2.promptFingerprint ?? null,
5572
- usageBaseline: cumulativeUsage
5593
+ conversationFingerprint: ctx2.conversationFingerprint ?? null,
5594
+ compactions: ctx2.compactions ?? null,
5595
+ usageBaseline: cumulativeUsage ?? ctx2.usageBaseline ?? null
5573
5596
  });
5574
- yield { type: "session", state: closing };
5597
+ if (closing !== lastState) yield { type: "session", state: closing };
5575
5598
  }
5576
5599
  const resolvedConfig = ctx2.resolvedReasoningEffort ? { reasoningEffort: ctx2.resolvedReasoningEffort } : void 0;
5577
5600
  yield {
@@ -5651,15 +5674,27 @@ var init_model3 = __esm({
5651
5674
  });
5652
5675
 
5653
5676
  // packages/agent-runtime/src/codex/run-spec.ts
5654
- function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
5677
+ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadStanding = null, imagePaths = []) {
5655
5678
  const { policy, config } = req;
5656
5679
  const directory = req.local.cwd ?? "";
5657
5680
  const dialect = readCodexDialect(config.runtimeOptions);
5658
5681
  const baseInstructionsFile = !policy.hostFs && instructionsFile ? instructionsFile : null;
5659
5682
  const model = config.model ? parseCodexModel(config.model) : null;
5660
- const promptFingerprint = fingerprintPrompt(req.systemPrompt);
5661
- const threadHasThisPrompt = resumeThreadId !== null && threadPromptFingerprint !== null && threadPromptFingerprint === promptFingerprint;
5662
- const promptRidesInput = baseInstructionsFile === null && !threadHasThisPrompt;
5683
+ const segments = standingSegments(req);
5684
+ const conversationText = segments.conversation.trim();
5685
+ const fingerprints = {
5686
+ core: fingerprintPrompt(segments.core),
5687
+ conversation: conversationText.length > 0 ? fingerprintPrompt(conversationText) : null
5688
+ };
5689
+ const resumed = resumeThreadId !== null;
5690
+ const held = resumed ? threadStanding : null;
5691
+ const viaInput = baseInstructionsFile === null;
5692
+ const preamble = {
5693
+ core: viaInput && (held?.core ?? null) !== fingerprints.core,
5694
+ // Compared both ways: a held segment that is now empty differs from it, and nothing held
5695
+ // equals nothing to send.
5696
+ conversation: viaInput && (held?.conversation ?? null) !== fingerprints.conversation
5697
+ };
5663
5698
  return {
5664
5699
  resumeThreadId,
5665
5700
  directory,
@@ -5668,9 +5703,9 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
5668
5703
  skipGitRepoCheck: true,
5669
5704
  ...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
5670
5705
  baseInstructionsFile,
5671
- input: withImages(buildInput(req, promptRidesInput), imagePaths),
5672
- promptRidesInput,
5673
- promptFingerprint,
5706
+ input: withImages(buildInput(req, segments, preamble, resumed), imagePaths),
5707
+ preamble,
5708
+ fingerprints,
5674
5709
  config: buildConfig(req, baseInstructionsFile),
5675
5710
  secretEnv: codexSecretEnv(req)
5676
5711
  };
@@ -5686,12 +5721,24 @@ function codexSecretEnv(req) {
5686
5721
  }
5687
5722
  return Object.keys(env).length > 0 ? env : null;
5688
5723
  }
5689
- function buildInput(req, promptRidesInput) {
5724
+ function buildInput(req, segments, preamble, resumed) {
5690
5725
  const userText = req.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
5691
5726
  const body = userText.trim().length > 0 ? userText : req.prompt;
5692
- if (!promptRidesInput) return body;
5693
- const system = req.systemPrompt.trim();
5694
- return system.length > 0 ? `${system}
5727
+ const standing = [];
5728
+ if (!resumed) {
5729
+ if (preamble.core) standing.push(joinStanding(segments).trim());
5730
+ } else {
5731
+ if (preamble.core) standing.push(`${CORE_REPLACES}
5732
+
5733
+ ${segments.core.trim()}`);
5734
+ if (preamble.conversation) {
5735
+ standing.push(`${CONVERSATION_REPLACES}
5736
+
5737
+ ${segments.conversation.trim()}`.trim());
5738
+ }
5739
+ }
5740
+ const lead = standing.filter((s) => s.length > 0);
5741
+ return lead.length > 0 ? `${lead.join("\n\n---\n\n")}
5695
5742
 
5696
5743
  ${body}` : body;
5697
5744
  }
@@ -5777,10 +5824,11 @@ function buildConfig(req, baseInstructionsFile) {
5777
5824
  function isStringRecord2(v) {
5778
5825
  return !!v && typeof v === "object" && Object.values(v).every((x) => typeof x === "string");
5779
5826
  }
5780
- var CABANE_MCP_SERVER3, TURN_CONTROL_MCP_SERVER2, ACTIVE_CONVERSATION_HEADER3, ENV_ENVELOPE_KEYS;
5827
+ var CABANE_MCP_SERVER3, TURN_CONTROL_MCP_SERVER2, ACTIVE_CONVERSATION_HEADER3, ENV_ENVELOPE_KEYS, CORE_REPLACES, CONVERSATION_REPLACES;
5781
5828
  var init_run_spec2 = __esm({
5782
5829
  "packages/agent-runtime/src/codex/run-spec.ts"() {
5783
5830
  "use strict";
5831
+ init_standing();
5784
5832
  init_policy3();
5785
5833
  init_model3();
5786
5834
  init_session3();
@@ -5795,6 +5843,8 @@ var init_run_spec2 = __esm({
5795
5843
  "CABANE_CONVERSATION_TITLE",
5796
5844
  "CABANE_HOST_ACCESS"
5797
5845
  ];
5846
+ 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.";
5847
+ 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.";
5798
5848
  }
5799
5849
  });
5800
5850
 
@@ -5933,7 +5983,6 @@ function createCodexAdapter(deps = {}) {
5933
5983
  const directory = req.local.cwd ?? "";
5934
5984
  const decision = decideResume3(req.session, directory);
5935
5985
  const resumeThreadId = "resume" in decision ? decision.resume : null;
5936
- const threadPromptFingerprint = "resume" in decision ? decision.promptFingerprint : null;
5937
5986
  const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
5938
5987
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
5939
5988
  deps.onWarn?.(
@@ -5950,6 +5999,32 @@ function createCodexAdapter(deps = {}) {
5950
5999
  degraded = false;
5951
6000
  }
5952
6001
  }
6002
+ let compactions = 0;
6003
+ let threadStanding = null;
6004
+ if ("resume" in decision) {
6005
+ compactions = deps.countCompactions ? await deps.countCompactions(decision.resume) : null;
6006
+ if (signal.aborted) return;
6007
+ if (compactions === null) {
6008
+ deps.onWarn?.(
6009
+ "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions (ENG210)",
6010
+ { threadId: decision.resume }
6011
+ );
6012
+ } else if (decision.compactions === compactions) {
6013
+ threadStanding = {
6014
+ core: decision.promptFingerprint,
6015
+ conversation: decision.conversationFingerprint
6016
+ };
6017
+ } else if (decision.compactions !== null && compactions > decision.compactions) {
6018
+ deps.onWarn?.(
6019
+ "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions (ENG210)",
6020
+ {
6021
+ threadId: decision.resume,
6022
+ compactions,
6023
+ compactionsSince: compactions - decision.compactions
6024
+ }
6025
+ );
6026
+ }
6027
+ }
5953
6028
  let instructions = null;
5954
6029
  if (!req.policy.hostFs) {
5955
6030
  if (!deps.writeInstructionsFile) {
@@ -5994,7 +6069,7 @@ function createCodexAdapter(deps = {}) {
5994
6069
  req,
5995
6070
  resumeThreadId,
5996
6071
  instructions?.path ?? null,
5997
- threadPromptFingerprint,
6072
+ threadStanding,
5998
6073
  images?.paths ?? []
5999
6074
  );
6000
6075
  if (spec.model === null) {
@@ -6009,10 +6084,18 @@ function createCodexAdapter(deps = {}) {
6009
6084
  cwd: req.local.cwd,
6010
6085
  resumedThreadId: resumeThreadId,
6011
6086
  degraded,
6012
- // Which prompt the thread carries once this turn lands. If we sent one, it's that one — the
6013
- // later copy is what the model reads. If we withheld it, the thread carries what it did. Read
6014
- // off the spec, so the record can't disagree with what was sent.
6015
- promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
6087
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6088
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6089
+ // the spec, so the record can't disagree with what was sent.
6090
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6091
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6092
+ compactions,
6093
+ // What the thread held before this turn, for the write at `thread.started`.
6094
+ heldStanding: {
6095
+ promptFingerprint: threadStanding?.core ?? null,
6096
+ conversationFingerprint: threadStanding?.conversation ?? null,
6097
+ compactions: threadStanding ? compactions : null
6098
+ },
6016
6099
  storedState: req.session,
6017
6100
  usageBaseline: threadUsageBaseline,
6018
6101
  // Codex reports no model in-stream, so record what the thread was started with (null on the
@@ -6083,7 +6166,7 @@ function makeRequest3(overrides2 = {}) {
6083
6166
  ...overrides2
6084
6167
  };
6085
6168
  }
6086
- var ABORT_SENTINEL3, NEW_THREAD_ID, BOUNDED_NOTICE2, COMPANION_POLICY2, DIR2, PROMPT, threadStarted, agentMessage, reasoning, toolFrame, turnCompleted, turnFailed, fatalError, errorItem, OP_SUCCESS, OP_FAILURE, sessionEvent3, CODEX_CONFORMANCE_FIXTURES;
6169
+ var ABORT_SENTINEL3, NEW_THREAD_ID, BOUNDED_NOTICE2, COMPANION_POLICY2, DIR2, PROMPT, threadStarted, agentMessage, reasoning, toolFrame, turnCompleted, turnFailed, fatalError, errorItem, OP_SUCCESS, OP_FAILURE, startedSession, closingSession, CODEX_CONFORMANCE_FIXTURES;
6087
6170
  var init_conformance4 = __esm({
6088
6171
  "packages/agent-runtime/src/codex/conformance.ts"() {
6089
6172
  "use strict";
@@ -6138,11 +6221,21 @@ var init_conformance4 = __esm({
6138
6221
  message: "files.read returned more than the result ceiling"
6139
6222
  }
6140
6223
  };
6141
- sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint = fingerprintPrompt(PROMPT), usageBaseline = null) => ({
6224
+ startedSession = (threadId, cwd = DIR2, degraded = false) => ({
6142
6225
  type: "session",
6143
- state: encodeSession3({ threadId, cwd, promptFingerprint, usageBaseline }),
6226
+ state: encodeSession3({ threadId, cwd, promptFingerprint: null, usageBaseline: null }),
6144
6227
  ...degraded ? { degraded: true } : {}
6145
6228
  });
6229
+ closingSession = (threadId, usageBaseline = null) => ({
6230
+ type: "session",
6231
+ state: encodeSession3({
6232
+ threadId,
6233
+ cwd: DIR2,
6234
+ promptFingerprint: fingerprintPrompt(PROMPT),
6235
+ usageBaseline,
6236
+ compactions: 0
6237
+ })
6238
+ });
6146
6239
  CODEX_CONFORMANCE_FIXTURES = [
6147
6240
  {
6148
6241
  // A plain text reply: the held agent message seals as the terminal final at `turn.completed`;
@@ -6165,11 +6258,11 @@ var init_conformance4 = __esm({
6165
6258
  })
6166
6259
  ],
6167
6260
  expected: [
6168
- sessionEvent3(NEW_THREAD_ID),
6261
+ startedSession(NEW_THREAD_ID),
6169
6262
  { type: "text", body: "Hello there.", terminal: true },
6170
6263
  // The closing session write — the thread's ending totals, recorded as the baseline the next turn
6171
6264
  // subtracts. A fresh thread, so the `result` usage below is the frame verbatim.
6172
- sessionEvent3(NEW_THREAD_ID, DIR2, false, fingerprintPrompt(PROMPT), {
6265
+ closingSession(NEW_THREAD_ID, {
6173
6266
  inputTokens: 100,
6174
6267
  outputTokens: 50,
6175
6268
  cacheReadTokens: 20,
@@ -6216,7 +6309,7 @@ var init_conformance4 = __esm({
6216
6309
  turnCompleted()
6217
6310
  ],
6218
6311
  expected: [
6219
- sessionEvent3(NEW_THREAD_ID),
6312
+ startedSession(NEW_THREAD_ID),
6220
6313
  { type: "text", body: "Reading your notes.", terminal: false },
6221
6314
  {
6222
6315
  type: "tool",
@@ -6236,6 +6329,7 @@ var init_conformance4 = __esm({
6236
6329
  result: [{ type: "text", text: "sleep early" }]
6237
6330
  },
6238
6331
  { type: "text", body: "Here's the summary.", terminal: true },
6332
+ closingSession(NEW_THREAD_ID),
6239
6333
  { type: "result", ok: true }
6240
6334
  ]
6241
6335
  },
@@ -6269,7 +6363,7 @@ var init_conformance4 = __esm({
6269
6363
  turnCompleted()
6270
6364
  ],
6271
6365
  expected: [
6272
- sessionEvent3(NEW_THREAD_ID),
6366
+ startedSession(NEW_THREAD_ID),
6273
6367
  {
6274
6368
  type: "tool",
6275
6369
  id: "query-success",
@@ -6287,6 +6381,7 @@ var init_conformance4 = __esm({
6287
6381
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
6288
6382
  result: OP_SUCCESS
6289
6383
  },
6384
+ closingSession(NEW_THREAD_ID),
6290
6385
  { type: "result", ok: true }
6291
6386
  ]
6292
6387
  },
@@ -6322,7 +6417,7 @@ var init_conformance4 = __esm({
6322
6417
  turnCompleted()
6323
6418
  ],
6324
6419
  expected: [
6325
- sessionEvent3(NEW_THREAD_ID),
6420
+ startedSession(NEW_THREAD_ID),
6326
6421
  {
6327
6422
  type: "tool",
6328
6423
  id: "query-failed",
@@ -6340,6 +6435,7 @@ var init_conformance4 = __esm({
6340
6435
  input: { op: "files.read", args: { path: "/ABOUT.md" } },
6341
6436
  result: OP_FAILURE
6342
6437
  },
6438
+ closingSession(NEW_THREAD_ID),
6343
6439
  { type: "result", ok: true }
6344
6440
  ]
6345
6441
  },
@@ -6363,7 +6459,7 @@ var init_conformance4 = __esm({
6363
6459
  turnCompleted()
6364
6460
  ],
6365
6461
  expected: [
6366
- sessionEvent3(NEW_THREAD_ID),
6462
+ startedSession(NEW_THREAD_ID),
6367
6463
  {
6368
6464
  type: "tool",
6369
6465
  id: "mcp-failed",
@@ -6381,6 +6477,7 @@ var init_conformance4 = __esm({
6381
6477
  input: { q: "receipt" },
6382
6478
  result: "connection closed"
6383
6479
  },
6480
+ closingSession(NEW_THREAD_ID),
6384
6481
  { type: "result", ok: true }
6385
6482
  ]
6386
6483
  },
@@ -6406,7 +6503,7 @@ var init_conformance4 = __esm({
6406
6503
  turnCompleted()
6407
6504
  ],
6408
6505
  expected: [
6409
- sessionEvent3(NEW_THREAD_ID),
6506
+ startedSession(NEW_THREAD_ID),
6410
6507
  {
6411
6508
  type: "tool",
6412
6509
  id: "mcp-image",
@@ -6424,6 +6521,7 @@ var init_conformance4 = __esm({
6424
6521
  input: {},
6425
6522
  result: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }]
6426
6523
  },
6524
+ closingSession(NEW_THREAD_ID),
6427
6525
  { type: "result", ok: true }
6428
6526
  ]
6429
6527
  },
@@ -6464,7 +6562,7 @@ var init_conformance4 = __esm({
6464
6562
  turnCompleted()
6465
6563
  ],
6466
6564
  expected: [
6467
- sessionEvent3(NEW_THREAD_ID),
6565
+ startedSession(NEW_THREAD_ID),
6468
6566
  { type: "tool", id: "t1", name: "list", phase: "start", summary: "", input: {} },
6469
6567
  {
6470
6568
  type: "tool",
@@ -6475,6 +6573,7 @@ var init_conformance4 = __esm({
6475
6573
  input: { path: "/product/tasks" },
6476
6574
  result: [{ type: "text", text: "CT481.md" }]
6477
6575
  },
6576
+ closingSession(NEW_THREAD_ID),
6478
6577
  { type: "result", ok: true }
6479
6578
  ]
6480
6579
  },
@@ -6492,7 +6591,7 @@ var init_conformance4 = __esm({
6492
6591
  turnCompleted()
6493
6592
  ],
6494
6593
  expected: [
6495
- sessionEvent3(NEW_THREAD_ID),
6594
+ startedSession(NEW_THREAD_ID),
6496
6595
  {
6497
6596
  type: "tool",
6498
6597
  id: "ws1",
@@ -6509,6 +6608,7 @@ var init_conformance4 = __esm({
6509
6608
  summary: "best pizza in nyc",
6510
6609
  input: { query: "best pizza in nyc" }
6511
6610
  },
6611
+ closingSession(NEW_THREAD_ID),
6512
6612
  { type: "result", ok: true }
6513
6613
  ]
6514
6614
  },
@@ -6524,7 +6624,7 @@ var init_conformance4 = __esm({
6524
6624
  turnCompleted()
6525
6625
  ],
6526
6626
  expected: [
6527
- sessionEvent3(NEW_THREAD_ID),
6627
+ startedSession(NEW_THREAD_ID),
6528
6628
  {
6529
6629
  type: "tool",
6530
6630
  id: "ws2",
@@ -6541,6 +6641,7 @@ var init_conformance4 = __esm({
6541
6641
  summary: "",
6542
6642
  input: { query: "" }
6543
6643
  },
6644
+ closingSession(NEW_THREAD_ID),
6544
6645
  { type: "result", ok: true }
6545
6646
  ]
6546
6647
  },
@@ -6571,7 +6672,7 @@ var init_conformance4 = __esm({
6571
6672
  turnCompleted()
6572
6673
  ],
6573
6674
  expected: [
6574
- sessionEvent3(NEW_THREAD_ID),
6675
+ startedSession(NEW_THREAD_ID),
6575
6676
  { type: "text", body: "Working on it.", terminal: false },
6576
6677
  {
6577
6678
  type: "tool",
@@ -6603,7 +6704,7 @@ var init_conformance4 = __esm({
6603
6704
  turnFailed("provider exploded")
6604
6705
  ],
6605
6706
  expected: [
6606
- sessionEvent3(NEW_THREAD_ID),
6707
+ startedSession(NEW_THREAD_ID),
6607
6708
  { type: "text", body: "Partial work.", terminal: false },
6608
6709
  { type: "runtime_notice", body: "provider exploded" },
6609
6710
  { type: "result", ok: false, reason: "error:provider exploded" }
@@ -6620,7 +6721,7 @@ var init_conformance4 = __esm({
6620
6721
  fatalError("stream died")
6621
6722
  ],
6622
6723
  expected: [
6623
- sessionEvent3(NEW_THREAD_ID),
6724
+ startedSession(NEW_THREAD_ID),
6624
6725
  { type: "text", body: "Starting.", terminal: false },
6625
6726
  { type: "runtime_notice", body: "stream died" },
6626
6727
  { type: "result", ok: false, reason: "error:stream died" }
@@ -6633,7 +6734,7 @@ var init_conformance4 = __esm({
6633
6734
  request: makeRequest3(),
6634
6735
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
6635
6736
  expected: [
6636
- sessionEvent3(NEW_THREAD_ID),
6737
+ startedSession(NEW_THREAD_ID),
6637
6738
  { type: "runtime_notice", body: "429 Too Many Requests" },
6638
6739
  { type: "result", ok: false, reason: "rate_limited" }
6639
6740
  ]
@@ -6643,7 +6744,7 @@ var init_conformance4 = __esm({
6643
6744
  request: makeRequest3(),
6644
6745
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("503 Service Unavailable")],
6645
6746
  expected: [
6646
- sessionEvent3(NEW_THREAD_ID),
6747
+ startedSession(NEW_THREAD_ID),
6647
6748
  { type: "runtime_notice", body: "503 Service Unavailable" },
6648
6749
  { type: "result", ok: false, reason: "server_error" }
6649
6750
  ]
@@ -6656,7 +6757,7 @@ var init_conformance4 = __esm({
6656
6757
  turnFailed("You've hit your session limit \xB7 resets 8:20pm (UTC)")
6657
6758
  ],
6658
6759
  expected: [
6659
- sessionEvent3(NEW_THREAD_ID),
6760
+ startedSession(NEW_THREAD_ID),
6660
6761
  {
6661
6762
  type: "runtime_notice",
6662
6763
  body: "You've hit your session limit \xB7 resets 8:20pm (UTC)"
@@ -6669,7 +6770,7 @@ var init_conformance4 = __esm({
6669
6770
  request: makeRequest3(),
6670
6771
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("401 Unauthorized: invalid api key")],
6671
6772
  expected: [
6672
- sessionEvent3(NEW_THREAD_ID),
6773
+ startedSession(NEW_THREAD_ID),
6673
6774
  { type: "runtime_notice", body: "401 Unauthorized: invalid api key" },
6674
6775
  { type: "result", ok: false, reason: "auth_expired" }
6675
6776
  ]
@@ -6681,7 +6782,7 @@ var init_conformance4 = __esm({
6681
6782
  request: makeRequest3(),
6682
6783
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
6683
6784
  expected: [
6684
- sessionEvent3(NEW_THREAD_ID),
6785
+ startedSession(NEW_THREAD_ID),
6685
6786
  { type: "runtime_notice", body: "something local broke" },
6686
6787
  { type: "result", ok: false, reason: "error:something local broke" }
6687
6788
  ]
@@ -6694,7 +6795,7 @@ var init_conformance4 = __esm({
6694
6795
  turnFailed(` ${BOUNDED_NOTICE2}${"y".repeat(20)} `)
6695
6796
  ],
6696
6797
  expected: [
6697
- sessionEvent3(NEW_THREAD_ID),
6798
+ startedSession(NEW_THREAD_ID),
6698
6799
  { type: "runtime_notice", body: BOUNDED_NOTICE2 },
6699
6800
  { type: "result", ok: false, reason: "usage_capped" }
6700
6801
  ]
@@ -6705,26 +6806,32 @@ var init_conformance4 = __esm({
6705
6806
  name: "empty failure text emits no runtime notice",
6706
6807
  request: makeRequest3(),
6707
6808
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
6708
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error:unknown" }]
6809
+ expected: [
6810
+ startedSession(NEW_THREAD_ID),
6811
+ { type: "result", ok: false, reason: "error:unknown" }
6812
+ ]
6709
6813
  },
6710
6814
  {
6711
6815
  name: "whitespace-only failure text emits no runtime notice",
6712
6816
  request: makeRequest3(),
6713
6817
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed(" ")],
6714
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
6818
+ expected: [startedSession(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
6715
6819
  },
6716
6820
  {
6717
6821
  name: "bare status token emits no runtime notice",
6718
6822
  request: makeRequest3(),
6719
6823
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429")],
6720
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "rate_limited" }]
6824
+ expected: [
6825
+ startedSession(NEW_THREAD_ID),
6826
+ { type: "result", ok: false, reason: "rate_limited" }
6827
+ ]
6721
6828
  },
6722
6829
  {
6723
6830
  name: "single-word provider sentence emits a runtime notice",
6724
6831
  request: makeRequest3(),
6725
6832
  nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("Unavailable.")],
6726
6833
  expected: [
6727
- sessionEvent3(NEW_THREAD_ID),
6834
+ startedSession(NEW_THREAD_ID),
6728
6835
  { type: "runtime_notice", body: "Unavailable." },
6729
6836
  { type: "result", ok: false, reason: "error:Unavailable." }
6730
6837
  ]
@@ -6752,7 +6859,7 @@ var init_conformance4 = __esm({
6752
6859
  turnCompleted()
6753
6860
  ],
6754
6861
  expected: [
6755
- sessionEvent3(NEW_THREAD_ID),
6862
+ startedSession(NEW_THREAD_ID),
6756
6863
  {
6757
6864
  type: "result",
6758
6865
  ok: false,
@@ -6772,8 +6879,9 @@ var init_conformance4 = __esm({
6772
6879
  turnCompleted()
6773
6880
  ],
6774
6881
  expected: [
6775
- sessionEvent3(NEW_THREAD_ID),
6882
+ startedSession(NEW_THREAD_ID),
6776
6883
  { type: "text", body: "All good now.", terminal: true },
6884
+ closingSession(NEW_THREAD_ID),
6777
6885
  { type: "result", ok: true }
6778
6886
  ]
6779
6887
  },
@@ -6785,7 +6893,11 @@ var init_conformance4 = __esm({
6785
6893
  name: "zero-content success (empty runtime result)",
6786
6894
  request: makeRequest3(),
6787
6895
  nativeStream: [threadStarted(NEW_THREAD_ID), turnCompleted()],
6788
- expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: true }]
6896
+ expected: [
6897
+ startedSession(NEW_THREAD_ID),
6898
+ closingSession(NEW_THREAD_ID),
6899
+ { type: "result", ok: true }
6900
+ ]
6789
6901
  },
6790
6902
  {
6791
6903
  // Empty-final: a clean turn that ended on a tool call with no closing message.
@@ -6810,7 +6922,7 @@ var init_conformance4 = __esm({
6810
6922
  turnCompleted()
6811
6923
  ],
6812
6924
  expected: [
6813
- sessionEvent3(NEW_THREAD_ID),
6925
+ startedSession(NEW_THREAD_ID),
6814
6926
  {
6815
6927
  type: "tool",
6816
6928
  id: "t1",
@@ -6828,6 +6940,7 @@ var init_conformance4 = __esm({
6828
6940
  input: { changes: [{ path: "a.md", kind: "add" }] },
6829
6941
  result: [{ path: "a.md", kind: "add" }]
6830
6942
  },
6943
+ closingSession(NEW_THREAD_ID),
6831
6944
  { type: "result", ok: true }
6832
6945
  ]
6833
6946
  },
@@ -6843,9 +6956,10 @@ var init_conformance4 = __esm({
6843
6956
  turnCompleted()
6844
6957
  ],
6845
6958
  expected: [
6846
- sessionEvent3(NEW_THREAD_ID),
6959
+ startedSession(NEW_THREAD_ID),
6847
6960
  { type: "thinking", text: "Let me check the notes." },
6848
6961
  { type: "text", body: "All set.", terminal: true },
6962
+ closingSession(NEW_THREAD_ID),
6849
6963
  { type: "result", ok: true }
6850
6964
  ]
6851
6965
  },
@@ -6863,8 +6977,9 @@ var init_conformance4 = __esm({
6863
6977
  }),
6864
6978
  nativeStream: [threadStarted(NEW_THREAD_ID), agentMessage("m1", "Done."), turnCompleted()],
6865
6979
  expected: [
6866
- sessionEvent3(NEW_THREAD_ID),
6980
+ startedSession(NEW_THREAD_ID),
6867
6981
  { type: "text", body: "Done.", terminal: true },
6982
+ closingSession(NEW_THREAD_ID),
6868
6983
  {
6869
6984
  type: "result",
6870
6985
  ok: true,
@@ -6886,7 +7001,8 @@ var init_conformance4 = __esm({
6886
7001
  threadId: "th1",
6887
7002
  cwd: DIR2,
6888
7003
  promptFingerprint: fingerprintPrompt(PROMPT),
6889
- usageBaseline: null
7004
+ usageBaseline: null,
7005
+ compactions: 0
6890
7006
  })
6891
7007
  }),
6892
7008
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
@@ -6908,8 +7024,9 @@ var init_conformance4 = __esm({
6908
7024
  }),
6909
7025
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
6910
7026
  expected: [
6911
- sessionEvent3("th1"),
7027
+ startedSession("th1"),
6912
7028
  { type: "text", body: "Back again.", terminal: true },
7029
+ closingSession("th1"),
6913
7030
  { type: "result", ok: true }
6914
7031
  ]
6915
7032
  },
@@ -6931,8 +7048,9 @@ var init_conformance4 = __esm({
6931
7048
  }),
6932
7049
  nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
6933
7050
  expected: [
6934
- sessionEvent3("th1"),
7051
+ startedSession("th1"),
6935
7052
  { type: "text", body: "Back again.", terminal: true },
7053
+ closingSession("th1"),
6936
7054
  { type: "result", ok: true }
6937
7055
  ]
6938
7056
  },
@@ -6958,8 +7076,9 @@ var init_conformance4 = __esm({
6958
7076
  turnCompleted()
6959
7077
  ],
6960
7078
  expected: [
6961
- sessionEvent3(NEW_THREAD_ID, DIR2, true),
7079
+ startedSession(NEW_THREAD_ID, DIR2, true),
6962
7080
  { type: "text", body: "Fresh start.", terminal: true },
7081
+ closingSession(NEW_THREAD_ID),
6963
7082
  { type: "result", ok: true }
6964
7083
  ]
6965
7084
  }
@@ -6987,6 +7106,7 @@ var init_src = __esm({
6987
7106
  init_empty_result();
6988
7107
  init_failure();
6989
7108
  init_turn_request();
7109
+ init_standing();
6990
7110
  init_conformance();
6991
7111
  init_transcript();
6992
7112
  init_transcript_sink();
@@ -8540,6 +8660,7 @@ function buildCompanionTurnRequest(params) {
8540
8660
  const { turnContext: t } = params;
8541
8661
  return {
8542
8662
  systemPrompt: t.systemPrompt,
8663
+ ...t.standing ? { standing: t.standing } : {},
8543
8664
  prompt: t.prompt,
8544
8665
  content: t.content,
8545
8666
  config: t.config,
@@ -8669,6 +8790,68 @@ var init_codex_images = __esm({
8669
8790
  }
8670
8791
  });
8671
8792
 
8793
+ // src/codex-compactions.ts
8794
+ import { createReadStream } from "fs";
8795
+ import { readdir } from "fs/promises";
8796
+ import { homedir as homedir8 } from "os";
8797
+ import { join as join23 } from "path";
8798
+ import { createInterface as createInterface2 } from "readline";
8799
+ function createCodexCompactionCounter(codexHome = process.env.CODEX_HOME || join23(homedir8(), ".codex")) {
8800
+ return async (threadId) => {
8801
+ try {
8802
+ const rollout = await findRollout(codexHome, threadId);
8803
+ return rollout ? await countCompactions(rollout) : null;
8804
+ } catch {
8805
+ return null;
8806
+ }
8807
+ };
8808
+ }
8809
+ async function findRollout(codexHome, threadId) {
8810
+ const id = UUID_V7.exec(threadId);
8811
+ if (!id) return null;
8812
+ const createdAt = parseInt(`${id[1]}${id[2]}`, 16);
8813
+ const suffix = `-${threadId}.jsonl`;
8814
+ for (const offset of [0, -DAY_MS, DAY_MS]) {
8815
+ const day = new Date(createdAt + offset);
8816
+ const dir2 = join23(
8817
+ codexHome,
8818
+ "sessions",
8819
+ String(day.getUTCFullYear()),
8820
+ String(day.getUTCMonth() + 1).padStart(2, "0"),
8821
+ String(day.getUTCDate()).padStart(2, "0")
8822
+ );
8823
+ let names;
8824
+ try {
8825
+ names = await readdir(dir2);
8826
+ } catch {
8827
+ continue;
8828
+ }
8829
+ const name = names.find((n) => n.startsWith("rollout-") && n.endsWith(suffix));
8830
+ if (name) return join23(dir2, name);
8831
+ }
8832
+ return null;
8833
+ }
8834
+ async function countCompactions(rollout) {
8835
+ const lines2 = createInterface2({
8836
+ input: createReadStream(rollout, { encoding: "utf8" }),
8837
+ crlfDelay: Infinity
8838
+ });
8839
+ let count = 0;
8840
+ for await (const line of lines2) {
8841
+ if (line.slice(0, RECORD_HEAD_CHARS).includes('"type":"compacted"')) count += 1;
8842
+ }
8843
+ return count;
8844
+ }
8845
+ var DAY_MS, UUID_V7, RECORD_HEAD_CHARS;
8846
+ var init_codex_compactions = __esm({
8847
+ "src/codex-compactions.ts"() {
8848
+ "use strict";
8849
+ DAY_MS = 864e5;
8850
+ UUID_V7 = /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8851
+ RECORD_HEAD_CHARS = 200;
8852
+ }
8853
+ });
8854
+
8672
8855
  // src/turn-seq-floor.ts
8673
8856
  function resolveSeqFloor(sources, ctx2) {
8674
8857
  const { serverFloor, outboxFloor } = sources;
@@ -8777,15 +8960,15 @@ var init_prepare_failure = __esm({
8777
8960
 
8778
8961
  // src/prepared.ts
8779
8962
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, rmSync as rmSync9, writeFileSync as writeFileSync8, existsSync as existsSync13 } from "fs";
8780
- import { join as join23 } from "path";
8963
+ import { join as join24 } from "path";
8781
8964
  function dirFor(workspaceId) {
8782
- return join23(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8965
+ return join24(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8783
8966
  }
8784
8967
  function conversationDir(workspaceId, conversationId) {
8785
- return join23(dirFor(workspaceId), encodeURIComponent(conversationId));
8968
+ return join24(dirFor(workspaceId), encodeURIComponent(conversationId));
8786
8969
  }
8787
8970
  function pathFor2(workspaceId, conversationId, agentId) {
8788
- return join23(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8971
+ return join24(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8789
8972
  }
8790
8973
  function readPrepared(workspaceId, conversationId, agentId) {
8791
8974
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -8823,9 +9006,9 @@ var init_prepared = __esm({
8823
9006
 
8824
9007
  // src/transcript-writer.ts
8825
9008
  import { appendFileSync, chmodSync as chmodSync5, copyFileSync as copyFileSync2, mkdirSync as mkdirSync14, readdirSync as readdirSync2, rmSync as rmSync10 } from "fs";
8826
- import { basename as basename3, dirname as dirname12, join as join24 } from "path";
9009
+ import { basename as basename3, dirname as dirname12, join as join25 } from "path";
8827
9010
  function transcriptsDir() {
8828
- return join24(runnerStateDir(), "transcripts");
9011
+ return join25(runnerStateDir(), "transcripts");
8829
9012
  }
8830
9013
  function fileName(meta) {
8831
9014
  const d = new Date(meta.ts);
@@ -8851,7 +9034,7 @@ function pruneOld(dir2, retain) {
8851
9034
  const drop = files.sort().slice(0, files.length - retain);
8852
9035
  for (const f of drop) {
8853
9036
  try {
8854
- rmSync10(join24(dir2, f), { force: true });
9037
+ rmSync10(join25(dir2, f), { force: true });
8855
9038
  } catch {
8856
9039
  }
8857
9040
  }
@@ -8869,7 +9052,7 @@ var init_transcript_writer = __esm({
8869
9052
  onWarn;
8870
9053
  constructor(dir2, meta, onWarn) {
8871
9054
  this.onWarn = onWarn;
8872
- this.path = join24(dir2, fileName(meta));
9055
+ this.path = join25(dir2, fileName(meta));
8873
9056
  try {
8874
9057
  mkdirSync14(dir2, { recursive: true });
8875
9058
  try {
@@ -8898,10 +9081,10 @@ var init_transcript_writer = __esm({
8898
9081
  preserveAnomaly() {
8899
9082
  if (this.broken) return;
8900
9083
  try {
8901
- const dir2 = join24(dirname12(this.path), "anomalies");
9084
+ const dir2 = join25(dirname12(this.path), "anomalies");
8902
9085
  mkdirSync14(dir2, { recursive: true, mode: 448 });
8903
9086
  chmodSync5(dir2, 448);
8904
- const target = join24(dir2, basename3(this.path));
9087
+ const target = join25(dir2, basename3(this.path));
8905
9088
  copyFileSync2(this.path, target);
8906
9089
  chmodSync5(target, 384);
8907
9090
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -9201,6 +9384,7 @@ var init_turn_execution = __esm({
9201
9384
  init_bundled_binaries();
9202
9385
  init_codex_instructions();
9203
9386
  init_codex_images();
9387
+ init_codex_compactions();
9204
9388
  init_config();
9205
9389
  init_api_error_shape();
9206
9390
  init_errors();
@@ -9886,6 +10070,8 @@ var init_turn_execution = __esm({
9886
10070
  turnId: this.turnId,
9887
10071
  warn: (meta, msg) => turnLog.warn(meta, msg)
9888
10072
  }),
10073
+ // Whether a resumed thread compacted since its standing preamble was sent.
10074
+ countCompactions: createCodexCompactionCounter(),
9889
10075
  ...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
9890
10076
  })
9891
10077
  );
@@ -9946,6 +10132,9 @@ var init_turn_execution = __esm({
9946
10132
  );
9947
10133
  return {
9948
10134
  systemPrompt: context.systemPrompt,
10135
+ // Replaced even when absent, so the segments can't outlive the
10136
+ // `systemPrompt` they were split from.
10137
+ standing: context.standing,
9949
10138
  prompt: context.prompt,
9950
10139
  content: context.content
9951
10140
  };
@@ -10338,7 +10527,7 @@ var init_turn_execution = __esm({
10338
10527
  // src/dispatcher.ts
10339
10528
  import { randomUUID as randomUUID4 } from "crypto";
10340
10529
  import { existsSync as existsSync15, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
10341
- import { join as join25 } from "path";
10530
+ import { join as join26 } from "path";
10342
10531
  function checkoutState(cwd) {
10343
10532
  if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
10344
10533
  if (!existsSync15(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
@@ -10351,7 +10540,7 @@ function checkoutState(cwd) {
10351
10540
  if (entries.length === 0) {
10352
10541
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
10353
10542
  }
10354
- const gitPath = join25(cwd, ".git");
10543
+ const gitPath = join26(cwd, ".git");
10355
10544
  if (!existsSync15(gitPath)) return { ok: true, reason: "usable" };
10356
10545
  let stat;
10357
10546
  try {
@@ -10359,7 +10548,7 @@ function checkoutState(cwd) {
10359
10548
  } catch (error) {
10360
10549
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
10361
10550
  }
10362
- if (stat.isDirectory() && !existsSync15(join25(gitPath, "HEAD")))
10551
+ if (stat.isDirectory() && !existsSync15(join26(gitPath, "HEAD")))
10363
10552
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
10364
10553
  return { ok: true, reason: "usable" };
10365
10554
  }
@@ -10737,8 +10926,8 @@ import {
10737
10926
  } from "fs";
10738
10927
  import { createRequire as createRequire3 } from "module";
10739
10928
  import { isIP } from "net";
10740
- import { homedir as homedir8, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo3 } from "os";
10741
- import { dirname as dirname14, join as join29, sep as sep5 } from "path";
10929
+ import { homedir as homedir9, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo3 } from "os";
10930
+ import { dirname as dirname14, join as join30, sep as sep5 } from "path";
10742
10931
  import { fileURLToPath as fileURLToPath7 } from "url";
10743
10932
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
10744
10933
  function accountHome() {
@@ -10772,7 +10961,7 @@ async function resolvedAddresses(baseUrl) {
10772
10961
  function companionPackageRoot(modulePath) {
10773
10962
  for (let dir2 = dirname14(modulePath); ; dir2 = dirname14(dir2)) {
10774
10963
  try {
10775
- const manifest = JSON.parse(readFileSync16(join29(dir2, "package.json"), "utf8"));
10964
+ const manifest = JSON.parse(readFileSync16(join30(dir2, "package.json"), "utf8"));
10776
10965
  if (manifest.name === "@cabane/companion") return dir2;
10777
10966
  } catch {
10778
10967
  }
@@ -10849,22 +11038,22 @@ function companionStoreDenies(input) {
10849
11038
  const always = [
10850
11039
  cabaneDir2,
10851
11040
  ...homes.flatMap((home) => [
10852
- join29(home, ".cabane"),
10853
- join29(home, "*", ".cabane"),
10854
- join29(home, "*", "*", ".cabane")
11041
+ join30(home, ".cabane"),
11042
+ join30(home, "*", ".cabane"),
11043
+ join30(home, "*", "*", ".cabane")
10855
11044
  ]),
10856
11045
  // A glob anchored at the root has no directory to start from.
10857
- ...dirname14(parent) === parent ? [] : [join29(parent, "*", ".cabane")]
11046
+ ...dirname14(parent) === parent ? [] : [join30(parent, "*", ".cabane")]
10858
11047
  ];
10859
11048
  const companionHome = dirname14(cabaneDir2);
10860
- const otherLogins = foundStores.map((store) => dirname14(store)).filter((home) => home !== companionHome).flatMap((home) => [join29(home, ".claude"), join29(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
11049
+ const otherLogins = foundStores.map((store) => dirname14(store)).filter((home) => home !== companionHome).flatMap((home) => [join30(home, ".claude"), join30(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
10861
11050
  return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
10862
11051
  }
10863
11052
  function companionStoreWatch(log2) {
10864
11053
  return new CompanionStoreWatch({
10865
11054
  log: log2,
10866
11055
  roots: () => {
10867
- const home = homedir8();
11056
+ const home = homedir9();
10868
11057
  return storeWalkRoots([.../* @__PURE__ */ new Set([home, accountHome() ?? home])], cabaneDir());
10869
11058
  }
10870
11059
  });
@@ -10874,7 +11063,7 @@ function isAtOrUnder(path, dir2) {
10874
11063
  }
10875
11064
  function expandHome(path, home) {
10876
11065
  if (path === "~") return home;
10877
- return path.startsWith("~/") ? join29(home, path.slice(2)) : path;
11066
+ return path.startsWith("~/") ? join30(home, path.slice(2)) : path;
10878
11067
  }
10879
11068
  function stripPort(pattern) {
10880
11069
  return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
@@ -10892,8 +11081,8 @@ function patternsOverlap(a, b) {
10892
11081
  }
10893
11082
  function harnessSandboxConfig(input) {
10894
11083
  const { policy } = input;
10895
- const claudeDir = input.claudeConfigDir ?? join29(input.home, ".claude");
10896
- const codexDir = input.codexHome ?? join29(input.home, ".codex");
11084
+ const claudeDir = input.claudeConfigDir ?? join30(input.home, ".claude");
11085
+ const codexDir = input.codexHome ?? join30(input.home, ".codex");
10897
11086
  const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
10898
11087
  const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
10899
11088
  const cabane2 = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
@@ -10910,7 +11099,7 @@ function harnessSandboxConfig(input) {
10910
11099
  foundStores: input.foundStores,
10911
11100
  ownState: [claudeDir, codexDir]
10912
11101
  }),
10913
- ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join29(home, p))),
11102
+ ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join30(home, p))),
10914
11103
  ...expand(policy.filesystem?.denyRead)
10915
11104
  ];
10916
11105
  const strict = policy.filesystem?.mode === "strict";
@@ -10918,7 +11107,7 @@ function harnessSandboxConfig(input) {
10918
11107
  const allowRead = strict ? [
10919
11108
  ...input.cwd ? [input.cwd] : [],
10920
11109
  claudeDir,
10921
- join29(input.home, ".claude.json"),
11110
+ join30(input.home, ".claude.json"),
10922
11111
  codexDir,
10923
11112
  ...input.runtimePaths ?? [],
10924
11113
  ...expand(policy.filesystem?.allowRead)
@@ -10958,7 +11147,7 @@ function harnessSandboxConfig(input) {
10958
11147
  ...input.cwd ? [input.cwd] : [],
10959
11148
  input.tmp,
10960
11149
  claudeDir,
10961
- join29(input.home, ".claude.json"),
11150
+ join30(input.home, ".claude.json"),
10962
11151
  codexDir,
10963
11152
  ...expand(policy.filesystem?.allowWrite)
10964
11153
  ],
@@ -11009,7 +11198,7 @@ async function prepareHarnessSandbox(input) {
11009
11198
  baseUrl: input.baseUrl,
11010
11199
  resolved: await resolvedAddresses(input.baseUrl),
11011
11200
  ownAddresses: ownAddresses(),
11012
- home: homedir8(),
11201
+ home: homedir9(),
11013
11202
  accountHome: accountHome(),
11014
11203
  tmp: tmpdir4(),
11015
11204
  cabaneDir: cabaneDir(),
@@ -11060,9 +11249,9 @@ async function prepareHarnessSandbox(input) {
11060
11249
  } finally {
11061
11250
  for (const name of held) delete process.env[name];
11062
11251
  }
11063
- mkdirSync17(join29(cabaneDir(), "sandbox"), { recursive: true });
11064
- const dir2 = mkdtempSync(join29(cabaneDir(), "sandbox", "turn-"));
11065
- const script = join29(dir2, "exec-sandboxed");
11252
+ mkdirSync17(join30(cabaneDir(), "sandbox"), { recursive: true });
11253
+ const dir2 = mkdtempSync(join30(cabaneDir(), "sandbox", "turn-"));
11254
+ const script = join30(dir2, "exec-sandboxed");
11066
11255
  writeFileSync10(
11067
11256
  script,
11068
11257
  [
@@ -11078,7 +11267,7 @@ async function prepareHarnessSandbox(input) {
11078
11267
  return {
11079
11268
  wrap: (command, args) => ({ command: script, args: [command, ...args] }),
11080
11269
  executableFor: (real) => {
11081
- const shim = join29(dir2, `exec-${++shims}`);
11270
+ const shim = join30(dir2, `exec-${++shims}`);
11082
11271
  writeFileSync10(shim, `#!/bin/bash
11083
11272
  exec ${shellQuote(script)} ${shellQuote(real)} "$@"
11084
11273
  `);
@@ -11171,7 +11360,7 @@ import {
11171
11360
  rmSync as rmSync15,
11172
11361
  writeFileSync as writeFileSync11
11173
11362
  } from "fs";
11174
- import { join as join30 } from "path";
11363
+ import { join as join31 } from "path";
11175
11364
  var MAX_ENTRIES, MAX_AGE_MS, Outbox;
11176
11365
  var init_outbox = __esm({
11177
11366
  "src/outbox.ts"() {
@@ -11189,10 +11378,10 @@ var init_outbox = __esm({
11189
11378
  // Resolved lazily (per call) so tests that swap HOME between
11190
11379
  // cases route writes at the right tmpdir.
11191
11380
  dir() {
11192
- return join30(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11381
+ return join31(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11193
11382
  }
11194
11383
  fileFor(turnId, seq) {
11195
- return join30(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11384
+ return join31(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11196
11385
  }
11197
11386
  // Persist a commit for later draining. Atomic (temp file + rename) so a
11198
11387
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -11234,7 +11423,7 @@ var init_outbox = __esm({
11234
11423
  const entries = [];
11235
11424
  for (const name of names) {
11236
11425
  if (!name.endsWith(".json")) continue;
11237
- const full = join30(dir2, name);
11426
+ const full = join31(dir2, name);
11238
11427
  try {
11239
11428
  const parsed = JSON.parse(readFileSync17(full, "utf8"));
11240
11429
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -11363,7 +11552,7 @@ var init_image_manifest = __esm({
11363
11552
 
11364
11553
  // src/runner/exec-shim.ts
11365
11554
  import { chmodSync as chmodSync8, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "fs";
11366
- import { join as join34 } from "path";
11555
+ import { join as join35 } from "path";
11367
11556
  function shellQuote2(value) {
11368
11557
  return `'${value.replace(/'/g, `'\\''`)}'`;
11369
11558
  }
@@ -11373,10 +11562,10 @@ function createShimDir() {
11373
11562
  return {
11374
11563
  write(argv) {
11375
11564
  if (!dir2) {
11376
- mkdirSync20(join34(cabaneDir(), "runner"), { recursive: true });
11377
- dir2 = mkdtempSync2(join34(cabaneDir(), "runner", "turn-"));
11565
+ mkdirSync20(join35(cabaneDir(), "runner"), { recursive: true });
11566
+ dir2 = mkdtempSync2(join35(cabaneDir(), "runner", "turn-"));
11378
11567
  }
11379
- const path = join34(dir2, `exec-${++count}`);
11568
+ const path = join35(dir2, `exec-${++count}`);
11380
11569
  writeFileSync13(path, `#!/bin/sh
11381
11570
  exec ${argv.map(shellQuote2).join(" ")} "$@"
11382
11571
  `);
@@ -11397,7 +11586,7 @@ var init_exec_shim = __esm({
11397
11586
  });
11398
11587
 
11399
11588
  // src/runner/launcher-channel.ts
11400
- import { createInterface as createInterface2 } from "readline";
11589
+ import { createInterface as createInterface3 } from "readline";
11401
11590
  function openLauncherChannel(stdin, emit, onLauncherGone) {
11402
11591
  const waiting = /* @__PURE__ */ new Map();
11403
11592
  const replies = /* @__PURE__ */ new Map();
@@ -11405,7 +11594,7 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
11405
11594
  let resolveFirst;
11406
11595
  let gotFirst = false;
11407
11596
  const first = new Promise((resolve2) => resolveFirst = resolve2);
11408
- const lines2 = createInterface2({ input: stdin, crlfDelay: Infinity });
11597
+ const lines2 = createInterface3({ input: stdin, crlfDelay: Infinity });
11409
11598
  lines2.on("line", (line) => {
11410
11599
  if (!gotFirst) {
11411
11600
  gotFirst = true;
@@ -15765,7 +15954,7 @@ function createLocalProcessProvider(opts = {}) {
15765
15954
  import { createHash as createHash5, randomUUID as randomUUID5 } from "crypto";
15766
15955
  import { constants as constants3, tmpdir as tmpdir3 } from "os";
15767
15956
  import {
15768
- createReadStream,
15957
+ createReadStream as createReadStream2,
15769
15958
  createWriteStream as createWriteStream2,
15770
15959
  existsSync as existsSync18,
15771
15960
  mkdirSync as mkdirSync16,
@@ -15776,7 +15965,7 @@ import {
15776
15965
  statSync as statSync4,
15777
15966
  writeFileSync as writeFileSync9
15778
15967
  } from "fs";
15779
- import { join as join28 } from "path";
15968
+ import { join as join29 } from "path";
15780
15969
  import { Writable } from "stream";
15781
15970
 
15782
15971
  // src/providers/docker-cli.ts
@@ -15849,7 +16038,7 @@ import {
15849
16038
  renameSync as renameSync10,
15850
16039
  rmSync as rmSync12
15851
16040
  } from "fs";
15852
- import { join as join26 } from "path";
16041
+ import { join as join27 } from "path";
15853
16042
  function routes(t) {
15854
16043
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
15855
16044
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -15895,7 +16084,7 @@ function publishStaged(from, to, move = atomicMove) {
15895
16084
  const staged = readdirSync4(from).filter((n) => n.endsWith(".json")).map((name) => {
15896
16085
  let entry = {};
15897
16086
  try {
15898
- entry = JSON.parse(readFileSync14(join26(from, name), "utf8"));
16087
+ entry = JSON.parse(readFileSync14(join27(from, name), "utf8"));
15899
16088
  } catch {
15900
16089
  }
15901
16090
  return { name, entry };
@@ -15908,7 +16097,7 @@ function publishStaged(from, to, move = atomicMove) {
15908
16097
  let published = 0;
15909
16098
  for (const { name } of staged) {
15910
16099
  try {
15911
- move(join26(from, name), join26(to, name));
16100
+ move(join27(from, name), join27(to, name));
15912
16101
  } catch {
15913
16102
  return { published, complete: false };
15914
16103
  }
@@ -15921,7 +16110,7 @@ function publishStaged(from, to, move = atomicMove) {
15921
16110
  // src/providers/runner-base.ts
15922
16111
  import { createHash as createHash4 } from "crypto";
15923
16112
  import { existsSync as existsSync17, realpathSync as realpathSync6 } from "fs";
15924
- import { dirname as dirname13, join as join27, sep as sep4 } from "path";
16113
+ import { dirname as dirname13, join as join28, sep as sep4 } from "path";
15925
16114
  import { fileURLToPath as fileURLToPath6 } from "url";
15926
16115
  var COMPANION_MOUNT = "/opt/cabane/companion";
15927
16116
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -15969,11 +16158,11 @@ async function ensureBaseImage(docker, image, log2) {
15969
16158
  function ownInstall(moduleFile = fileURLToPath6(import.meta.url)) {
15970
16159
  let dir2 = dirname13(moduleFile);
15971
16160
  for (let i = 0; i < 4; i++) {
15972
- if (existsSync17(join27(dir2, "dist", "cli.js")) && existsSync17(join27(dir2, "package.json"))) break;
16161
+ if (existsSync17(join28(dir2, "dist", "cli.js")) && existsSync17(join28(dir2, "package.json"))) break;
15973
16162
  dir2 = dirname13(dir2);
15974
16163
  }
15975
- if (!existsSync17(join27(dir2, "dist", "cli.js"))) return null;
15976
- const sdk = join27(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
16164
+ if (!existsSync17(join28(dir2, "dist", "cli.js"))) return null;
16165
+ const sdk = join28(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
15977
16166
  if (!existsSync17(sdk)) return null;
15978
16167
  const root = realpathSync6(dir2);
15979
16168
  return realpathSync6(sdk).startsWith(root + sep4) ? root : null;
@@ -16112,39 +16301,39 @@ function networkCreateError(stderr) {
16112
16301
  }
16113
16302
  function sessionArchivePath(deviceDir, attemptId) {
16114
16303
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
16115
- return join28(deviceDir, "attempts", attemptId, "session.tar");
16304
+ return join29(deviceDir, "attempts", attemptId, "session.tar");
16116
16305
  }
16117
16306
  function pruneOtherAttempts(deviceDir, pair2, keep) {
16118
- const root = join28(deviceDir, "attempts");
16307
+ const root = join29(deviceDir, "attempts");
16119
16308
  if (!existsSync18(root)) return;
16120
16309
  for (const id of readdirSync5(root)) {
16121
16310
  if (id === keep || !UUID_RE.test(id)) continue;
16122
16311
  let pairOf = "";
16123
16312
  try {
16124
- pairOf = readFileSync15(join28(root, id, "pair"), "utf8");
16313
+ pairOf = readFileSync15(join29(root, id, "pair"), "utf8");
16125
16314
  } catch {
16126
16315
  continue;
16127
16316
  }
16128
16317
  if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
16129
- rmSync13(join28(root, id), { recursive: true, force: true });
16318
+ rmSync13(join29(root, id), { recursive: true, force: true });
16130
16319
  }
16131
16320
  }
16132
16321
  }
16133
16322
  var IDLE_SESSION_ARCHIVE_MS = 30 * 24 * 60 * 6e4;
16134
16323
  function pruneIdleSessionArchives(deviceDir, now = Date.now(), maxAgeMs = IDLE_SESSION_ARCHIVE_MS) {
16135
- const root = join28(deviceDir, "attempts");
16324
+ const root = join29(deviceDir, "attempts");
16136
16325
  if (!existsSync18(root)) return [];
16137
16326
  const removed = [];
16138
16327
  for (const id of readdirSync5(root)) {
16139
16328
  if (!UUID_RE.test(id)) continue;
16140
16329
  let kept;
16141
16330
  try {
16142
- kept = statSync4(join28(root, id)).mtimeMs;
16331
+ kept = statSync4(join29(root, id)).mtimeMs;
16143
16332
  } catch {
16144
16333
  continue;
16145
16334
  }
16146
16335
  if (now - kept < maxAgeMs) continue;
16147
- rmSync13(join28(root, id), { recursive: true, force: true });
16336
+ rmSync13(join29(root, id), { recursive: true, force: true });
16148
16337
  removed.push(id);
16149
16338
  }
16150
16339
  return removed;
@@ -16345,11 +16534,11 @@ function createDockerProvider(opts) {
16345
16534
  await must(["network", "disconnect", "-f", other, name], "moving the container");
16346
16535
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
16347
16536
  };
16348
- const socketRoot = opts.socketRoot ?? join28(
16537
+ const socketRoot = opts.socketRoot ?? join29(
16349
16538
  process.env.XDG_RUNTIME_DIR && existsSync18(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
16350
16539
  `cabane-runners-${opts.deviceKey}`
16351
16540
  );
16352
- const socketDir = (name) => join28(socketRoot, createHash5("sha256").update(name).digest("hex").slice(0, 12));
16541
+ const socketDir = (name) => join29(socketRoot, createHash5("sha256").update(name).digest("hex").slice(0, 12));
16353
16542
  const create = async (name, attempt) => {
16354
16543
  mkdirSync16(socketRoot, { recursive: true, mode: 448 });
16355
16544
  mkdirSync16(socketDir(name), { recursive: true, mode: 457 });
@@ -16491,7 +16680,7 @@ function createDockerProvider(opts) {
16491
16680
  }
16492
16681
  };
16493
16682
  const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
16494
- const pendingPath = (name) => join28(opts.deviceDir, "runners-pending", `${name}.json`);
16683
+ const pendingPath = (name) => join29(opts.deviceDir, "runners-pending", `${name}.json`);
16495
16684
  const readPending = (name) => {
16496
16685
  try {
16497
16686
  const ids = JSON.parse(readFileSync15(pendingPath(name), "utf8"));
@@ -16505,12 +16694,12 @@ function createDockerProvider(opts) {
16505
16694
  rmSync13(pendingPath(name), { force: true });
16506
16695
  return;
16507
16696
  }
16508
- mkdirSync16(join28(opts.deviceDir, "runners-pending"), { recursive: true });
16697
+ mkdirSync16(join29(opts.deviceDir, "runners-pending"), { recursive: true });
16509
16698
  writeFileSync9(pendingPath(name), JSON.stringify([...new Set(ids)]));
16510
16699
  };
16511
16700
  const keepSession = async (name, attempt, attemptId) => {
16512
16701
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
16513
- mkdirSync16(join28(archive, ".."), { recursive: true });
16702
+ mkdirSync16(join29(archive, ".."), { recursive: true });
16514
16703
  const partial = `${archive}.${process.pid}.partial`;
16515
16704
  const file = new CappedFile(partial, sessionCap);
16516
16705
  const r = await execIn(
@@ -16527,7 +16716,7 @@ function createDockerProvider(opts) {
16527
16716
  );
16528
16717
  if (r.code === 0 && !file.over) {
16529
16718
  renameSync11(partial, archive);
16530
- writeFileSync9(join28(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
16719
+ writeFileSync9(join29(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
16531
16720
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
16532
16721
  await writeMarker(name, attemptId);
16533
16722
  log2.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -16541,7 +16730,7 @@ function createDockerProvider(opts) {
16541
16730
  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"
16542
16731
  );
16543
16732
  };
16544
- const stagingDir = (name) => join28(opts.deviceDir, "runners-pending", `${name}.outbox`);
16733
+ const stagingDir = (name) => join29(opts.deviceDir, "runners-pending", `${name}.outbox`);
16545
16734
  const ensureRunning = async (name) => {
16546
16735
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
16547
16736
  if (seen.code !== 0) return false;
@@ -16549,7 +16738,7 @@ function createDockerProvider(opts) {
16549
16738
  return (await docker.run(["start", name])).code === 0;
16550
16739
  };
16551
16740
  const dropArchive = (attemptId) => {
16552
- rmSync13(join28(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
16741
+ rmSync13(join29(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
16553
16742
  recursive: true,
16554
16743
  force: true
16555
16744
  });
@@ -16618,7 +16807,7 @@ function createDockerProvider(opts) {
16618
16807
  if (seen.stdout.trim().split(" ")[0] !== "true")
16619
16808
  await must(["start", name], "starting the container");
16620
16809
  const git = await serveGitCredentials({
16621
- socketPath: join28(socketDir(name), "git.sock"),
16810
+ socketPath: join29(socketDir(name), "git.sock"),
16622
16811
  hosts: opts.gitHosts,
16623
16812
  fill: opts.gitFill,
16624
16813
  log: log2
@@ -16654,7 +16843,7 @@ function createDockerProvider(opts) {
16654
16843
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
16655
16844
  if (existsSync18(archive)) {
16656
16845
  const r = await execIn(name, ["tar", "-x", "-C", CONTAINER_HOME, "-f", "-"], {
16657
- input: createReadStream(archive)
16846
+ input: createReadStream2(archive)
16658
16847
  });
16659
16848
  if (r.code !== 0) {
16660
16849
  log2.warn(
@@ -16767,15 +16956,15 @@ function createDockerProvider(opts) {
16767
16956
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
16768
16957
  if (transcripts) {
16769
16958
  const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
16770
- if (kept.length) mkdirSync16(join28(opts.hostStateDir, "transcripts"), { recursive: true });
16771
- for (const f of kept) writeFileSync9(join28(opts.hostStateDir, "transcripts", f.name), f.data);
16959
+ if (kept.length) mkdirSync16(join29(opts.hostStateDir, "transcripts"), { recursive: true });
16960
+ for (const f of kept) writeFileSync9(join29(opts.hostStateDir, "transcripts", f.name), f.data);
16772
16961
  await removeIn(
16773
16962
  name,
16774
16963
  `${CONTAINER_STATE}/transcripts`,
16775
16964
  kept.map((f) => f.name)
16776
16965
  );
16777
16966
  }
16778
- const outboxRel = join28("outbox", encodeURIComponent(attempt.agentId));
16967
+ const outboxRel = join29("outbox", encodeURIComponent(attempt.agentId));
16779
16968
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
16780
16969
  let unpublished = false;
16781
16970
  if (outbox) {
@@ -16801,7 +16990,7 @@ function createDockerProvider(opts) {
16801
16990
  const staged = stagingDir(name);
16802
16991
  mkdirSync16(staged, { recursive: true });
16803
16992
  writeFileSync9(
16804
- join28(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
16993
+ join29(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
16805
16994
  `${JSON.stringify(entry)}
16806
16995
  `
16807
16996
  );
@@ -16814,7 +17003,7 @@ function createDockerProvider(opts) {
16814
17003
  if (outbox.complete) {
16815
17004
  const published = publishStaged(
16816
17005
  stagingDir(name),
16817
- join28(opts.hostStateDir, outboxRel),
17006
+ join29(opts.hostStateDir, outboxRel),
16818
17007
  opts.moveFile
16819
17008
  );
16820
17009
  queued = published.published;
@@ -18789,9 +18978,9 @@ function handleUncaught(log2, err, origin) {
18789
18978
  // src/crash-marker.ts
18790
18979
  init_config();
18791
18980
  import { existsSync as existsSync20, mkdirSync as mkdirSync19, readFileSync as readFileSync18, rmSync as rmSync16, writeFileSync as writeFileSync12 } from "fs";
18792
- import { join as join31 } from "path";
18981
+ import { join as join32 } from "path";
18793
18982
  function crashMarkerPath() {
18794
- return join31(cabaneDir(), "last-error.json");
18983
+ return join32(cabaneDir(), "last-error.json");
18795
18984
  }
18796
18985
  function recordCrash(rec2) {
18797
18986
  try {
@@ -19104,8 +19293,8 @@ async function runAttached(runtime) {
19104
19293
  init_install_kind();
19105
19294
  init_cabane_command();
19106
19295
  import { readdirSync as readdirSync7, realpathSync as realpathSync8, existsSync as existsSync21 } from "fs";
19107
- import { homedir as homedir9 } from "os";
19108
- import { dirname as dirname15, join as join32 } from "path";
19296
+ import { homedir as homedir10 } from "os";
19297
+ import { dirname as dirname15, join as join33 } from "path";
19109
19298
  init_config();
19110
19299
  init_sandbox_policy();
19111
19300
  init_secrets();
@@ -19174,7 +19363,7 @@ function versionLine() {
19174
19363
  const install = companionInstall();
19175
19364
  const own = installedCommandPath({ install });
19176
19365
  if (install === "script") {
19177
- return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join32(homedir9(), ".cabane"))})`;
19366
+ return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join33(homedir10(), ".cabane"))})`;
19178
19367
  }
19179
19368
  if (own)
19180
19369
  return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(dirname15(dirname15(own)))})`;
@@ -19200,8 +19389,8 @@ function otherInstall(install, own) {
19200
19389
  }
19201
19390
  };
19202
19391
  if (install === "npm") {
19203
- const script = join32(
19204
- homedir9(),
19392
+ const script = join33(
19393
+ homedir10(),
19205
19394
  ".cabane",
19206
19395
  "node",
19207
19396
  "lib",
@@ -19209,11 +19398,11 @@ function otherInstall(install, own) {
19209
19398
  "@cabane",
19210
19399
  "companion"
19211
19400
  );
19212
- return existsSync21(script) ? join32(homedir9(), ".cabane") : null;
19401
+ return existsSync21(script) ? join33(homedir10(), ".cabane") : null;
19213
19402
  }
19214
19403
  const found = firstOnPath("cabane-companion", process.env.PATH);
19215
19404
  if (!found) return null;
19216
- const ownAlias = own ? real(join32(dirname15(own), "cabane-companion")) : null;
19405
+ const ownAlias = own ? real(join33(dirname15(own), "cabane-companion")) : null;
19217
19406
  return real(found) === ownAlias ? null : dirname15(found);
19218
19407
  }
19219
19408
  async function liveState() {
@@ -19440,7 +19629,7 @@ init_errors();
19440
19629
  init_src();
19441
19630
  init_transcript_writer();
19442
19631
  import { existsSync as existsSync22, readFileSync as readFileSync19, readdirSync as readdirSync8 } from "fs";
19443
- import { isAbsolute, join as join33 } from "path";
19632
+ import { isAbsolute, join as join34 } from "path";
19444
19633
  async function transcript(opts = {}) {
19445
19634
  const dir2 = transcriptsDir();
19446
19635
  if (opts.follow) {
@@ -19457,7 +19646,7 @@ async function transcript(opts = {}) {
19457
19646
  process.stdout.write(emptyMessage(dir2));
19458
19647
  return;
19459
19648
  }
19460
- process.stdout.write(renderFile(join33(dir2, newest)) + "\n");
19649
+ process.stdout.write(renderFile(join34(dir2, newest)) + "\n");
19461
19650
  return;
19462
19651
  }
19463
19652
  printList(dir2);
@@ -19540,7 +19729,7 @@ function isComplete(content) {
19540
19729
  async function followTranscripts(dir2) {
19541
19730
  const follower = new TranscriptFollower({
19542
19731
  listFiles: () => listFiles(dir2),
19543
- read: (f) => readFileSync19(join33(dir2, f), "utf8"),
19732
+ read: (f) => readFileSync19(join34(dir2, f), "utf8"),
19544
19733
  write: (s) => process.stdout.write(s),
19545
19734
  // CSI: cursor up `n` lines, then erase from cursor to end of screen.
19546
19735
  clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
@@ -19580,7 +19769,7 @@ function printList(dir2) {
19580
19769
 
19581
19770
  `);
19582
19771
  for (const f of files.slice(0, 20)) {
19583
- const { meta, outcome } = peek(join33(dir2, f));
19772
+ const { meta, outcome } = peek(join34(dir2, f));
19584
19773
  const when = fmtTime(rec(meta)?.ts);
19585
19774
  const ws = str2(rec(meta)?.workspaceSlug);
19586
19775
  const o = rec(outcome);
@@ -19618,10 +19807,10 @@ function resolveTarget(dir2, target) {
19618
19807
  if (existsSync22(target)) return target;
19619
19808
  throw new CompanionError(`no transcript at ${target}.`);
19620
19809
  }
19621
- const exact = join33(dir2, target);
19810
+ const exact = join34(dir2, target);
19622
19811
  if (existsSync22(exact)) return exact;
19623
19812
  const matches = listFiles(dir2).filter((f) => f.includes(target));
19624
- if (matches.length === 1) return join33(dir2, matches[0]);
19813
+ if (matches.length === 1) return join34(dir2, matches[0]);
19625
19814
  if (matches.length === 0) {
19626
19815
  throw new CompanionError(
19627
19816
  `no transcript matching "${target}" in ${dir2}. Run \`${cabane("transcript")}\` to list them.`