@cabane/companion 0.6.188 → 0.6.189
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 +286 -155
- package/dist/launch.js +9 -1
- package/dist/pairing-config.js +9 -1
- package/dist/runtime.js +192 -143
- 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) {
|
|
@@ -7365,6 +7509,14 @@ function required(env, name) {
|
|
|
7365
7509
|
if (!value) throw new Error(`runner: ${name} is not set`);
|
|
7366
7510
|
return value;
|
|
7367
7511
|
}
|
|
7512
|
+
function runnerPlace(env) {
|
|
7513
|
+
const workRoot2 = env[PLACE_ENV.workRoot];
|
|
7514
|
+
const transcriptDir = env[PLACE_ENV.transcriptDir];
|
|
7515
|
+
return {
|
|
7516
|
+
...workRoot2 ? { workRoot: workRoot2 } : {},
|
|
7517
|
+
...transcriptDir ? { transcriptDir } : {}
|
|
7518
|
+
};
|
|
7519
|
+
}
|
|
7368
7520
|
function parseRunnerInput(env, stdin) {
|
|
7369
7521
|
const dispatch = dispatchSchema.parse(JSON.parse(required(env, RUNNER_ENV.dispatch)));
|
|
7370
7522
|
const spec = runnerSpecSchema.parse(JSON.parse(required(env, RUNNER_ENV.spec)));
|
|
@@ -7372,10 +7524,12 @@ function parseRunnerInput(env, stdin) {
|
|
|
7372
7524
|
return {
|
|
7373
7525
|
baseUrl: required(env, RUNNER_ENV.url),
|
|
7374
7526
|
credential: credentials.credential,
|
|
7527
|
+
...credentials.secrets ? { secrets: credentials.secrets } : {},
|
|
7375
7528
|
dispatch,
|
|
7376
7529
|
turnId: required(env, RUNNER_ENV.turnId),
|
|
7377
7530
|
resumed: env[RUNNER_ENV.resumed] === "1",
|
|
7378
|
-
spec
|
|
7531
|
+
spec,
|
|
7532
|
+
place: runnerPlace(env)
|
|
7379
7533
|
};
|
|
7380
7534
|
}
|
|
7381
7535
|
function runnerEnv(input) {
|
|
@@ -7388,7 +7542,10 @@ function runnerEnv(input) {
|
|
|
7388
7542
|
};
|
|
7389
7543
|
}
|
|
7390
7544
|
function runnerStdin(input) {
|
|
7391
|
-
return `${JSON.stringify({
|
|
7545
|
+
return `${JSON.stringify({
|
|
7546
|
+
credential: input.credential,
|
|
7547
|
+
...input.secrets ? { secrets: input.secrets } : {}
|
|
7548
|
+
})}
|
|
7392
7549
|
`;
|
|
7393
7550
|
}
|
|
7394
7551
|
function encodeLauncherMessage(message2) {
|
|
@@ -7418,7 +7575,7 @@ function decodeRunnerEvent(line) {
|
|
|
7418
7575
|
if (!value || typeof value !== "object" || !("cabaneRunner" in value)) return null;
|
|
7419
7576
|
return value;
|
|
7420
7577
|
}
|
|
7421
|
-
var RUNNER_ENV, runtimeHealthSchema, runnerSpecSchema, dispatchSchema, credentialsSchema;
|
|
7578
|
+
var RUNNER_ENV, PLACE_ENV, runtimeHealthSchema, runnerSpecSchema, dispatchSchema, credentialsSchema;
|
|
7422
7579
|
var init_protocol = __esm({
|
|
7423
7580
|
"src/runner/protocol.ts"() {
|
|
7424
7581
|
"use strict";
|
|
@@ -7437,6 +7594,10 @@ var init_protocol = __esm({
|
|
|
7437
7594
|
// Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
|
|
7438
7595
|
spec: "CABANE_RUNNER_SPEC"
|
|
7439
7596
|
};
|
|
7597
|
+
PLACE_ENV = {
|
|
7598
|
+
workRoot: "CABANE_WORK_ROOT",
|
|
7599
|
+
transcriptDir: "CABANE_TRANSCRIPT_DIR"
|
|
7600
|
+
};
|
|
7440
7601
|
runtimeHealthSchema = z14.object({
|
|
7441
7602
|
quotaState: z14.enum(["ok", "limited", "auth_expired", "unknown"]),
|
|
7442
7603
|
limitedUntil: z14.string().nullable(),
|
|
@@ -7486,7 +7647,8 @@ var init_protocol = __esm({
|
|
|
7486
7647
|
episode: z14.number().int().optional()
|
|
7487
7648
|
});
|
|
7488
7649
|
credentialsSchema = z14.object({
|
|
7489
|
-
credential: z14.string().startsWith("cabrn_")
|
|
7650
|
+
credential: z14.string().startsWith("cabrn_"),
|
|
7651
|
+
secrets: z14.record(z14.string(), z14.string()).optional()
|
|
7490
7652
|
});
|
|
7491
7653
|
}
|
|
7492
7654
|
});
|
|
@@ -7498,6 +7660,19 @@ function selfRunnerCommand() {
|
|
|
7498
7660
|
if (!script) throw new Error("runner: cannot tell which script started this companion");
|
|
7499
7661
|
return [process.execPath, ...process.execArgv, script, "run-turn"];
|
|
7500
7662
|
}
|
|
7663
|
+
function wrappedRunnerCommand(prefix, own) {
|
|
7664
|
+
return [...prefix ?? [], ...own];
|
|
7665
|
+
}
|
|
7666
|
+
function runnerExecEnv(base, boxed) {
|
|
7667
|
+
const env = {};
|
|
7668
|
+
for (const [k, v] of Object.entries(base)) {
|
|
7669
|
+
if (v === void 0) continue;
|
|
7670
|
+
if (boxed && (BOXED_ENV_DROPPED.includes(k) || k.startsWith(BOXED_ENV_DROPPED_PREFIX)))
|
|
7671
|
+
continue;
|
|
7672
|
+
env[k] = v;
|
|
7673
|
+
}
|
|
7674
|
+
return env;
|
|
7675
|
+
}
|
|
7501
7676
|
function healthSnapshot(store) {
|
|
7502
7677
|
const out = {};
|
|
7503
7678
|
for (const r of store?.reports() ?? []) {
|
|
@@ -7694,13 +7869,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7694
7869
|
return { ok: false, durationMs: durationMs2, reason: reason2 };
|
|
7695
7870
|
}
|
|
7696
7871
|
try {
|
|
7697
|
-
const
|
|
7698
|
-
|
|
7699
|
-
if (v !== void 0) env[k] = v;
|
|
7700
|
-
}
|
|
7872
|
+
const boxed = launch.boxed === true;
|
|
7873
|
+
const env = runnerExecEnv(launch.env ?? process.env, boxed);
|
|
7701
7874
|
const input = {
|
|
7702
7875
|
baseUrl: opts.baseUrl,
|
|
7703
7876
|
credential: minted.credential,
|
|
7877
|
+
...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
|
|
7704
7878
|
dispatch: payload,
|
|
7705
7879
|
turnId,
|
|
7706
7880
|
resumed: handleOpts.resumed,
|
|
@@ -7772,7 +7946,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7772
7946
|
locks?.release(lockOwner(event.id));
|
|
7773
7947
|
return;
|
|
7774
7948
|
case "process_group":
|
|
7775
|
-
groups.push(event.pgid);
|
|
7949
|
+
if (!boxed) groups.push(event.pgid);
|
|
7776
7950
|
return;
|
|
7777
7951
|
case "remote_session":
|
|
7778
7952
|
remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
|
|
@@ -7793,7 +7967,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7793
7967
|
}
|
|
7794
7968
|
if (result) return result;
|
|
7795
7969
|
const durationMs = Date.now() - startedAt;
|
|
7796
|
-
const
|
|
7970
|
+
const stopped = controller.signal.aborted && thrown === void 0;
|
|
7971
|
+
const reason = stopped ? "cancelled" : thrown !== void 0 ? `runner_failed: ${thrown}` : `runner_exited: ${code}`;
|
|
7797
7972
|
await child?.reap(groups);
|
|
7798
7973
|
const reaped = await (launch.reapTurn ?? reapTurnScopes)(turnId).catch((err) => ({
|
|
7799
7974
|
failures: [err instanceof Error ? err.message : String(err)]
|
|
@@ -7808,8 +7983,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7808
7983
|
await opts.api.setActiveRun(opts.workspaceId, payload.conversationId, payload.agentId, {
|
|
7809
7984
|
activeRunStartedAt: null,
|
|
7810
7985
|
turnId,
|
|
7811
|
-
outcome: "failed",
|
|
7812
|
-
errorReason: reason.slice(0, ERROR_REASON_MAX),
|
|
7986
|
+
...stopped ? { outcome: "interrupted" } : { outcome: "failed", errorReason: reason.slice(0, ERROR_REASON_MAX) },
|
|
7813
7987
|
readThrough: payload.messageId
|
|
7814
7988
|
});
|
|
7815
7989
|
} catch (err) {
|
|
@@ -7827,11 +8001,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7827
8001
|
reason: "exited"
|
|
7828
8002
|
});
|
|
7829
8003
|
if (thrown !== void 0) throw new Error(thrown);
|
|
7830
|
-
log2.
|
|
8004
|
+
if (stopped) log2.info({ turnId, code }, "runner: stopped before it could report");
|
|
8005
|
+
else log2.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
7831
8006
|
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
7832
8007
|
return { ok: false, durationMs, reason };
|
|
7833
8008
|
}
|
|
7834
|
-
var PINO_LEVELS;
|
|
8009
|
+
var BOXED_ENV_DROPPED, BOXED_ENV_DROPPED_PREFIX, PINO_LEVELS;
|
|
7835
8010
|
var init_launch = __esm({
|
|
7836
8011
|
"src/runner/launch.ts"() {
|
|
7837
8012
|
"use strict";
|
|
@@ -7842,6 +8017,16 @@ var init_launch = __esm({
|
|
|
7842
8017
|
init_boot_id();
|
|
7843
8018
|
init_turn_containment();
|
|
7844
8019
|
init_protocol();
|
|
8020
|
+
BOXED_ENV_DROPPED = [
|
|
8021
|
+
"ANTHROPIC_API_KEY",
|
|
8022
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
8023
|
+
"CODEX_API_KEY",
|
|
8024
|
+
"GH_TOKEN",
|
|
8025
|
+
"GITHUB_TOKEN",
|
|
8026
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
8027
|
+
"XDG_RUNTIME_DIR"
|
|
8028
|
+
];
|
|
8029
|
+
BOXED_ENV_DROPPED_PREFIX = "CABANE_GATE_";
|
|
7845
8030
|
PINO_LEVELS = {
|
|
7846
8031
|
10: "trace",
|
|
7847
8032
|
20: "debug",
|
|
@@ -8456,9 +8641,9 @@ import { z as z16 } from "zod";
|
|
|
8456
8641
|
function secretsPath() {
|
|
8457
8642
|
return join19(cabaneDir(), "secrets.json");
|
|
8458
8643
|
}
|
|
8459
|
-
function
|
|
8644
|
+
function loadSecretValues() {
|
|
8460
8645
|
const path = secretsPath();
|
|
8461
|
-
if (!existsSync10(path)) return
|
|
8646
|
+
if (!existsSync10(path)) return {};
|
|
8462
8647
|
let raw;
|
|
8463
8648
|
try {
|
|
8464
8649
|
raw = readFileSync8(path, "utf8");
|
|
@@ -8467,7 +8652,7 @@ function loadSecretStore() {
|
|
|
8467
8652
|
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
8468
8653
|
);
|
|
8469
8654
|
}
|
|
8470
|
-
if (raw.trim().length === 0) return
|
|
8655
|
+
if (raw.trim().length === 0) return {};
|
|
8471
8656
|
let parsed;
|
|
8472
8657
|
try {
|
|
8473
8658
|
parsed = JSON.parse(raw);
|
|
@@ -8482,14 +8667,17 @@ function loadSecretStore() {
|
|
|
8482
8667
|
`${path} must be a flat object mapping secret names to string values (e.g. { "GITHUB_TOKEN": "ghp_\u2026" }).`
|
|
8483
8668
|
);
|
|
8484
8669
|
}
|
|
8485
|
-
return
|
|
8670
|
+
return result.data;
|
|
8486
8671
|
}
|
|
8487
8672
|
function loadSecretStoreTolerant(onWarn) {
|
|
8673
|
+
return makeStore(loadSecretValuesTolerant(onWarn));
|
|
8674
|
+
}
|
|
8675
|
+
function loadSecretValuesTolerant(onWarn) {
|
|
8488
8676
|
try {
|
|
8489
|
-
return
|
|
8677
|
+
return loadSecretValues();
|
|
8490
8678
|
} catch (err) {
|
|
8491
8679
|
onWarn?.(err instanceof Error ? err.message : String(err));
|
|
8492
|
-
return
|
|
8680
|
+
return {};
|
|
8493
8681
|
}
|
|
8494
8682
|
}
|
|
8495
8683
|
function makeStore(map) {
|
|
@@ -8581,6 +8769,31 @@ var init_secrets = __esm({
|
|
|
8581
8769
|
}
|
|
8582
8770
|
});
|
|
8583
8771
|
|
|
8772
|
+
// src/turn-env.ts
|
|
8773
|
+
function harnessLoginEnv(store, runtime) {
|
|
8774
|
+
const names = [
|
|
8775
|
+
...runtime in API_KEY_VARIABLES ? API_KEY_VARIABLES[runtime] : [],
|
|
8776
|
+
GITHUB_TOKEN_NAME
|
|
8777
|
+
];
|
|
8778
|
+
const env = {};
|
|
8779
|
+
for (const name of names) {
|
|
8780
|
+
const value = store.get(name);
|
|
8781
|
+
if (value) env[name] = value;
|
|
8782
|
+
}
|
|
8783
|
+
return env;
|
|
8784
|
+
}
|
|
8785
|
+
function turnIdentityEnv(ids) {
|
|
8786
|
+
return { CABANE_CONVERSATION_ID: ids.conversationId, CABANE_AGENT_ID: ids.agentId };
|
|
8787
|
+
}
|
|
8788
|
+
var GITHUB_TOKEN_NAME;
|
|
8789
|
+
var init_turn_env = __esm({
|
|
8790
|
+
"src/turn-env.ts"() {
|
|
8791
|
+
"use strict";
|
|
8792
|
+
init_harness_usable();
|
|
8793
|
+
GITHUB_TOKEN_NAME = "GH_TOKEN";
|
|
8794
|
+
}
|
|
8795
|
+
});
|
|
8796
|
+
|
|
8584
8797
|
// src/transcript-writer.ts
|
|
8585
8798
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
8586
8799
|
import { basename as basename3, dirname as dirname7, join as join20 } from "path";
|
|
@@ -8971,6 +9184,7 @@ var init_turn_execution = __esm({
|
|
|
8971
9184
|
init_prepare_failure();
|
|
8972
9185
|
init_prepared();
|
|
8973
9186
|
init_secrets();
|
|
9187
|
+
init_turn_env();
|
|
8974
9188
|
init_turn_control_tools();
|
|
8975
9189
|
init_transcript_writer();
|
|
8976
9190
|
init_turn_containment();
|
|
@@ -9038,6 +9252,7 @@ var init_turn_execution = __esm({
|
|
|
9038
9252
|
// The harness's secret environment for this turn. Only ever handed to the
|
|
9039
9253
|
// adapter; never logged, never merged into `process.env`.
|
|
9040
9254
|
secretEnv = {};
|
|
9255
|
+
secretStore;
|
|
9041
9256
|
effectiveCwd;
|
|
9042
9257
|
hookEnv;
|
|
9043
9258
|
turnEnv;
|
|
@@ -9326,7 +9541,8 @@ var init_turn_execution = __esm({
|
|
|
9326
9541
|
throw this.concluded(reason, reason);
|
|
9327
9542
|
}
|
|
9328
9543
|
this.resolvedMcpServers = resolvedMcpServers;
|
|
9329
|
-
this.secretEnv = secretEnv;
|
|
9544
|
+
this.secretEnv = { ...secretEnv, ...harnessLoginEnv(secretStore, this.turnContext.runtime) };
|
|
9545
|
+
this.secretStore = secretStore;
|
|
9330
9546
|
}
|
|
9331
9547
|
async prepareEnvironment() {
|
|
9332
9548
|
const { payload, workspaceId, turnId, turnLog } = this;
|
|
@@ -9477,6 +9693,10 @@ var init_turn_execution = __esm({
|
|
|
9477
9693
|
}
|
|
9478
9694
|
const turnEnv = {
|
|
9479
9695
|
...hookEnv,
|
|
9696
|
+
...turnIdentityEnv({
|
|
9697
|
+
conversationId: this.payload.conversationId,
|
|
9698
|
+
agentId: this.payload.agentId
|
|
9699
|
+
}),
|
|
9480
9700
|
CABANE_COMPANION_HOME: agentCompanionHome()
|
|
9481
9701
|
};
|
|
9482
9702
|
this.codexLocal = this.turnContext.runtime === "codex" ? { configSetsSandboxMode: await readCodexConfigSetsSandboxMode() } : void 0;
|
|
@@ -10551,8 +10771,14 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
|
|
|
10551
10771
|
agentUsername: spec.agentUsername,
|
|
10552
10772
|
credential: input.credential,
|
|
10553
10773
|
local: spec.local,
|
|
10554
|
-
|
|
10774
|
+
// The place the runner stands in names its own root and transcript
|
|
10775
|
+
// directory when it has them (`PLACE_ENV`); the launcher's host-computed
|
|
10776
|
+
// values are what a runner on the person's own machine gets.
|
|
10777
|
+
workRoot: input.place?.workRoot ?? spec.workRoot,
|
|
10555
10778
|
runConfig: spec.runConfig,
|
|
10779
|
+
// The launcher's copy of the device's secrets, when it sent one: a runner
|
|
10780
|
+
// in a box has no `secrets.json` to read.
|
|
10781
|
+
...input.secrets ? { secretStore: makeStore(input.secrets) } : {},
|
|
10556
10782
|
log: log2,
|
|
10557
10783
|
...queryFn ? { queryFn } : {},
|
|
10558
10784
|
...spec.opencodeServerUrl ? { opencodeServerUrl: spec.opencodeServerUrl } : {},
|
|
@@ -10562,7 +10788,7 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
|
|
|
10562
10788
|
...spec.totalTimeoutMs !== void 0 ? { totalTimeoutMs: spec.totalTimeoutMs } : {},
|
|
10563
10789
|
...spec.leaseRenewalMs !== void 0 ? { leaseRenewalMs: spec.leaseRenewalMs } : {},
|
|
10564
10790
|
...spec.preparingRowDelayMs !== void 0 ? { preparingRowDelayMs: spec.preparingRowDelayMs } : {},
|
|
10565
|
-
...spec.transcripts ? { transcriptDir: transcriptsDir() } : {},
|
|
10791
|
+
...spec.transcripts ? { transcriptDir: input.place?.transcriptDir ?? transcriptsDir() } : {},
|
|
10566
10792
|
connectorHealth,
|
|
10567
10793
|
inRunner: {
|
|
10568
10794
|
bootId: spec.bootId,
|
|
@@ -10697,6 +10923,7 @@ var init_run_turn = __esm({
|
|
|
10697
10923
|
init_exec_shim();
|
|
10698
10924
|
init_launcher_channel();
|
|
10699
10925
|
init_opencode_lock();
|
|
10926
|
+
init_secrets();
|
|
10700
10927
|
init_transcript_writer();
|
|
10701
10928
|
init_turn_execution();
|
|
10702
10929
|
init_protocol();
|
|
@@ -11134,133 +11361,8 @@ function spaced(sentence) {
|
|
|
11134
11361
|
|
|
11135
11362
|
// src/harness-detect.ts
|
|
11136
11363
|
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
|
|
11364
|
+
init_harness_usable();
|
|
11365
|
+
init_harness_versions();
|
|
11264
11366
|
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
11265
11367
|
var DEFAULT_OPENCODE_PROBE_MS = 1e3;
|
|
11266
11368
|
var CONFIGURED_OPENCODE_PROBE_MS = 4e3;
|
|
@@ -12985,6 +13087,10 @@ function runtimeIssuesFor(gates) {
|
|
|
12985
13087
|
return issues;
|
|
12986
13088
|
}
|
|
12987
13089
|
|
|
13090
|
+
// src/supervisor.ts
|
|
13091
|
+
init_harness_versions();
|
|
13092
|
+
init_harness_usable();
|
|
13093
|
+
|
|
12988
13094
|
// src/opencode-models.ts
|
|
12989
13095
|
var OPENCODE_RUNTIME = "opencode";
|
|
12990
13096
|
function mapOpencodeProviders(json) {
|
|
@@ -14473,7 +14579,16 @@ var CompanionSupervisor = class {
|
|
|
14473
14579
|
},
|
|
14474
14580
|
runner: {
|
|
14475
14581
|
provider: this.runnerProvider,
|
|
14476
|
-
command:
|
|
14582
|
+
command: wrappedRunnerCommand(
|
|
14583
|
+
this.config.runner?.command,
|
|
14584
|
+
this.runnerCommand ?? selfRunnerCommand()
|
|
14585
|
+
),
|
|
14586
|
+
...this.config.runner?.command ? {
|
|
14587
|
+
boxed: true,
|
|
14588
|
+
secrets: () => loadSecretValuesTolerant(
|
|
14589
|
+
(m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
|
|
14590
|
+
)
|
|
14591
|
+
} : {},
|
|
14477
14592
|
...this.runnerEnv ? { env: this.runnerEnv } : {},
|
|
14478
14593
|
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
14479
14594
|
connectorHealth: this.connectorHealth,
|
|
@@ -17109,7 +17224,7 @@ init_errors();
|
|
|
17109
17224
|
init_src();
|
|
17110
17225
|
init_transcript_writer();
|
|
17111
17226
|
import { existsSync as existsSync19, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "fs";
|
|
17112
|
-
import { isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
17227
|
+
import { basename as basename4, isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
17113
17228
|
async function transcript(opts = {}) {
|
|
17114
17229
|
const dir2 = transcriptsDir();
|
|
17115
17230
|
if (opts.follow) {
|
|
@@ -17232,13 +17347,28 @@ async function followTranscripts(dir2) {
|
|
|
17232
17347
|
follower.tick();
|
|
17233
17348
|
});
|
|
17234
17349
|
}
|
|
17235
|
-
function
|
|
17350
|
+
function listTranscriptFiles(dir2) {
|
|
17351
|
+
let entries;
|
|
17236
17352
|
try {
|
|
17237
|
-
|
|
17353
|
+
entries = readdirSync6(dir2, { withFileTypes: true });
|
|
17238
17354
|
} catch {
|
|
17239
17355
|
return [];
|
|
17240
17356
|
}
|
|
17357
|
+
const files = [];
|
|
17358
|
+
for (const entry of entries) {
|
|
17359
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(entry.name);
|
|
17360
|
+
if (!entry.isDirectory() || entry.name === "anomalies") continue;
|
|
17361
|
+
let inner;
|
|
17362
|
+
try {
|
|
17363
|
+
inner = readdirSync6(join31(dir2, entry.name));
|
|
17364
|
+
} catch {
|
|
17365
|
+
continue;
|
|
17366
|
+
}
|
|
17367
|
+
for (const name of inner) if (name.endsWith(".jsonl")) files.push(`${entry.name}/${name}`);
|
|
17368
|
+
}
|
|
17369
|
+
return files.sort((a, b) => basename4(a) < basename4(b) ? 1 : basename4(a) > basename4(b) ? -1 : 0);
|
|
17241
17370
|
}
|
|
17371
|
+
var listFiles = listTranscriptFiles;
|
|
17242
17372
|
function printList(dir2) {
|
|
17243
17373
|
const files = listFiles(dir2);
|
|
17244
17374
|
if (files.length === 0) {
|
|
@@ -17285,6 +17415,7 @@ function peek(path) {
|
|
|
17285
17415
|
function resolveTarget(dir2, target) {
|
|
17286
17416
|
if (isAbsolute2(target) || target.includes("/")) {
|
|
17287
17417
|
if (existsSync19(target)) return target;
|
|
17418
|
+
if (!isAbsolute2(target) && existsSync19(join31(dir2, target))) return join31(dir2, target);
|
|
17288
17419
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
17289
17420
|
}
|
|
17290
17421
|
const exact = join31(dir2, target);
|
package/dist/launch.js
CHANGED
|
@@ -196,7 +196,15 @@ var companionConfigSchema = z2.object({
|
|
|
196
196
|
// and an agent with no folder of its own and no prepare hook works there
|
|
197
197
|
// (`turn-execution.ts`, `prepareEnvironment`). Default `~/.cabane/work`: hidden, so pairing a
|
|
198
198
|
// laptop creates no new visible folder; set it to make the work visible.
|
|
199
|
-
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional()
|
|
199
|
+
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
200
|
+
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
201
|
+
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
202
|
+
// is the operator's (a box of their own making, ENG225); the companion knows nothing of it
|
|
203
|
+
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
204
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
|
|
205
|
+
runner: z2.object({
|
|
206
|
+
command: z2.array(z2.string().min(1)).min(1)
|
|
207
|
+
}).strict().optional()
|
|
200
208
|
});
|
|
201
209
|
var legacyOfferSchema = z2.object({
|
|
202
210
|
claudeCode: z2.object({ enabled: z2.boolean().optional() }).passthrough().optional(),
|
package/dist/pairing-config.js
CHANGED
|
@@ -221,7 +221,15 @@ var companionConfigSchema = z2.object({
|
|
|
221
221
|
// and an agent with no folder of its own and no prepare hook works there
|
|
222
222
|
// (`turn-execution.ts`, `prepareEnvironment`). Default `~/.cabane/work`: hidden, so pairing a
|
|
223
223
|
// laptop creates no new visible folder; set it to make the work visible.
|
|
224
|
-
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional()
|
|
224
|
+
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
225
|
+
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
226
|
+
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
227
|
+
// is the operator's (a box of their own making, ENG225); the companion knows nothing of it
|
|
228
|
+
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
229
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
|
|
230
|
+
runner: z2.object({
|
|
231
|
+
command: z2.array(z2.string().min(1)).min(1)
|
|
232
|
+
}).strict().optional()
|
|
225
233
|
});
|
|
226
234
|
function retiredConfigKeys(raw) {
|
|
227
235
|
if (!raw || typeof raw !== "object") return [];
|
package/dist/runtime.js
CHANGED
|
@@ -295,7 +295,15 @@ var companionConfigSchema = z2.object({
|
|
|
295
295
|
// and an agent with no folder of its own and no prepare hook works there
|
|
296
296
|
// (`turn-execution.ts`, `prepareEnvironment`). Default `~/.cabane/work`: hidden, so pairing a
|
|
297
297
|
// laptop creates no new visible folder; set it to make the work visible.
|
|
298
|
-
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional()
|
|
298
|
+
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
299
|
+
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
300
|
+
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
301
|
+
// is the operator's (a box of their own making, ENG225); the companion knows nothing of it
|
|
302
|
+
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
303
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
|
|
304
|
+
runner: z2.object({
|
|
305
|
+
command: z2.array(z2.string().min(1)).min(1)
|
|
306
|
+
}).strict().optional()
|
|
299
307
|
});
|
|
300
308
|
function workRoot(cfg) {
|
|
301
309
|
const root = cfg.root;
|
|
@@ -7445,7 +7453,8 @@ var dispatchSchema = z14.object({
|
|
|
7445
7453
|
episode: z14.number().int().optional()
|
|
7446
7454
|
});
|
|
7447
7455
|
var credentialsSchema = z14.object({
|
|
7448
|
-
credential: z14.string().startsWith("cabrn_")
|
|
7456
|
+
credential: z14.string().startsWith("cabrn_"),
|
|
7457
|
+
secrets: z14.record(z14.string(), z14.string()).optional()
|
|
7449
7458
|
});
|
|
7450
7459
|
function runnerEnv(input) {
|
|
7451
7460
|
return {
|
|
@@ -7457,7 +7466,10 @@ function runnerEnv(input) {
|
|
|
7457
7466
|
};
|
|
7458
7467
|
}
|
|
7459
7468
|
function runnerStdin(input) {
|
|
7460
|
-
return `${JSON.stringify({
|
|
7469
|
+
return `${JSON.stringify({
|
|
7470
|
+
credential: input.credential,
|
|
7471
|
+
...input.secrets ? { secrets: input.secrets } : {}
|
|
7472
|
+
})}
|
|
7461
7473
|
`;
|
|
7462
7474
|
}
|
|
7463
7475
|
function encodeLauncherMessage(message) {
|
|
@@ -7482,6 +7494,29 @@ function selfRunnerCommand() {
|
|
|
7482
7494
|
if (!script) throw new Error("runner: cannot tell which script started this companion");
|
|
7483
7495
|
return [process.execPath, ...process.execArgv, script, "run-turn"];
|
|
7484
7496
|
}
|
|
7497
|
+
function wrappedRunnerCommand(prefix, own) {
|
|
7498
|
+
return [...prefix ?? [], ...own];
|
|
7499
|
+
}
|
|
7500
|
+
var BOXED_ENV_DROPPED = [
|
|
7501
|
+
"ANTHROPIC_API_KEY",
|
|
7502
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
7503
|
+
"CODEX_API_KEY",
|
|
7504
|
+
"GH_TOKEN",
|
|
7505
|
+
"GITHUB_TOKEN",
|
|
7506
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
7507
|
+
"XDG_RUNTIME_DIR"
|
|
7508
|
+
];
|
|
7509
|
+
var BOXED_ENV_DROPPED_PREFIX = "CABANE_GATE_";
|
|
7510
|
+
function runnerExecEnv(base, boxed) {
|
|
7511
|
+
const env = {};
|
|
7512
|
+
for (const [k, v] of Object.entries(base)) {
|
|
7513
|
+
if (v === void 0) continue;
|
|
7514
|
+
if (boxed && (BOXED_ENV_DROPPED.includes(k) || k.startsWith(BOXED_ENV_DROPPED_PREFIX)))
|
|
7515
|
+
continue;
|
|
7516
|
+
env[k] = v;
|
|
7517
|
+
}
|
|
7518
|
+
return env;
|
|
7519
|
+
}
|
|
7485
7520
|
function healthSnapshot(store) {
|
|
7486
7521
|
const out = {};
|
|
7487
7522
|
for (const r of store?.reports() ?? []) {
|
|
@@ -7686,13 +7721,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7686
7721
|
return { ok: false, durationMs: durationMs2, reason: reason2 };
|
|
7687
7722
|
}
|
|
7688
7723
|
try {
|
|
7689
|
-
const
|
|
7690
|
-
|
|
7691
|
-
if (v !== void 0) env[k] = v;
|
|
7692
|
-
}
|
|
7724
|
+
const boxed = launch.boxed === true;
|
|
7725
|
+
const env = runnerExecEnv(launch.env ?? process.env, boxed);
|
|
7693
7726
|
const input = {
|
|
7694
7727
|
baseUrl: opts.baseUrl,
|
|
7695
7728
|
credential: minted.credential,
|
|
7729
|
+
...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
|
|
7696
7730
|
dispatch: payload,
|
|
7697
7731
|
turnId,
|
|
7698
7732
|
resumed: handleOpts.resumed,
|
|
@@ -7764,7 +7798,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7764
7798
|
locks?.release(lockOwner(event.id));
|
|
7765
7799
|
return;
|
|
7766
7800
|
case "process_group":
|
|
7767
|
-
groups.push(event.pgid);
|
|
7801
|
+
if (!boxed) groups.push(event.pgid);
|
|
7768
7802
|
return;
|
|
7769
7803
|
case "remote_session":
|
|
7770
7804
|
remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
|
|
@@ -7785,7 +7819,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7785
7819
|
}
|
|
7786
7820
|
if (result) return result;
|
|
7787
7821
|
const durationMs = Date.now() - startedAt;
|
|
7788
|
-
const
|
|
7822
|
+
const stopped = controller.signal.aborted && thrown === void 0;
|
|
7823
|
+
const reason = stopped ? "cancelled" : thrown !== void 0 ? `runner_failed: ${thrown}` : `runner_exited: ${code}`;
|
|
7789
7824
|
await child?.reap(groups);
|
|
7790
7825
|
const reaped = await (launch.reapTurn ?? reapTurnScopes)(turnId).catch((err) => ({
|
|
7791
7826
|
failures: [err instanceof Error ? err.message : String(err)]
|
|
@@ -7800,8 +7835,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7800
7835
|
await opts.api.setActiveRun(opts.workspaceId, payload.conversationId, payload.agentId, {
|
|
7801
7836
|
activeRunStartedAt: null,
|
|
7802
7837
|
turnId,
|
|
7803
|
-
outcome: "failed",
|
|
7804
|
-
errorReason: reason.slice(0, ERROR_REASON_MAX),
|
|
7838
|
+
...stopped ? { outcome: "interrupted" } : { outcome: "failed", errorReason: reason.slice(0, ERROR_REASON_MAX) },
|
|
7805
7839
|
readThrough: payload.messageId
|
|
7806
7840
|
});
|
|
7807
7841
|
} catch (err) {
|
|
@@ -7819,7 +7853,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7819
7853
|
reason: "exited"
|
|
7820
7854
|
});
|
|
7821
7855
|
if (thrown !== void 0) throw new Error(thrown);
|
|
7822
|
-
log.
|
|
7856
|
+
if (stopped) log.info({ turnId, code }, "runner: stopped before it could report");
|
|
7857
|
+
else log.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
7823
7858
|
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
7824
7859
|
return { ok: false, durationMs, reason };
|
|
7825
7860
|
}
|
|
@@ -7881,9 +7916,9 @@ function secretsPath() {
|
|
|
7881
7916
|
return join18(cabaneDir(), "secrets.json");
|
|
7882
7917
|
}
|
|
7883
7918
|
var secretStoreSchema = z16.record(z16.string(), z16.string());
|
|
7884
|
-
function
|
|
7919
|
+
function loadSecretValues() {
|
|
7885
7920
|
const path = secretsPath();
|
|
7886
|
-
if (!existsSync10(path)) return
|
|
7921
|
+
if (!existsSync10(path)) return {};
|
|
7887
7922
|
let raw;
|
|
7888
7923
|
try {
|
|
7889
7924
|
raw = readFileSync8(path, "utf8");
|
|
@@ -7892,7 +7927,7 @@ function loadSecretStore() {
|
|
|
7892
7927
|
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
7893
7928
|
);
|
|
7894
7929
|
}
|
|
7895
|
-
if (raw.trim().length === 0) return
|
|
7930
|
+
if (raw.trim().length === 0) return {};
|
|
7896
7931
|
let parsed;
|
|
7897
7932
|
try {
|
|
7898
7933
|
parsed = JSON.parse(raw);
|
|
@@ -7907,14 +7942,17 @@ function loadSecretStore() {
|
|
|
7907
7942
|
`${path} must be a flat object mapping secret names to string values (e.g. { "GITHUB_TOKEN": "ghp_\u2026" }).`
|
|
7908
7943
|
);
|
|
7909
7944
|
}
|
|
7910
|
-
return
|
|
7945
|
+
return result.data;
|
|
7911
7946
|
}
|
|
7912
7947
|
function loadSecretStoreTolerant(onWarn) {
|
|
7948
|
+
return makeStore(loadSecretValuesTolerant(onWarn));
|
|
7949
|
+
}
|
|
7950
|
+
function loadSecretValuesTolerant(onWarn) {
|
|
7913
7951
|
try {
|
|
7914
|
-
return
|
|
7952
|
+
return loadSecretValues();
|
|
7915
7953
|
} catch (err) {
|
|
7916
7954
|
onWarn?.(err instanceof Error ? err.message : String(err));
|
|
7917
|
-
return
|
|
7955
|
+
return {};
|
|
7918
7956
|
}
|
|
7919
7957
|
}
|
|
7920
7958
|
function makeStore(map) {
|
|
@@ -7939,6 +7977,130 @@ function requiredSecretNames(mcpServers) {
|
|
|
7939
7977
|
return [...names].sort();
|
|
7940
7978
|
}
|
|
7941
7979
|
|
|
7980
|
+
// src/harness-usable.ts
|
|
7981
|
+
import { spawn as spawn4 } from "child_process";
|
|
7982
|
+
|
|
7983
|
+
// src/harness-versions.ts
|
|
7984
|
+
var EMPTY = { claudeCode: null, opencode: null, codex: null };
|
|
7985
|
+
function parseVersionToken(raw) {
|
|
7986
|
+
if (!raw) return null;
|
|
7987
|
+
const m = raw.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/);
|
|
7988
|
+
return m ? m[0] : null;
|
|
7989
|
+
}
|
|
7990
|
+
async function probeOpencodeVersion(serverUrl, fetchImpl = fetch) {
|
|
7991
|
+
try {
|
|
7992
|
+
const base = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl;
|
|
7993
|
+
const res = await fetchImpl(`${base}/global/health`, {
|
|
7994
|
+
headers: { accept: "application/json" }
|
|
7995
|
+
});
|
|
7996
|
+
if (!res.ok) return null;
|
|
7997
|
+
return extractOpencodeVersion(await res.json());
|
|
7998
|
+
} catch {
|
|
7999
|
+
return null;
|
|
8000
|
+
}
|
|
8001
|
+
}
|
|
8002
|
+
function extractOpencodeVersion(json) {
|
|
8003
|
+
if (!json || typeof json !== "object") return null;
|
|
8004
|
+
const obj = json;
|
|
8005
|
+
const candidates = [obj.version];
|
|
8006
|
+
for (const v of Object.values(obj)) {
|
|
8007
|
+
if (v && typeof v === "object") candidates.push(v.version);
|
|
8008
|
+
}
|
|
8009
|
+
for (const c of candidates) {
|
|
8010
|
+
if (typeof c === "string") {
|
|
8011
|
+
const parsed = parseVersionToken(c);
|
|
8012
|
+
if (parsed) return parsed;
|
|
8013
|
+
}
|
|
8014
|
+
}
|
|
8015
|
+
return null;
|
|
8016
|
+
}
|
|
8017
|
+
function emptyHarnessVersions() {
|
|
8018
|
+
return { ...EMPTY };
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
// src/harness-usable.ts
|
|
8022
|
+
var API_KEY_VARIABLES = {
|
|
8023
|
+
"claude-code": ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
8024
|
+
codex: ["CODEX_API_KEY"]
|
|
8025
|
+
};
|
|
8026
|
+
function apiKeyVariable(runtime, env = process.env) {
|
|
8027
|
+
return API_KEY_VARIABLES[runtime].find((name) => !!env[name]?.trim()) ?? null;
|
|
8028
|
+
}
|
|
8029
|
+
var CHECK_TIMEOUT_MS = 5e3;
|
|
8030
|
+
async function checkHarnessUsable(runtime, deps = {}) {
|
|
8031
|
+
const env = deps.env ?? process.env;
|
|
8032
|
+
const resolveBundled = deps.resolveBundled ?? ((r) => resolveBundledBinary(r));
|
|
8033
|
+
const run = deps.run ?? runBounded;
|
|
8034
|
+
const resolution = resolveBundled(runtime);
|
|
8035
|
+
if (resolution.status !== "present" || !resolution.path) {
|
|
8036
|
+
return {
|
|
8037
|
+
status: "bundle_missing",
|
|
8038
|
+
bundled: resolution.status === "present" ? "executable_missing" : resolution.status
|
|
8039
|
+
};
|
|
8040
|
+
}
|
|
8041
|
+
const path = resolution.path;
|
|
8042
|
+
const version = run(path, ["--version"], env).then(
|
|
8043
|
+
(r) => r.code === 0 ? parseVersionToken(r.output) : null
|
|
8044
|
+
);
|
|
8045
|
+
const variable = apiKeyVariable(runtime, env);
|
|
8046
|
+
if (variable) return { status: "api_key", variable, version: await version };
|
|
8047
|
+
const statusArgs = runtime === "codex" ? ["login", "status"] : ["auth", "status", "--json"];
|
|
8048
|
+
const [answer, v] = await Promise.all([run(path, statusArgs, env), version]);
|
|
8049
|
+
if (answer.error) return { status: "check_failed", reason: answer.error };
|
|
8050
|
+
const signedIn = runtime === "codex" ? readCodexStatus(answer) : readClaudeStatus(answer);
|
|
8051
|
+
if (typeof signedIn === "string") return { status: "check_failed", reason: signedIn };
|
|
8052
|
+
return signedIn ? { status: "signed_in", version: v } : { status: "signed_out", version: v };
|
|
8053
|
+
}
|
|
8054
|
+
function readClaudeStatus(answer) {
|
|
8055
|
+
const start = answer.output.indexOf("{");
|
|
8056
|
+
const end = answer.output.lastIndexOf("}");
|
|
8057
|
+
if (start < 0 || end < start) return "unreadable";
|
|
8058
|
+
try {
|
|
8059
|
+
const parsed = JSON.parse(answer.output.slice(start, end + 1));
|
|
8060
|
+
const loggedIn = parsed?.loggedIn;
|
|
8061
|
+
return typeof loggedIn === "boolean" ? loggedIn : "unreadable";
|
|
8062
|
+
} catch {
|
|
8063
|
+
return "unreadable";
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
function readCodexStatus(answer) {
|
|
8067
|
+
if (answer.code === 0) return true;
|
|
8068
|
+
if (/not logged in/i.test(answer.output)) return false;
|
|
8069
|
+
if (/config/i.test(answer.output)) return "config";
|
|
8070
|
+
return "unreadable";
|
|
8071
|
+
}
|
|
8072
|
+
function runBounded(path, args, env) {
|
|
8073
|
+
return new Promise((resolve) => {
|
|
8074
|
+
let settled = false;
|
|
8075
|
+
let out = "";
|
|
8076
|
+
const done = (result) => {
|
|
8077
|
+
if (settled) return;
|
|
8078
|
+
settled = true;
|
|
8079
|
+
clearTimeout(timer);
|
|
8080
|
+
resolve(result);
|
|
8081
|
+
};
|
|
8082
|
+
let child;
|
|
8083
|
+
try {
|
|
8084
|
+
child = spawn4(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
|
|
8085
|
+
} catch {
|
|
8086
|
+
resolve({ code: null, output: "", error: "spawn" });
|
|
8087
|
+
return;
|
|
8088
|
+
}
|
|
8089
|
+
const capture = (chunk) => {
|
|
8090
|
+
if (out.length < 8192) out += chunk.toString();
|
|
8091
|
+
};
|
|
8092
|
+
child.stdout?.on("data", capture);
|
|
8093
|
+
child.stderr?.on("data", capture);
|
|
8094
|
+
const timer = setTimeout(() => {
|
|
8095
|
+
child.kill("SIGKILL");
|
|
8096
|
+
done({ code: null, output: out, error: "timeout" });
|
|
8097
|
+
}, CHECK_TIMEOUT_MS);
|
|
8098
|
+
timer.unref?.();
|
|
8099
|
+
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
8100
|
+
child.once("close", (code) => done({ code, output: out }));
|
|
8101
|
+
});
|
|
8102
|
+
}
|
|
8103
|
+
|
|
7942
8104
|
// src/transcript-writer.ts
|
|
7943
8105
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
7944
8106
|
import { basename as basename3, dirname as dirname7, join as join19 } from "path";
|
|
@@ -8083,7 +8245,7 @@ function hostReport() {
|
|
|
8083
8245
|
}
|
|
8084
8246
|
|
|
8085
8247
|
// src/providers/local-process.ts
|
|
8086
|
-
import { spawn as
|
|
8248
|
+
import { spawn as spawn5 } from "child_process";
|
|
8087
8249
|
import { constants as constants2 } from "os";
|
|
8088
8250
|
var DEFAULT_KILL_GRACE_MS = 3e4;
|
|
8089
8251
|
var DEFAULT_REAP_GRACE_MS = 3e3;
|
|
@@ -8094,7 +8256,7 @@ function exitCode(code, signal) {
|
|
|
8094
8256
|
}
|
|
8095
8257
|
function createLocalProcessProvider(opts = {}) {
|
|
8096
8258
|
const killGraceMs = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
8097
|
-
const spawnFn = opts.spawnFn ??
|
|
8259
|
+
const spawnFn = opts.spawnFn ?? spawn5;
|
|
8098
8260
|
const reapGraceMs = opts.reapGraceMs ?? DEFAULT_REAP_GRACE_MS;
|
|
8099
8261
|
const killFn = opts.killFn ?? ((pid, signal) => process.kill(pid, signal));
|
|
8100
8262
|
const groupAlive = (pgid) => {
|
|
@@ -8296,44 +8458,6 @@ function checkReason(check) {
|
|
|
8296
8458
|
}
|
|
8297
8459
|
}
|
|
8298
8460
|
|
|
8299
|
-
// src/harness-versions.ts
|
|
8300
|
-
var EMPTY = { claudeCode: null, opencode: null, codex: null };
|
|
8301
|
-
function parseVersionToken(raw) {
|
|
8302
|
-
if (!raw) return null;
|
|
8303
|
-
const m = raw.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/);
|
|
8304
|
-
return m ? m[0] : null;
|
|
8305
|
-
}
|
|
8306
|
-
async function probeOpencodeVersion(serverUrl, fetchImpl = fetch) {
|
|
8307
|
-
try {
|
|
8308
|
-
const base = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl;
|
|
8309
|
-
const res = await fetchImpl(`${base}/global/health`, {
|
|
8310
|
-
headers: { accept: "application/json" }
|
|
8311
|
-
});
|
|
8312
|
-
if (!res.ok) return null;
|
|
8313
|
-
return extractOpencodeVersion(await res.json());
|
|
8314
|
-
} catch {
|
|
8315
|
-
return null;
|
|
8316
|
-
}
|
|
8317
|
-
}
|
|
8318
|
-
function extractOpencodeVersion(json) {
|
|
8319
|
-
if (!json || typeof json !== "object") return null;
|
|
8320
|
-
const obj = json;
|
|
8321
|
-
const candidates = [obj.version];
|
|
8322
|
-
for (const v of Object.values(obj)) {
|
|
8323
|
-
if (v && typeof v === "object") candidates.push(v.version);
|
|
8324
|
-
}
|
|
8325
|
-
for (const c of candidates) {
|
|
8326
|
-
if (typeof c === "string") {
|
|
8327
|
-
const parsed = parseVersionToken(c);
|
|
8328
|
-
if (parsed) return parsed;
|
|
8329
|
-
}
|
|
8330
|
-
}
|
|
8331
|
-
return null;
|
|
8332
|
-
}
|
|
8333
|
-
function emptyHarnessVersions() {
|
|
8334
|
-
return { ...EMPTY };
|
|
8335
|
-
}
|
|
8336
|
-
|
|
8337
8461
|
// src/harness-copy.ts
|
|
8338
8462
|
import { homedir as homedir8 } from "os";
|
|
8339
8463
|
import { join as join21 } from "path";
|
|
@@ -8369,90 +8493,6 @@ function spaced(sentence) {
|
|
|
8369
8493
|
return sentence ? ` ${sentence}` : "";
|
|
8370
8494
|
}
|
|
8371
8495
|
|
|
8372
|
-
// src/harness-usable.ts
|
|
8373
|
-
import { spawn as spawn5 } from "child_process";
|
|
8374
|
-
var API_KEY_VARIABLES = {
|
|
8375
|
-
"claude-code": ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
8376
|
-
codex: ["CODEX_API_KEY"]
|
|
8377
|
-
};
|
|
8378
|
-
function apiKeyVariable(runtime, env = process.env) {
|
|
8379
|
-
return API_KEY_VARIABLES[runtime].find((name) => !!env[name]?.trim()) ?? null;
|
|
8380
|
-
}
|
|
8381
|
-
var CHECK_TIMEOUT_MS = 5e3;
|
|
8382
|
-
async function checkHarnessUsable(runtime, deps = {}) {
|
|
8383
|
-
const env = deps.env ?? process.env;
|
|
8384
|
-
const resolveBundled = deps.resolveBundled ?? ((r) => resolveBundledBinary(r));
|
|
8385
|
-
const run = deps.run ?? runBounded;
|
|
8386
|
-
const resolution = resolveBundled(runtime);
|
|
8387
|
-
if (resolution.status !== "present" || !resolution.path) {
|
|
8388
|
-
return {
|
|
8389
|
-
status: "bundle_missing",
|
|
8390
|
-
bundled: resolution.status === "present" ? "executable_missing" : resolution.status
|
|
8391
|
-
};
|
|
8392
|
-
}
|
|
8393
|
-
const path = resolution.path;
|
|
8394
|
-
const version = run(path, ["--version"], env).then(
|
|
8395
|
-
(r) => r.code === 0 ? parseVersionToken(r.output) : null
|
|
8396
|
-
);
|
|
8397
|
-
const variable = apiKeyVariable(runtime, env);
|
|
8398
|
-
if (variable) return { status: "api_key", variable, version: await version };
|
|
8399
|
-
const statusArgs = runtime === "codex" ? ["login", "status"] : ["auth", "status", "--json"];
|
|
8400
|
-
const [answer, v] = await Promise.all([run(path, statusArgs, env), version]);
|
|
8401
|
-
if (answer.error) return { status: "check_failed", reason: answer.error };
|
|
8402
|
-
const signedIn = runtime === "codex" ? readCodexStatus(answer) : readClaudeStatus(answer);
|
|
8403
|
-
if (typeof signedIn === "string") return { status: "check_failed", reason: signedIn };
|
|
8404
|
-
return signedIn ? { status: "signed_in", version: v } : { status: "signed_out", version: v };
|
|
8405
|
-
}
|
|
8406
|
-
function readClaudeStatus(answer) {
|
|
8407
|
-
const start = answer.output.indexOf("{");
|
|
8408
|
-
const end = answer.output.lastIndexOf("}");
|
|
8409
|
-
if (start < 0 || end < start) return "unreadable";
|
|
8410
|
-
try {
|
|
8411
|
-
const parsed = JSON.parse(answer.output.slice(start, end + 1));
|
|
8412
|
-
const loggedIn = parsed?.loggedIn;
|
|
8413
|
-
return typeof loggedIn === "boolean" ? loggedIn : "unreadable";
|
|
8414
|
-
} catch {
|
|
8415
|
-
return "unreadable";
|
|
8416
|
-
}
|
|
8417
|
-
}
|
|
8418
|
-
function readCodexStatus(answer) {
|
|
8419
|
-
if (answer.code === 0) return true;
|
|
8420
|
-
if (/not logged in/i.test(answer.output)) return false;
|
|
8421
|
-
if (/config/i.test(answer.output)) return "config";
|
|
8422
|
-
return "unreadable";
|
|
8423
|
-
}
|
|
8424
|
-
function runBounded(path, args, env) {
|
|
8425
|
-
return new Promise((resolve) => {
|
|
8426
|
-
let settled = false;
|
|
8427
|
-
let out = "";
|
|
8428
|
-
const done = (result) => {
|
|
8429
|
-
if (settled) return;
|
|
8430
|
-
settled = true;
|
|
8431
|
-
clearTimeout(timer);
|
|
8432
|
-
resolve(result);
|
|
8433
|
-
};
|
|
8434
|
-
let child;
|
|
8435
|
-
try {
|
|
8436
|
-
child = spawn5(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
|
|
8437
|
-
} catch {
|
|
8438
|
-
resolve({ code: null, output: "", error: "spawn" });
|
|
8439
|
-
return;
|
|
8440
|
-
}
|
|
8441
|
-
const capture = (chunk) => {
|
|
8442
|
-
if (out.length < 8192) out += chunk.toString();
|
|
8443
|
-
};
|
|
8444
|
-
child.stdout?.on("data", capture);
|
|
8445
|
-
child.stderr?.on("data", capture);
|
|
8446
|
-
const timer = setTimeout(() => {
|
|
8447
|
-
child.kill("SIGKILL");
|
|
8448
|
-
done({ code: null, output: out, error: "timeout" });
|
|
8449
|
-
}, CHECK_TIMEOUT_MS);
|
|
8450
|
-
timer.unref?.();
|
|
8451
|
-
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
8452
|
-
child.once("close", (code) => done({ code, output: out }));
|
|
8453
|
-
});
|
|
8454
|
-
}
|
|
8455
|
-
|
|
8456
8496
|
// src/harness-detect.ts
|
|
8457
8497
|
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
8458
8498
|
var DEFAULT_OPENCODE_PROBE_MS = 1e3;
|
|
@@ -10254,7 +10294,16 @@ var CompanionSupervisor = class {
|
|
|
10254
10294
|
},
|
|
10255
10295
|
runner: {
|
|
10256
10296
|
provider: this.runnerProvider,
|
|
10257
|
-
command:
|
|
10297
|
+
command: wrappedRunnerCommand(
|
|
10298
|
+
this.config.runner?.command,
|
|
10299
|
+
this.runnerCommand ?? selfRunnerCommand()
|
|
10300
|
+
),
|
|
10301
|
+
...this.config.runner?.command ? {
|
|
10302
|
+
boxed: true,
|
|
10303
|
+
secrets: () => loadSecretValuesTolerant(
|
|
10304
|
+
(m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
|
|
10305
|
+
)
|
|
10306
|
+
} : {},
|
|
10258
10307
|
...this.runnerEnv ? { env: this.runnerEnv } : {},
|
|
10259
10308
|
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
10260
10309
|
connectorHealth: this.connectorHealth,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.189",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
|
|
6
6
|
"license": "UNLICENSED",
|