@cabane/companion 0.6.188 → 0.6.190
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 +354 -188
- package/dist/launch.js +9 -1
- package/dist/pairing-config.js +9 -1
- package/dist/runtime.js +260 -176
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -788,7 +788,15 @@ var init_config = __esm({
|
|
|
788
788
|
// and an agent with no folder of its own and no prepare hook works there
|
|
789
789
|
// (`turn-execution.ts`, `prepareEnvironment`). Default `~/.cabane/work`: hidden, so pairing a
|
|
790
790
|
// laptop creates no new visible folder; set it to make the work visible.
|
|
791
|
-
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional()
|
|
791
|
+
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
792
|
+
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
793
|
+
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
794
|
+
// is the operator's (a box of their own making, ENG225); the companion knows nothing of it
|
|
795
|
+
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
796
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
|
|
797
|
+
runner: z2.object({
|
|
798
|
+
command: z2.array(z2.string().min(1)).min(1)
|
|
799
|
+
}).strict().optional()
|
|
792
800
|
});
|
|
793
801
|
legacyOfferSchema = z2.object({
|
|
794
802
|
claudeCode: z2.object({ enabled: z2.boolean().optional() }).passthrough().optional(),
|
|
@@ -1014,6 +1022,142 @@ var init_bundled_binaries = __esm({
|
|
|
1014
1022
|
}
|
|
1015
1023
|
});
|
|
1016
1024
|
|
|
1025
|
+
// src/harness-versions.ts
|
|
1026
|
+
function parseVersionToken(raw) {
|
|
1027
|
+
if (!raw) return null;
|
|
1028
|
+
const m = raw.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/);
|
|
1029
|
+
return m ? m[0] : null;
|
|
1030
|
+
}
|
|
1031
|
+
async function probeOpencodeVersion(serverUrl, fetchImpl = fetch) {
|
|
1032
|
+
try {
|
|
1033
|
+
const base = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl;
|
|
1034
|
+
const res = await fetchImpl(`${base}/global/health`, {
|
|
1035
|
+
headers: { accept: "application/json" }
|
|
1036
|
+
});
|
|
1037
|
+
if (!res.ok) return null;
|
|
1038
|
+
return extractOpencodeVersion(await res.json());
|
|
1039
|
+
} catch {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function extractOpencodeVersion(json) {
|
|
1044
|
+
if (!json || typeof json !== "object") return null;
|
|
1045
|
+
const obj = json;
|
|
1046
|
+
const candidates = [obj.version];
|
|
1047
|
+
for (const v of Object.values(obj)) {
|
|
1048
|
+
if (v && typeof v === "object") candidates.push(v.version);
|
|
1049
|
+
}
|
|
1050
|
+
for (const c of candidates) {
|
|
1051
|
+
if (typeof c === "string") {
|
|
1052
|
+
const parsed = parseVersionToken(c);
|
|
1053
|
+
if (parsed) return parsed;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
function emptyHarnessVersions() {
|
|
1059
|
+
return { ...EMPTY };
|
|
1060
|
+
}
|
|
1061
|
+
var EMPTY;
|
|
1062
|
+
var init_harness_versions = __esm({
|
|
1063
|
+
"src/harness-versions.ts"() {
|
|
1064
|
+
"use strict";
|
|
1065
|
+
EMPTY = { claudeCode: null, opencode: null, codex: null };
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// src/harness-usable.ts
|
|
1070
|
+
import { spawn as spawn2 } from "child_process";
|
|
1071
|
+
function apiKeyVariable(runtime, env = process.env) {
|
|
1072
|
+
return API_KEY_VARIABLES[runtime].find((name) => !!env[name]?.trim()) ?? null;
|
|
1073
|
+
}
|
|
1074
|
+
async function checkHarnessUsable(runtime, deps = {}) {
|
|
1075
|
+
const env = deps.env ?? process.env;
|
|
1076
|
+
const resolveBundled = deps.resolveBundled ?? ((r) => resolveBundledBinary(r));
|
|
1077
|
+
const run = deps.run ?? runBounded;
|
|
1078
|
+
const resolution = resolveBundled(runtime);
|
|
1079
|
+
if (resolution.status !== "present" || !resolution.path) {
|
|
1080
|
+
return {
|
|
1081
|
+
status: "bundle_missing",
|
|
1082
|
+
bundled: resolution.status === "present" ? "executable_missing" : resolution.status
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const path = resolution.path;
|
|
1086
|
+
const version = run(path, ["--version"], env).then(
|
|
1087
|
+
(r) => r.code === 0 ? parseVersionToken(r.output) : null
|
|
1088
|
+
);
|
|
1089
|
+
const variable = apiKeyVariable(runtime, env);
|
|
1090
|
+
if (variable) return { status: "api_key", variable, version: await version };
|
|
1091
|
+
const statusArgs = runtime === "codex" ? ["login", "status"] : ["auth", "status", "--json"];
|
|
1092
|
+
const [answer, v] = await Promise.all([run(path, statusArgs, env), version]);
|
|
1093
|
+
if (answer.error) return { status: "check_failed", reason: answer.error };
|
|
1094
|
+
const signedIn = runtime === "codex" ? readCodexStatus(answer) : readClaudeStatus(answer);
|
|
1095
|
+
if (typeof signedIn === "string") return { status: "check_failed", reason: signedIn };
|
|
1096
|
+
return signedIn ? { status: "signed_in", version: v } : { status: "signed_out", version: v };
|
|
1097
|
+
}
|
|
1098
|
+
function readClaudeStatus(answer) {
|
|
1099
|
+
const start2 = answer.output.indexOf("{");
|
|
1100
|
+
const end = answer.output.lastIndexOf("}");
|
|
1101
|
+
if (start2 < 0 || end < start2) return "unreadable";
|
|
1102
|
+
try {
|
|
1103
|
+
const parsed = JSON.parse(answer.output.slice(start2, end + 1));
|
|
1104
|
+
const loggedIn = parsed?.loggedIn;
|
|
1105
|
+
return typeof loggedIn === "boolean" ? loggedIn : "unreadable";
|
|
1106
|
+
} catch {
|
|
1107
|
+
return "unreadable";
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function readCodexStatus(answer) {
|
|
1111
|
+
if (answer.code === 0) return true;
|
|
1112
|
+
if (/not logged in/i.test(answer.output)) return false;
|
|
1113
|
+
if (/config/i.test(answer.output)) return "config";
|
|
1114
|
+
return "unreadable";
|
|
1115
|
+
}
|
|
1116
|
+
function runBounded(path, args, env) {
|
|
1117
|
+
return new Promise((resolve2) => {
|
|
1118
|
+
let settled = false;
|
|
1119
|
+
let out = "";
|
|
1120
|
+
const done = (result) => {
|
|
1121
|
+
if (settled) return;
|
|
1122
|
+
settled = true;
|
|
1123
|
+
clearTimeout(timer);
|
|
1124
|
+
resolve2(result);
|
|
1125
|
+
};
|
|
1126
|
+
let child;
|
|
1127
|
+
try {
|
|
1128
|
+
child = spawn2(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
|
|
1129
|
+
} catch {
|
|
1130
|
+
resolve2({ code: null, output: "", error: "spawn" });
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const capture = (chunk) => {
|
|
1134
|
+
if (out.length < 8192) out += chunk.toString();
|
|
1135
|
+
};
|
|
1136
|
+
child.stdout?.on("data", capture);
|
|
1137
|
+
child.stderr?.on("data", capture);
|
|
1138
|
+
const timer = setTimeout(() => {
|
|
1139
|
+
child.kill("SIGKILL");
|
|
1140
|
+
done({ code: null, output: out, error: "timeout" });
|
|
1141
|
+
}, CHECK_TIMEOUT_MS);
|
|
1142
|
+
timer.unref?.();
|
|
1143
|
+
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
1144
|
+
child.once("close", (code) => done({ code, output: out }));
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
var API_KEY_VARIABLES, CHECK_TIMEOUT_MS;
|
|
1148
|
+
var init_harness_usable = __esm({
|
|
1149
|
+
"src/harness-usable.ts"() {
|
|
1150
|
+
"use strict";
|
|
1151
|
+
init_bundled_binaries();
|
|
1152
|
+
init_harness_versions();
|
|
1153
|
+
API_KEY_VARIABLES = {
|
|
1154
|
+
"claude-code": ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
1155
|
+
codex: ["CODEX_API_KEY"]
|
|
1156
|
+
};
|
|
1157
|
+
CHECK_TIMEOUT_MS = 5e3;
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1017
1161
|
// packages/agent-runtime/src/failure.ts
|
|
1018
1162
|
import { z as z4 } from "zod";
|
|
1019
1163
|
function encodeFailureReason(failure) {
|
|
@@ -5828,6 +5972,10 @@ var init_transport2 = __esm({
|
|
|
5828
5972
|
});
|
|
5829
5973
|
|
|
5830
5974
|
// packages/agent-runtime/src/codex/index.ts
|
|
5975
|
+
function isMissingRollout(err) {
|
|
5976
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
5977
|
+
return /no rollout found for thread id/i.test(message2);
|
|
5978
|
+
}
|
|
5831
5979
|
function createCodexAdapter(deps = {}) {
|
|
5832
5980
|
const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
|
|
5833
5981
|
return {
|
|
@@ -5846,8 +5994,8 @@ function createCodexAdapter(deps = {}) {
|
|
|
5846
5994
|
}
|
|
5847
5995
|
const directory = req.local.cwd ?? "";
|
|
5848
5996
|
const decision = decideResume3(req.session, directory);
|
|
5849
|
-
|
|
5850
|
-
|
|
5997
|
+
let resumeThreadId = "resume" in decision ? decision.resume : null;
|
|
5998
|
+
let threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
|
|
5851
5999
|
if ("fresh" in decision && decision.reason === "cwd_mismatch") {
|
|
5852
6000
|
deps.onWarn?.(
|
|
5853
6001
|
"codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
|
|
@@ -5912,38 +6060,69 @@ function createCodexAdapter(deps = {}) {
|
|
|
5912
6060
|
}
|
|
5913
6061
|
if (signal.aborted) return;
|
|
5914
6062
|
}
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
model
|
|
5919
|
-
|
|
6063
|
+
for (; ; ) {
|
|
6064
|
+
const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
|
|
6065
|
+
if (spec.model === null) {
|
|
6066
|
+
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
6067
|
+
model: req.config.model
|
|
6068
|
+
});
|
|
6069
|
+
}
|
|
6070
|
+
let emitted = false;
|
|
6071
|
+
try {
|
|
6072
|
+
const result = await transport.run(spec, signal);
|
|
6073
|
+
if (signal.aborted) return;
|
|
6074
|
+
for await (const event of decodeCodexStream(result.events, {
|
|
6075
|
+
signal,
|
|
6076
|
+
cwd: req.local.cwd,
|
|
6077
|
+
resumedThreadId: resumeThreadId,
|
|
6078
|
+
degraded,
|
|
6079
|
+
// Which segments the thread carries once this turn lands. A segment sent is that one —
|
|
6080
|
+
// the later copy is what the model reads. One withheld is what the thread had. Read off
|
|
6081
|
+
// the spec, so the record can't disagree with what was sent.
|
|
6082
|
+
promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
|
|
6083
|
+
conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
|
|
6084
|
+
compactions,
|
|
6085
|
+
// What the thread held before this turn, for the write at `thread.started`.
|
|
6086
|
+
heldStanding: {
|
|
6087
|
+
promptFingerprint: threadStanding?.core ?? null,
|
|
6088
|
+
conversationFingerprint: threadStanding?.conversation ?? null,
|
|
6089
|
+
compactions: threadStanding ? compactions : null
|
|
6090
|
+
},
|
|
6091
|
+
storedState: req.session,
|
|
6092
|
+
usageBaseline: threadUsageBaseline,
|
|
6093
|
+
// Codex reports no model in-stream, so record what the thread was started with (null on the
|
|
6094
|
+
// default token). Same for the reasoning effort.
|
|
6095
|
+
resolvedModel: spec.model,
|
|
6096
|
+
resolvedReasoningEffort: spec.modelReasoningEffort ?? null
|
|
6097
|
+
})) {
|
|
6098
|
+
emitted = true;
|
|
6099
|
+
yield event;
|
|
6100
|
+
}
|
|
6101
|
+
return;
|
|
6102
|
+
} catch (err) {
|
|
6103
|
+
if (resumeThreadId === null || emitted || signal.aborted || !isMissingRollout(err)) {
|
|
6104
|
+
throw err;
|
|
6105
|
+
}
|
|
6106
|
+
deps.onWarn?.(
|
|
6107
|
+
"codex adapter: the stored thread has no rollout on this host \u2014 starting a fresh thread",
|
|
6108
|
+
{ threadId: resumeThreadId }
|
|
6109
|
+
);
|
|
6110
|
+
resumeThreadId = null;
|
|
6111
|
+
threadUsageBaseline = null;
|
|
6112
|
+
threadStanding = null;
|
|
6113
|
+
compactions = 0;
|
|
6114
|
+
req = { ...req, session: null };
|
|
6115
|
+
degraded = true;
|
|
6116
|
+
if (hooks?.fresh) {
|
|
6117
|
+
const recomposed = await hooks.fresh("session_start_failed");
|
|
6118
|
+
if (signal.aborted) return;
|
|
6119
|
+
if (recomposed) {
|
|
6120
|
+
req = { ...req, ...recomposed, session: null };
|
|
6121
|
+
degraded = false;
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
5920
6125
|
}
|
|
5921
|
-
const result = await transport.run(spec, signal);
|
|
5922
|
-
if (signal.aborted) return;
|
|
5923
|
-
yield* decodeCodexStream(result.events, {
|
|
5924
|
-
signal,
|
|
5925
|
-
cwd: req.local.cwd,
|
|
5926
|
-
resumedThreadId: resumeThreadId,
|
|
5927
|
-
degraded,
|
|
5928
|
-
// Which segments the thread carries once this turn lands. A segment sent is that one —
|
|
5929
|
-
// the later copy is what the model reads. One withheld is what the thread had. Read off
|
|
5930
|
-
// the spec, so the record can't disagree with what was sent.
|
|
5931
|
-
promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
|
|
5932
|
-
conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
|
|
5933
|
-
compactions,
|
|
5934
|
-
// What the thread held before this turn, for the write at `thread.started`.
|
|
5935
|
-
heldStanding: {
|
|
5936
|
-
promptFingerprint: threadStanding?.core ?? null,
|
|
5937
|
-
conversationFingerprint: threadStanding?.conversation ?? null,
|
|
5938
|
-
compactions: threadStanding ? compactions : null
|
|
5939
|
-
},
|
|
5940
|
-
storedState: req.session,
|
|
5941
|
-
usageBaseline: threadUsageBaseline,
|
|
5942
|
-
// Codex reports no model in-stream, so record what the thread was started with (null on the
|
|
5943
|
-
// default token). Same for the reasoning effort.
|
|
5944
|
-
resolvedModel: spec.model,
|
|
5945
|
-
resolvedReasoningEffort: spec.modelReasoningEffort ?? null
|
|
5946
|
-
});
|
|
5947
6126
|
} finally {
|
|
5948
6127
|
if (images) {
|
|
5949
6128
|
try {
|
|
@@ -7365,6 +7544,14 @@ function required(env, name) {
|
|
|
7365
7544
|
if (!value) throw new Error(`runner: ${name} is not set`);
|
|
7366
7545
|
return value;
|
|
7367
7546
|
}
|
|
7547
|
+
function runnerPlace(env) {
|
|
7548
|
+
const workRoot2 = env[PLACE_ENV.workRoot];
|
|
7549
|
+
const transcriptDir = env[PLACE_ENV.transcriptDir];
|
|
7550
|
+
return {
|
|
7551
|
+
...workRoot2 ? { workRoot: workRoot2 } : {},
|
|
7552
|
+
...transcriptDir ? { transcriptDir } : {}
|
|
7553
|
+
};
|
|
7554
|
+
}
|
|
7368
7555
|
function parseRunnerInput(env, stdin) {
|
|
7369
7556
|
const dispatch = dispatchSchema.parse(JSON.parse(required(env, RUNNER_ENV.dispatch)));
|
|
7370
7557
|
const spec = runnerSpecSchema.parse(JSON.parse(required(env, RUNNER_ENV.spec)));
|
|
@@ -7372,10 +7559,12 @@ function parseRunnerInput(env, stdin) {
|
|
|
7372
7559
|
return {
|
|
7373
7560
|
baseUrl: required(env, RUNNER_ENV.url),
|
|
7374
7561
|
credential: credentials.credential,
|
|
7562
|
+
...credentials.secrets ? { secrets: credentials.secrets } : {},
|
|
7375
7563
|
dispatch,
|
|
7376
7564
|
turnId: required(env, RUNNER_ENV.turnId),
|
|
7377
7565
|
resumed: env[RUNNER_ENV.resumed] === "1",
|
|
7378
|
-
spec
|
|
7566
|
+
spec,
|
|
7567
|
+
place: runnerPlace(env)
|
|
7379
7568
|
};
|
|
7380
7569
|
}
|
|
7381
7570
|
function runnerEnv(input) {
|
|
@@ -7388,7 +7577,10 @@ function runnerEnv(input) {
|
|
|
7388
7577
|
};
|
|
7389
7578
|
}
|
|
7390
7579
|
function runnerStdin(input) {
|
|
7391
|
-
return `${JSON.stringify({
|
|
7580
|
+
return `${JSON.stringify({
|
|
7581
|
+
credential: input.credential,
|
|
7582
|
+
...input.secrets ? { secrets: input.secrets } : {}
|
|
7583
|
+
})}
|
|
7392
7584
|
`;
|
|
7393
7585
|
}
|
|
7394
7586
|
function encodeLauncherMessage(message2) {
|
|
@@ -7418,7 +7610,7 @@ function decodeRunnerEvent(line) {
|
|
|
7418
7610
|
if (!value || typeof value !== "object" || !("cabaneRunner" in value)) return null;
|
|
7419
7611
|
return value;
|
|
7420
7612
|
}
|
|
7421
|
-
var RUNNER_ENV, runtimeHealthSchema, runnerSpecSchema, dispatchSchema, credentialsSchema;
|
|
7613
|
+
var RUNNER_ENV, PLACE_ENV, runtimeHealthSchema, runnerSpecSchema, dispatchSchema, credentialsSchema;
|
|
7422
7614
|
var init_protocol = __esm({
|
|
7423
7615
|
"src/runner/protocol.ts"() {
|
|
7424
7616
|
"use strict";
|
|
@@ -7437,6 +7629,10 @@ var init_protocol = __esm({
|
|
|
7437
7629
|
// Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
|
|
7438
7630
|
spec: "CABANE_RUNNER_SPEC"
|
|
7439
7631
|
};
|
|
7632
|
+
PLACE_ENV = {
|
|
7633
|
+
workRoot: "CABANE_WORK_ROOT",
|
|
7634
|
+
transcriptDir: "CABANE_TRANSCRIPT_DIR"
|
|
7635
|
+
};
|
|
7440
7636
|
runtimeHealthSchema = z14.object({
|
|
7441
7637
|
quotaState: z14.enum(["ok", "limited", "auth_expired", "unknown"]),
|
|
7442
7638
|
limitedUntil: z14.string().nullable(),
|
|
@@ -7486,7 +7682,8 @@ var init_protocol = __esm({
|
|
|
7486
7682
|
episode: z14.number().int().optional()
|
|
7487
7683
|
});
|
|
7488
7684
|
credentialsSchema = z14.object({
|
|
7489
|
-
credential: z14.string().startsWith("cabrn_")
|
|
7685
|
+
credential: z14.string().startsWith("cabrn_"),
|
|
7686
|
+
secrets: z14.record(z14.string(), z14.string()).optional()
|
|
7490
7687
|
});
|
|
7491
7688
|
}
|
|
7492
7689
|
});
|
|
@@ -7498,6 +7695,19 @@ function selfRunnerCommand() {
|
|
|
7498
7695
|
if (!script) throw new Error("runner: cannot tell which script started this companion");
|
|
7499
7696
|
return [process.execPath, ...process.execArgv, script, "run-turn"];
|
|
7500
7697
|
}
|
|
7698
|
+
function wrappedRunnerCommand(prefix, own) {
|
|
7699
|
+
return [...prefix ?? [], ...own];
|
|
7700
|
+
}
|
|
7701
|
+
function runnerExecEnv(base, boxed) {
|
|
7702
|
+
const env = {};
|
|
7703
|
+
for (const [k, v] of Object.entries(base)) {
|
|
7704
|
+
if (v === void 0) continue;
|
|
7705
|
+
if (boxed && (BOXED_ENV_DROPPED.includes(k) || k.startsWith(BOXED_ENV_DROPPED_PREFIX)))
|
|
7706
|
+
continue;
|
|
7707
|
+
env[k] = v;
|
|
7708
|
+
}
|
|
7709
|
+
return env;
|
|
7710
|
+
}
|
|
7501
7711
|
function healthSnapshot(store) {
|
|
7502
7712
|
const out = {};
|
|
7503
7713
|
for (const r of store?.reports() ?? []) {
|
|
@@ -7694,13 +7904,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7694
7904
|
return { ok: false, durationMs: durationMs2, reason: reason2 };
|
|
7695
7905
|
}
|
|
7696
7906
|
try {
|
|
7697
|
-
const
|
|
7698
|
-
|
|
7699
|
-
if (v !== void 0) env[k] = v;
|
|
7700
|
-
}
|
|
7907
|
+
const boxed = launch.boxed === true;
|
|
7908
|
+
const env = runnerExecEnv(launch.env ?? process.env, boxed);
|
|
7701
7909
|
const input = {
|
|
7702
7910
|
baseUrl: opts.baseUrl,
|
|
7703
7911
|
credential: minted.credential,
|
|
7912
|
+
...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
|
|
7704
7913
|
dispatch: payload,
|
|
7705
7914
|
turnId,
|
|
7706
7915
|
resumed: handleOpts.resumed,
|
|
@@ -7772,7 +7981,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7772
7981
|
locks?.release(lockOwner(event.id));
|
|
7773
7982
|
return;
|
|
7774
7983
|
case "process_group":
|
|
7775
|
-
groups.push(event.pgid);
|
|
7984
|
+
if (!boxed) groups.push(event.pgid);
|
|
7776
7985
|
return;
|
|
7777
7986
|
case "remote_session":
|
|
7778
7987
|
remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
|
|
@@ -7793,7 +8002,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7793
8002
|
}
|
|
7794
8003
|
if (result) return result;
|
|
7795
8004
|
const durationMs = Date.now() - startedAt;
|
|
7796
|
-
const
|
|
8005
|
+
const stopped = controller.signal.aborted && thrown === void 0;
|
|
8006
|
+
const reason = stopped ? "cancelled" : thrown !== void 0 ? `runner_failed: ${thrown}` : `runner_exited: ${code}`;
|
|
7797
8007
|
await child?.reap(groups);
|
|
7798
8008
|
const reaped = await (launch.reapTurn ?? reapTurnScopes)(turnId).catch((err) => ({
|
|
7799
8009
|
failures: [err instanceof Error ? err.message : String(err)]
|
|
@@ -7808,8 +8018,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7808
8018
|
await opts.api.setActiveRun(opts.workspaceId, payload.conversationId, payload.agentId, {
|
|
7809
8019
|
activeRunStartedAt: null,
|
|
7810
8020
|
turnId,
|
|
7811
|
-
outcome: "failed",
|
|
7812
|
-
errorReason: reason.slice(0, ERROR_REASON_MAX),
|
|
8021
|
+
...stopped ? { outcome: "interrupted" } : { outcome: "failed", errorReason: reason.slice(0, ERROR_REASON_MAX) },
|
|
7813
8022
|
readThrough: payload.messageId
|
|
7814
8023
|
});
|
|
7815
8024
|
} catch (err) {
|
|
@@ -7827,11 +8036,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7827
8036
|
reason: "exited"
|
|
7828
8037
|
});
|
|
7829
8038
|
if (thrown !== void 0) throw new Error(thrown);
|
|
7830
|
-
log2.
|
|
8039
|
+
if (stopped) log2.info({ turnId, code }, "runner: stopped before it could report");
|
|
8040
|
+
else log2.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
7831
8041
|
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
7832
8042
|
return { ok: false, durationMs, reason };
|
|
7833
8043
|
}
|
|
7834
|
-
var PINO_LEVELS;
|
|
8044
|
+
var BOXED_ENV_DROPPED, BOXED_ENV_DROPPED_PREFIX, PINO_LEVELS;
|
|
7835
8045
|
var init_launch = __esm({
|
|
7836
8046
|
"src/runner/launch.ts"() {
|
|
7837
8047
|
"use strict";
|
|
@@ -7842,6 +8052,16 @@ var init_launch = __esm({
|
|
|
7842
8052
|
init_boot_id();
|
|
7843
8053
|
init_turn_containment();
|
|
7844
8054
|
init_protocol();
|
|
8055
|
+
BOXED_ENV_DROPPED = [
|
|
8056
|
+
"ANTHROPIC_API_KEY",
|
|
8057
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
8058
|
+
"CODEX_API_KEY",
|
|
8059
|
+
"GH_TOKEN",
|
|
8060
|
+
"GITHUB_TOKEN",
|
|
8061
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
8062
|
+
"XDG_RUNTIME_DIR"
|
|
8063
|
+
];
|
|
8064
|
+
BOXED_ENV_DROPPED_PREFIX = "CABANE_GATE_";
|
|
7845
8065
|
PINO_LEVELS = {
|
|
7846
8066
|
10: "trace",
|
|
7847
8067
|
20: "debug",
|
|
@@ -8456,9 +8676,9 @@ import { z as z16 } from "zod";
|
|
|
8456
8676
|
function secretsPath() {
|
|
8457
8677
|
return join19(cabaneDir(), "secrets.json");
|
|
8458
8678
|
}
|
|
8459
|
-
function
|
|
8679
|
+
function loadSecretValues() {
|
|
8460
8680
|
const path = secretsPath();
|
|
8461
|
-
if (!existsSync10(path)) return
|
|
8681
|
+
if (!existsSync10(path)) return {};
|
|
8462
8682
|
let raw;
|
|
8463
8683
|
try {
|
|
8464
8684
|
raw = readFileSync8(path, "utf8");
|
|
@@ -8467,7 +8687,7 @@ function loadSecretStore() {
|
|
|
8467
8687
|
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
8468
8688
|
);
|
|
8469
8689
|
}
|
|
8470
|
-
if (raw.trim().length === 0) return
|
|
8690
|
+
if (raw.trim().length === 0) return {};
|
|
8471
8691
|
let parsed;
|
|
8472
8692
|
try {
|
|
8473
8693
|
parsed = JSON.parse(raw);
|
|
@@ -8482,14 +8702,17 @@ function loadSecretStore() {
|
|
|
8482
8702
|
`${path} must be a flat object mapping secret names to string values (e.g. { "GITHUB_TOKEN": "ghp_\u2026" }).`
|
|
8483
8703
|
);
|
|
8484
8704
|
}
|
|
8485
|
-
return
|
|
8705
|
+
return result.data;
|
|
8486
8706
|
}
|
|
8487
8707
|
function loadSecretStoreTolerant(onWarn) {
|
|
8708
|
+
return makeStore(loadSecretValuesTolerant(onWarn));
|
|
8709
|
+
}
|
|
8710
|
+
function loadSecretValuesTolerant(onWarn) {
|
|
8488
8711
|
try {
|
|
8489
|
-
return
|
|
8712
|
+
return loadSecretValues();
|
|
8490
8713
|
} catch (err) {
|
|
8491
8714
|
onWarn?.(err instanceof Error ? err.message : String(err));
|
|
8492
|
-
return
|
|
8715
|
+
return {};
|
|
8493
8716
|
}
|
|
8494
8717
|
}
|
|
8495
8718
|
function makeStore(map) {
|
|
@@ -8581,6 +8804,31 @@ var init_secrets = __esm({
|
|
|
8581
8804
|
}
|
|
8582
8805
|
});
|
|
8583
8806
|
|
|
8807
|
+
// src/turn-env.ts
|
|
8808
|
+
function harnessLoginEnv(store, runtime) {
|
|
8809
|
+
const names = [
|
|
8810
|
+
...runtime in API_KEY_VARIABLES ? API_KEY_VARIABLES[runtime] : [],
|
|
8811
|
+
GITHUB_TOKEN_NAME
|
|
8812
|
+
];
|
|
8813
|
+
const env = {};
|
|
8814
|
+
for (const name of names) {
|
|
8815
|
+
const value = store.get(name);
|
|
8816
|
+
if (value) env[name] = value;
|
|
8817
|
+
}
|
|
8818
|
+
return env;
|
|
8819
|
+
}
|
|
8820
|
+
function turnIdentityEnv(ids) {
|
|
8821
|
+
return { CABANE_CONVERSATION_ID: ids.conversationId, CABANE_AGENT_ID: ids.agentId };
|
|
8822
|
+
}
|
|
8823
|
+
var GITHUB_TOKEN_NAME;
|
|
8824
|
+
var init_turn_env = __esm({
|
|
8825
|
+
"src/turn-env.ts"() {
|
|
8826
|
+
"use strict";
|
|
8827
|
+
init_harness_usable();
|
|
8828
|
+
GITHUB_TOKEN_NAME = "GH_TOKEN";
|
|
8829
|
+
}
|
|
8830
|
+
});
|
|
8831
|
+
|
|
8584
8832
|
// src/transcript-writer.ts
|
|
8585
8833
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
8586
8834
|
import { basename as basename3, dirname as dirname7, join as join20 } from "path";
|
|
@@ -8971,6 +9219,7 @@ var init_turn_execution = __esm({
|
|
|
8971
9219
|
init_prepare_failure();
|
|
8972
9220
|
init_prepared();
|
|
8973
9221
|
init_secrets();
|
|
9222
|
+
init_turn_env();
|
|
8974
9223
|
init_turn_control_tools();
|
|
8975
9224
|
init_transcript_writer();
|
|
8976
9225
|
init_turn_containment();
|
|
@@ -9038,6 +9287,7 @@ var init_turn_execution = __esm({
|
|
|
9038
9287
|
// The harness's secret environment for this turn. Only ever handed to the
|
|
9039
9288
|
// adapter; never logged, never merged into `process.env`.
|
|
9040
9289
|
secretEnv = {};
|
|
9290
|
+
secretStore;
|
|
9041
9291
|
effectiveCwd;
|
|
9042
9292
|
hookEnv;
|
|
9043
9293
|
turnEnv;
|
|
@@ -9326,7 +9576,8 @@ var init_turn_execution = __esm({
|
|
|
9326
9576
|
throw this.concluded(reason, reason);
|
|
9327
9577
|
}
|
|
9328
9578
|
this.resolvedMcpServers = resolvedMcpServers;
|
|
9329
|
-
this.secretEnv = secretEnv;
|
|
9579
|
+
this.secretEnv = { ...secretEnv, ...harnessLoginEnv(secretStore, this.turnContext.runtime) };
|
|
9580
|
+
this.secretStore = secretStore;
|
|
9330
9581
|
}
|
|
9331
9582
|
async prepareEnvironment() {
|
|
9332
9583
|
const { payload, workspaceId, turnId, turnLog } = this;
|
|
@@ -9477,6 +9728,10 @@ var init_turn_execution = __esm({
|
|
|
9477
9728
|
}
|
|
9478
9729
|
const turnEnv = {
|
|
9479
9730
|
...hookEnv,
|
|
9731
|
+
...turnIdentityEnv({
|
|
9732
|
+
conversationId: this.payload.conversationId,
|
|
9733
|
+
agentId: this.payload.agentId
|
|
9734
|
+
}),
|
|
9480
9735
|
CABANE_COMPANION_HOME: agentCompanionHome()
|
|
9481
9736
|
};
|
|
9482
9737
|
this.codexLocal = this.turnContext.runtime === "codex" ? { configSetsSandboxMode: await readCodexConfigSetsSandboxMode() } : void 0;
|
|
@@ -10551,8 +10806,14 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
|
|
|
10551
10806
|
agentUsername: spec.agentUsername,
|
|
10552
10807
|
credential: input.credential,
|
|
10553
10808
|
local: spec.local,
|
|
10554
|
-
|
|
10809
|
+
// The place the runner stands in names its own root and transcript
|
|
10810
|
+
// directory when it has them (`PLACE_ENV`); the launcher's host-computed
|
|
10811
|
+
// values are what a runner on the person's own machine gets.
|
|
10812
|
+
workRoot: input.place?.workRoot ?? spec.workRoot,
|
|
10555
10813
|
runConfig: spec.runConfig,
|
|
10814
|
+
// The launcher's copy of the device's secrets, when it sent one: a runner
|
|
10815
|
+
// in a box has no `secrets.json` to read.
|
|
10816
|
+
...input.secrets ? { secretStore: makeStore(input.secrets) } : {},
|
|
10556
10817
|
log: log2,
|
|
10557
10818
|
...queryFn ? { queryFn } : {},
|
|
10558
10819
|
...spec.opencodeServerUrl ? { opencodeServerUrl: spec.opencodeServerUrl } : {},
|
|
@@ -10562,7 +10823,7 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
|
|
|
10562
10823
|
...spec.totalTimeoutMs !== void 0 ? { totalTimeoutMs: spec.totalTimeoutMs } : {},
|
|
10563
10824
|
...spec.leaseRenewalMs !== void 0 ? { leaseRenewalMs: spec.leaseRenewalMs } : {},
|
|
10564
10825
|
...spec.preparingRowDelayMs !== void 0 ? { preparingRowDelayMs: spec.preparingRowDelayMs } : {},
|
|
10565
|
-
...spec.transcripts ? { transcriptDir: transcriptsDir() } : {},
|
|
10826
|
+
...spec.transcripts ? { transcriptDir: input.place?.transcriptDir ?? transcriptsDir() } : {},
|
|
10566
10827
|
connectorHealth,
|
|
10567
10828
|
inRunner: {
|
|
10568
10829
|
bootId: spec.bootId,
|
|
@@ -10697,6 +10958,7 @@ var init_run_turn = __esm({
|
|
|
10697
10958
|
init_exec_shim();
|
|
10698
10959
|
init_launcher_channel();
|
|
10699
10960
|
init_opencode_lock();
|
|
10961
|
+
init_secrets();
|
|
10700
10962
|
init_transcript_writer();
|
|
10701
10963
|
init_turn_execution();
|
|
10702
10964
|
init_protocol();
|
|
@@ -11134,133 +11396,8 @@ function spaced(sentence) {
|
|
|
11134
11396
|
|
|
11135
11397
|
// src/harness-detect.ts
|
|
11136
11398
|
init_config();
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
init_bundled_binaries();
|
|
11140
|
-
import { spawn as spawn2 } from "child_process";
|
|
11141
|
-
|
|
11142
|
-
// src/harness-versions.ts
|
|
11143
|
-
var EMPTY = { claudeCode: null, opencode: null, codex: null };
|
|
11144
|
-
function parseVersionToken(raw) {
|
|
11145
|
-
if (!raw) return null;
|
|
11146
|
-
const m = raw.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/);
|
|
11147
|
-
return m ? m[0] : null;
|
|
11148
|
-
}
|
|
11149
|
-
async function probeOpencodeVersion(serverUrl, fetchImpl = fetch) {
|
|
11150
|
-
try {
|
|
11151
|
-
const base = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl;
|
|
11152
|
-
const res = await fetchImpl(`${base}/global/health`, {
|
|
11153
|
-
headers: { accept: "application/json" }
|
|
11154
|
-
});
|
|
11155
|
-
if (!res.ok) return null;
|
|
11156
|
-
return extractOpencodeVersion(await res.json());
|
|
11157
|
-
} catch {
|
|
11158
|
-
return null;
|
|
11159
|
-
}
|
|
11160
|
-
}
|
|
11161
|
-
function extractOpencodeVersion(json) {
|
|
11162
|
-
if (!json || typeof json !== "object") return null;
|
|
11163
|
-
const obj = json;
|
|
11164
|
-
const candidates = [obj.version];
|
|
11165
|
-
for (const v of Object.values(obj)) {
|
|
11166
|
-
if (v && typeof v === "object") candidates.push(v.version);
|
|
11167
|
-
}
|
|
11168
|
-
for (const c of candidates) {
|
|
11169
|
-
if (typeof c === "string") {
|
|
11170
|
-
const parsed = parseVersionToken(c);
|
|
11171
|
-
if (parsed) return parsed;
|
|
11172
|
-
}
|
|
11173
|
-
}
|
|
11174
|
-
return null;
|
|
11175
|
-
}
|
|
11176
|
-
function emptyHarnessVersions() {
|
|
11177
|
-
return { ...EMPTY };
|
|
11178
|
-
}
|
|
11179
|
-
|
|
11180
|
-
// src/harness-usable.ts
|
|
11181
|
-
var API_KEY_VARIABLES = {
|
|
11182
|
-
"claude-code": ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
11183
|
-
codex: ["CODEX_API_KEY"]
|
|
11184
|
-
};
|
|
11185
|
-
function apiKeyVariable(runtime, env = process.env) {
|
|
11186
|
-
return API_KEY_VARIABLES[runtime].find((name) => !!env[name]?.trim()) ?? null;
|
|
11187
|
-
}
|
|
11188
|
-
var CHECK_TIMEOUT_MS = 5e3;
|
|
11189
|
-
async function checkHarnessUsable(runtime, deps = {}) {
|
|
11190
|
-
const env = deps.env ?? process.env;
|
|
11191
|
-
const resolveBundled = deps.resolveBundled ?? ((r) => resolveBundledBinary(r));
|
|
11192
|
-
const run = deps.run ?? runBounded;
|
|
11193
|
-
const resolution = resolveBundled(runtime);
|
|
11194
|
-
if (resolution.status !== "present" || !resolution.path) {
|
|
11195
|
-
return {
|
|
11196
|
-
status: "bundle_missing",
|
|
11197
|
-
bundled: resolution.status === "present" ? "executable_missing" : resolution.status
|
|
11198
|
-
};
|
|
11199
|
-
}
|
|
11200
|
-
const path = resolution.path;
|
|
11201
|
-
const version = run(path, ["--version"], env).then(
|
|
11202
|
-
(r) => r.code === 0 ? parseVersionToken(r.output) : null
|
|
11203
|
-
);
|
|
11204
|
-
const variable = apiKeyVariable(runtime, env);
|
|
11205
|
-
if (variable) return { status: "api_key", variable, version: await version };
|
|
11206
|
-
const statusArgs = runtime === "codex" ? ["login", "status"] : ["auth", "status", "--json"];
|
|
11207
|
-
const [answer, v] = await Promise.all([run(path, statusArgs, env), version]);
|
|
11208
|
-
if (answer.error) return { status: "check_failed", reason: answer.error };
|
|
11209
|
-
const signedIn = runtime === "codex" ? readCodexStatus(answer) : readClaudeStatus(answer);
|
|
11210
|
-
if (typeof signedIn === "string") return { status: "check_failed", reason: signedIn };
|
|
11211
|
-
return signedIn ? { status: "signed_in", version: v } : { status: "signed_out", version: v };
|
|
11212
|
-
}
|
|
11213
|
-
function readClaudeStatus(answer) {
|
|
11214
|
-
const start2 = answer.output.indexOf("{");
|
|
11215
|
-
const end = answer.output.lastIndexOf("}");
|
|
11216
|
-
if (start2 < 0 || end < start2) return "unreadable";
|
|
11217
|
-
try {
|
|
11218
|
-
const parsed = JSON.parse(answer.output.slice(start2, end + 1));
|
|
11219
|
-
const loggedIn = parsed?.loggedIn;
|
|
11220
|
-
return typeof loggedIn === "boolean" ? loggedIn : "unreadable";
|
|
11221
|
-
} catch {
|
|
11222
|
-
return "unreadable";
|
|
11223
|
-
}
|
|
11224
|
-
}
|
|
11225
|
-
function readCodexStatus(answer) {
|
|
11226
|
-
if (answer.code === 0) return true;
|
|
11227
|
-
if (/not logged in/i.test(answer.output)) return false;
|
|
11228
|
-
if (/config/i.test(answer.output)) return "config";
|
|
11229
|
-
return "unreadable";
|
|
11230
|
-
}
|
|
11231
|
-
function runBounded(path, args, env) {
|
|
11232
|
-
return new Promise((resolve2) => {
|
|
11233
|
-
let settled = false;
|
|
11234
|
-
let out = "";
|
|
11235
|
-
const done = (result) => {
|
|
11236
|
-
if (settled) return;
|
|
11237
|
-
settled = true;
|
|
11238
|
-
clearTimeout(timer);
|
|
11239
|
-
resolve2(result);
|
|
11240
|
-
};
|
|
11241
|
-
let child;
|
|
11242
|
-
try {
|
|
11243
|
-
child = spawn2(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
|
|
11244
|
-
} catch {
|
|
11245
|
-
resolve2({ code: null, output: "", error: "spawn" });
|
|
11246
|
-
return;
|
|
11247
|
-
}
|
|
11248
|
-
const capture = (chunk) => {
|
|
11249
|
-
if (out.length < 8192) out += chunk.toString();
|
|
11250
|
-
};
|
|
11251
|
-
child.stdout?.on("data", capture);
|
|
11252
|
-
child.stderr?.on("data", capture);
|
|
11253
|
-
const timer = setTimeout(() => {
|
|
11254
|
-
child.kill("SIGKILL");
|
|
11255
|
-
done({ code: null, output: out, error: "timeout" });
|
|
11256
|
-
}, CHECK_TIMEOUT_MS);
|
|
11257
|
-
timer.unref?.();
|
|
11258
|
-
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
11259
|
-
child.once("close", (code) => done({ code, output: out }));
|
|
11260
|
-
});
|
|
11261
|
-
}
|
|
11262
|
-
|
|
11263
|
-
// src/harness-detect.ts
|
|
11399
|
+
init_harness_usable();
|
|
11400
|
+
init_harness_versions();
|
|
11264
11401
|
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
11265
11402
|
var DEFAULT_OPENCODE_PROBE_MS = 1e3;
|
|
11266
11403
|
var CONFIGURED_OPENCODE_PROBE_MS = 4e3;
|
|
@@ -12985,6 +13122,10 @@ function runtimeIssuesFor(gates) {
|
|
|
12985
13122
|
return issues;
|
|
12986
13123
|
}
|
|
12987
13124
|
|
|
13125
|
+
// src/supervisor.ts
|
|
13126
|
+
init_harness_versions();
|
|
13127
|
+
init_harness_usable();
|
|
13128
|
+
|
|
12988
13129
|
// src/opencode-models.ts
|
|
12989
13130
|
var OPENCODE_RUNTIME = "opencode";
|
|
12990
13131
|
function mapOpencodeProviders(json) {
|
|
@@ -14473,7 +14614,16 @@ var CompanionSupervisor = class {
|
|
|
14473
14614
|
},
|
|
14474
14615
|
runner: {
|
|
14475
14616
|
provider: this.runnerProvider,
|
|
14476
|
-
command:
|
|
14617
|
+
command: wrappedRunnerCommand(
|
|
14618
|
+
this.config.runner?.command,
|
|
14619
|
+
this.runnerCommand ?? selfRunnerCommand()
|
|
14620
|
+
),
|
|
14621
|
+
...this.config.runner?.command ? {
|
|
14622
|
+
boxed: true,
|
|
14623
|
+
secrets: () => loadSecretValuesTolerant(
|
|
14624
|
+
(m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
|
|
14625
|
+
)
|
|
14626
|
+
} : {},
|
|
14477
14627
|
...this.runnerEnv ? { env: this.runnerEnv } : {},
|
|
14478
14628
|
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
14479
14629
|
connectorHealth: this.connectorHealth,
|
|
@@ -17109,7 +17259,7 @@ init_errors();
|
|
|
17109
17259
|
init_src();
|
|
17110
17260
|
init_transcript_writer();
|
|
17111
17261
|
import { existsSync as existsSync19, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "fs";
|
|
17112
|
-
import { isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
17262
|
+
import { basename as basename4, isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
17113
17263
|
async function transcript(opts = {}) {
|
|
17114
17264
|
const dir2 = transcriptsDir();
|
|
17115
17265
|
if (opts.follow) {
|
|
@@ -17232,13 +17382,28 @@ async function followTranscripts(dir2) {
|
|
|
17232
17382
|
follower.tick();
|
|
17233
17383
|
});
|
|
17234
17384
|
}
|
|
17235
|
-
function
|
|
17385
|
+
function listTranscriptFiles(dir2) {
|
|
17386
|
+
let entries;
|
|
17236
17387
|
try {
|
|
17237
|
-
|
|
17388
|
+
entries = readdirSync6(dir2, { withFileTypes: true });
|
|
17238
17389
|
} catch {
|
|
17239
17390
|
return [];
|
|
17240
17391
|
}
|
|
17392
|
+
const files = [];
|
|
17393
|
+
for (const entry of entries) {
|
|
17394
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(entry.name);
|
|
17395
|
+
if (!entry.isDirectory() || entry.name === "anomalies") continue;
|
|
17396
|
+
let inner;
|
|
17397
|
+
try {
|
|
17398
|
+
inner = readdirSync6(join31(dir2, entry.name));
|
|
17399
|
+
} catch {
|
|
17400
|
+
continue;
|
|
17401
|
+
}
|
|
17402
|
+
for (const name of inner) if (name.endsWith(".jsonl")) files.push(`${entry.name}/${name}`);
|
|
17403
|
+
}
|
|
17404
|
+
return files.sort((a, b) => basename4(a) < basename4(b) ? 1 : basename4(a) > basename4(b) ? -1 : 0);
|
|
17241
17405
|
}
|
|
17406
|
+
var listFiles = listTranscriptFiles;
|
|
17242
17407
|
function printList(dir2) {
|
|
17243
17408
|
const files = listFiles(dir2);
|
|
17244
17409
|
if (files.length === 0) {
|
|
@@ -17285,6 +17450,7 @@ function peek(path) {
|
|
|
17285
17450
|
function resolveTarget(dir2, target) {
|
|
17286
17451
|
if (isAbsolute2(target) || target.includes("/")) {
|
|
17287
17452
|
if (existsSync19(target)) return target;
|
|
17453
|
+
if (!isAbsolute2(target) && existsSync19(join31(dir2, target))) return join31(dir2, target);
|
|
17288
17454
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
17289
17455
|
}
|
|
17290
17456
|
const exact = join31(dir2, target);
|