@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.
- package/dist/cli.js +355 -166
- package/dist/runtime.js +326 -152
- 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
|
|
2088
|
-
// charter)
|
|
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
|
|
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(
|
|
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
|
-
|
|
5463
|
-
|
|
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 &&
|
|
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
|
-
|
|
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,
|
|
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
|
|
5661
|
-
const
|
|
5662
|
-
const
|
|
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,
|
|
5672
|
-
|
|
5673
|
-
|
|
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,
|
|
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
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
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
|
-
|
|
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
|
|
6013
|
-
// later copy is what the model reads.
|
|
6014
|
-
//
|
|
6015
|
-
promptFingerprint: spec.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8963
|
+
import { join as join24 } from "path";
|
|
8781
8964
|
function dirFor(workspaceId) {
|
|
8782
|
-
return
|
|
8965
|
+
return join24(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
8783
8966
|
}
|
|
8784
8967
|
function conversationDir(workspaceId, conversationId) {
|
|
8785
|
-
return
|
|
8968
|
+
return join24(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
8786
8969
|
}
|
|
8787
8970
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
8788
|
-
return
|
|
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
|
|
9009
|
+
import { basename as basename3, dirname as dirname12, join as join25 } from "path";
|
|
8827
9010
|
function transcriptsDir() {
|
|
8828
|
-
return
|
|
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(
|
|
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 =
|
|
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 =
|
|
9084
|
+
const dir2 = join25(dirname12(this.path), "anomalies");
|
|
8902
9085
|
mkdirSync14(dir2, { recursive: true, mode: 448 });
|
|
8903
9086
|
chmodSync5(dir2, 448);
|
|
8904
|
-
const target =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
10741
|
-
import { dirname as dirname14, join as
|
|
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(
|
|
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
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
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 ? [] : [
|
|
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) => [
|
|
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 =
|
|
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("~/") ?
|
|
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 ??
|
|
10896
|
-
const codexDir = input.codexHome ??
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
11064
|
-
const dir2 = mkdtempSync(
|
|
11065
|
-
const script =
|
|
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 =
|
|
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
|
|
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
|
|
11381
|
+
return join31(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
11193
11382
|
}
|
|
11194
11383
|
fileFor(turnId, seq) {
|
|
11195
|
-
return
|
|
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 =
|
|
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
|
|
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(
|
|
11377
|
-
dir2 = mkdtempSync2(
|
|
11565
|
+
mkdirSync20(join35(cabaneDir(), "runner"), { recursive: true });
|
|
11566
|
+
dir2 = mkdtempSync2(join35(cabaneDir(), "runner", "turn-"));
|
|
11378
11567
|
}
|
|
11379
|
-
const path =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
16161
|
+
if (existsSync17(join28(dir2, "dist", "cli.js")) && existsSync17(join28(dir2, "package.json"))) break;
|
|
15973
16162
|
dir2 = dirname13(dir2);
|
|
15974
16163
|
}
|
|
15975
|
-
if (!existsSync17(
|
|
15976
|
-
const 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
|
|
16304
|
+
return join29(deviceDir, "attempts", attemptId, "session.tar");
|
|
16116
16305
|
}
|
|
16117
16306
|
function pruneOtherAttempts(deviceDir, pair2, keep) {
|
|
16118
|
-
const root =
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
16331
|
+
kept = statSync4(join29(root, id)).mtimeMs;
|
|
16143
16332
|
} catch {
|
|
16144
16333
|
continue;
|
|
16145
16334
|
}
|
|
16146
16335
|
if (now - kept < maxAgeMs) continue;
|
|
16147
|
-
rmSync13(
|
|
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 ??
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
16771
|
-
for (const f of kept) writeFileSync9(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
18981
|
+
import { join as join32 } from "path";
|
|
18793
18982
|
function crashMarkerPath() {
|
|
18794
|
-
return
|
|
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
|
|
19108
|
-
import { dirname as dirname15, join as
|
|
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(
|
|
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 =
|
|
19204
|
-
|
|
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) ?
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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.`
|