@cabane/companion 0.6.187 → 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 +489 -243
- package/dist/launch.js +9 -1
- package/dist/pairing-config.js +9 -1
- package/dist/runtime.js +264 -188
- 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;
|
|
@@ -12420,14 +12522,19 @@ var DeviceApi = class {
|
|
|
12420
12522
|
return parsed;
|
|
12421
12523
|
}
|
|
12422
12524
|
// Pull the agents this device should run, across all the owner's workspaces.
|
|
12423
|
-
getAssignments() {
|
|
12424
|
-
return this.request("GET", "/api/companion/assignments");
|
|
12525
|
+
getAssignments(signal) {
|
|
12526
|
+
return this.request("GET", "/api/companion/assignments", void 0, requestSignal(signal));
|
|
12425
12527
|
}
|
|
12426
12528
|
// Ask again for the credential of an agent assigned to this device, when the saved copy is
|
|
12427
12529
|
// gone. The previous credential stops working; any agent not assigned here is a 404, and a
|
|
12428
12530
|
// second ask inside a minute a 429.
|
|
12429
|
-
reissueAgentCredential(agentId) {
|
|
12430
|
-
return this.request(
|
|
12531
|
+
reissueAgentCredential(agentId, signal) {
|
|
12532
|
+
return this.request(
|
|
12533
|
+
"POST",
|
|
12534
|
+
`/api/companion/agents/${encodeURIComponent(agentId)}/credential`,
|
|
12535
|
+
void 0,
|
|
12536
|
+
requestSignal(signal, 3e4)
|
|
12537
|
+
);
|
|
12431
12538
|
}
|
|
12432
12539
|
getRuns(cursor) {
|
|
12433
12540
|
const query = new URLSearchParams({ limit: "100", order: "asc" });
|
|
@@ -12467,6 +12574,10 @@ function errorMessage2(status2, body) {
|
|
|
12467
12574
|
if (typeof body === "string" && body.length > 0) return `${status2} ${body.slice(0, 200)}`;
|
|
12468
12575
|
return `${status2} error`;
|
|
12469
12576
|
}
|
|
12577
|
+
function requestSignal(signal, timeoutMs = 5e3) {
|
|
12578
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
12579
|
+
return signal ? AbortSignal.any([signal, deadline]) : deadline;
|
|
12580
|
+
}
|
|
12470
12581
|
|
|
12471
12582
|
// src/supervisor.ts
|
|
12472
12583
|
init_config();
|
|
@@ -12976,6 +13087,10 @@ function runtimeIssuesFor(gates) {
|
|
|
12976
13087
|
return issues;
|
|
12977
13088
|
}
|
|
12978
13089
|
|
|
13090
|
+
// src/supervisor.ts
|
|
13091
|
+
init_harness_versions();
|
|
13092
|
+
init_harness_usable();
|
|
13093
|
+
|
|
12979
13094
|
// src/opencode-models.ts
|
|
12980
13095
|
var OPENCODE_RUNTIME = "opencode";
|
|
12981
13096
|
function mapOpencodeProviders(json) {
|
|
@@ -13842,6 +13957,7 @@ var CompanionSupervisor = class {
|
|
|
13842
13957
|
pollTimer = null;
|
|
13843
13958
|
refreshing = false;
|
|
13844
13959
|
stopped = false;
|
|
13960
|
+
reconciliationAbort = new AbortController();
|
|
13845
13961
|
// The version-skew warning is logged once, not every heartbeat.
|
|
13846
13962
|
versionSkewWarned = false;
|
|
13847
13963
|
// This device's id, captured from the heartbeat / assignments response. The SSE
|
|
@@ -13887,6 +14003,7 @@ var CompanionSupervisor = class {
|
|
|
13887
14003
|
// heartbeat + poll loops. A companion with no device token (logged out) does
|
|
13888
14004
|
// nothing but say so.
|
|
13889
14005
|
async start() {
|
|
14006
|
+
if (this.stopped) return;
|
|
13890
14007
|
this.log.info(
|
|
13891
14008
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
13892
14009
|
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
|
|
@@ -13971,6 +14088,7 @@ var CompanionSupervisor = class {
|
|
|
13971
14088
|
});
|
|
13972
14089
|
this.updater.start();
|
|
13973
14090
|
await this.refreshAssignments();
|
|
14091
|
+
if (this.stopped) return;
|
|
13974
14092
|
this.deviceSub.start();
|
|
13975
14093
|
this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
13976
14094
|
this.heartbeatTimer.unref?.();
|
|
@@ -14083,10 +14201,12 @@ var CompanionSupervisor = class {
|
|
|
14083
14201
|
let items;
|
|
14084
14202
|
let device;
|
|
14085
14203
|
try {
|
|
14086
|
-
const resp = await this.deviceApi.getAssignments();
|
|
14204
|
+
const resp = await this.deviceApi.getAssignments(this.reconciliationAbort.signal);
|
|
14205
|
+
if (this.stopped) return;
|
|
14087
14206
|
items = resp.assignments;
|
|
14088
14207
|
device = resp.device;
|
|
14089
14208
|
} catch (err) {
|
|
14209
|
+
if (this.stopped) return;
|
|
14090
14210
|
this.log.error(
|
|
14091
14211
|
{ ...apiErrorLogFields(err) },
|
|
14092
14212
|
"Couldn't load the assigned agents. Check that this device is active in Cabane; trying again shortly."
|
|
@@ -14105,6 +14225,7 @@ var CompanionSupervisor = class {
|
|
|
14105
14225
|
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
14106
14226
|
}
|
|
14107
14227
|
items = await this.restoreMissingCredentials(device.id, items);
|
|
14228
|
+
if (this.stopped) return;
|
|
14108
14229
|
this.uncredentialed = new Set(
|
|
14109
14230
|
items.filter((it) => !it.credential && !getCredential(device.id, it.agentId)).map((it) => `${it.workspaceId}|${it.agentId}`)
|
|
14110
14231
|
);
|
|
@@ -14117,6 +14238,7 @@ var CompanionSupervisor = class {
|
|
|
14117
14238
|
for (const wsId of [...this.workspaces.keys()]) {
|
|
14118
14239
|
if (!byWorkspace.has(wsId)) await this.removeWorkspace(wsId);
|
|
14119
14240
|
}
|
|
14241
|
+
if (this.stopped) return;
|
|
14120
14242
|
for (const [wsId, list] of byWorkspace) this.reconcileWorkspace(device.id, wsId, list);
|
|
14121
14243
|
const pruned = pruneCredentials(
|
|
14122
14244
|
device.id,
|
|
@@ -14148,6 +14270,7 @@ var CompanionSupervisor = class {
|
|
|
14148
14270
|
async restoreMissingCredentials(deviceId, items) {
|
|
14149
14271
|
const restored = [];
|
|
14150
14272
|
for (const it of items) {
|
|
14273
|
+
if (this.stopped) return restored;
|
|
14151
14274
|
if (it.credential || getCredential(deviceId, it.agentId)) {
|
|
14152
14275
|
restored.push(it);
|
|
14153
14276
|
continue;
|
|
@@ -14159,7 +14282,11 @@ var CompanionSupervisor = class {
|
|
|
14159
14282
|
continue;
|
|
14160
14283
|
}
|
|
14161
14284
|
try {
|
|
14162
|
-
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
14285
|
+
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
14286
|
+
it.agentId,
|
|
14287
|
+
this.reconciliationAbort.signal
|
|
14288
|
+
);
|
|
14289
|
+
if (this.stopped) return restored;
|
|
14163
14290
|
setCredential(deviceId, it.agentId, credential);
|
|
14164
14291
|
this.log.info(
|
|
14165
14292
|
{ agentId: it.agentId, agentName: it.agentDisplayName },
|
|
@@ -14452,7 +14579,16 @@ var CompanionSupervisor = class {
|
|
|
14452
14579
|
},
|
|
14453
14580
|
runner: {
|
|
14454
14581
|
provider: this.runnerProvider,
|
|
14455
|
-
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
|
+
} : {},
|
|
14456
14592
|
...this.runnerEnv ? { env: this.runnerEnv } : {},
|
|
14457
14593
|
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
14458
14594
|
connectorHealth: this.connectorHealth,
|
|
@@ -15034,6 +15170,7 @@ var CompanionSupervisor = class {
|
|
|
15034
15170
|
// ---- control actions ----
|
|
15035
15171
|
async shutdown() {
|
|
15036
15172
|
this.stopped = true;
|
|
15173
|
+
this.reconciliationAbort.abort();
|
|
15037
15174
|
this.updater?.stop();
|
|
15038
15175
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
15039
15176
|
if (this.detectTimer) clearInterval(this.detectTimer);
|
|
@@ -15311,33 +15448,46 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15311
15448
|
});
|
|
15312
15449
|
let restartForUpdate = async () => {
|
|
15313
15450
|
};
|
|
15314
|
-
|
|
15315
|
-
config: cfg,
|
|
15316
|
-
log: log2,
|
|
15317
|
-
hub,
|
|
15318
|
-
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
15319
|
-
...embeddedRunner({
|
|
15320
|
-
electron: Boolean(process.versions.electron),
|
|
15321
|
-
execPath: process.execPath,
|
|
15322
|
-
moduleUrl: import.meta.url,
|
|
15323
|
-
env: process.env
|
|
15324
|
-
}) ?? {},
|
|
15325
|
-
restartForUpdate: () => restartForUpdate()
|
|
15326
|
-
});
|
|
15327
|
-
await supervisor.start();
|
|
15451
|
+
let supervisor;
|
|
15328
15452
|
let control = null;
|
|
15329
15453
|
let dashboard = null;
|
|
15454
|
+
let stopped = false;
|
|
15455
|
+
const stop2 = async () => {
|
|
15456
|
+
if (stopped) return;
|
|
15457
|
+
stopped = true;
|
|
15458
|
+
clearRuntimeStateIfOurs(instanceId);
|
|
15459
|
+
process.removeListener("exit", clearRuntimeOnExit);
|
|
15460
|
+
await Promise.allSettled([
|
|
15461
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
15462
|
+
...control ? [control.close()] : [],
|
|
15463
|
+
...dashboard ? [dashboard.close()] : []
|
|
15464
|
+
]);
|
|
15465
|
+
};
|
|
15330
15466
|
try {
|
|
15467
|
+
supervisor = new CompanionSupervisor({
|
|
15468
|
+
config: cfg,
|
|
15469
|
+
log: log2,
|
|
15470
|
+
hub,
|
|
15471
|
+
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
15472
|
+
...embeddedRunner({
|
|
15473
|
+
electron: Boolean(process.versions.electron),
|
|
15474
|
+
execPath: process.execPath,
|
|
15475
|
+
moduleUrl: import.meta.url,
|
|
15476
|
+
env: process.env
|
|
15477
|
+
}) ?? {},
|
|
15478
|
+
restartForUpdate: () => restartForUpdate()
|
|
15479
|
+
});
|
|
15480
|
+
const active2 = supervisor;
|
|
15331
15481
|
control = await startControlServer({
|
|
15332
15482
|
status: () => ({
|
|
15333
15483
|
...hub.statusJson(),
|
|
15334
|
-
...
|
|
15335
|
-
detection:
|
|
15484
|
+
...active2.lastHeartbeat(),
|
|
15485
|
+
detection: active2.currentDetection()
|
|
15336
15486
|
}),
|
|
15337
|
-
detect: () =>
|
|
15338
|
-
allow: (runtime) =>
|
|
15339
|
-
update: (auto) =>
|
|
15340
|
-
stop: () => void
|
|
15487
|
+
detect: () => active2.redetect(),
|
|
15488
|
+
allow: (runtime) => active2.allowHarness(runtime),
|
|
15489
|
+
update: (auto) => active2.update(auto),
|
|
15490
|
+
stop: () => void stop2().then(() => active2.requestStop())
|
|
15341
15491
|
});
|
|
15342
15492
|
if (opts.dashboard) {
|
|
15343
15493
|
const preferredPort = opts.port ?? cfg.dashboardPort;
|
|
@@ -15348,6 +15498,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15348
15498
|
});
|
|
15349
15499
|
hub.setDashboardUrl(dashboard.url);
|
|
15350
15500
|
}
|
|
15501
|
+
if (stopped) throw new Error("The companion was stopped during startup.");
|
|
15351
15502
|
writeRuntimeState({
|
|
15352
15503
|
pid: process.pid,
|
|
15353
15504
|
socket: control.path,
|
|
@@ -15365,7 +15516,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15365
15516
|
clearRuntimeStateIfOurs(instanceId);
|
|
15366
15517
|
process.removeListener("exit", clearRuntimeOnExit);
|
|
15367
15518
|
await Promise.allSettled([
|
|
15368
|
-
supervisor.shutdown(),
|
|
15519
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
15369
15520
|
...control ? [control.close()] : [],
|
|
15370
15521
|
...dashboard ? [dashboard.close()] : []
|
|
15371
15522
|
]);
|
|
@@ -15377,22 +15528,17 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15377
15528
|
});
|
|
15378
15529
|
throw err;
|
|
15379
15530
|
}
|
|
15380
|
-
|
|
15381
|
-
const stop2 = async () => {
|
|
15382
|
-
if (stopped) return;
|
|
15383
|
-
stopped = true;
|
|
15384
|
-
clearRuntimeStateIfOurs(instanceId);
|
|
15385
|
-
process.removeListener("exit", clearRuntimeOnExit);
|
|
15386
|
-
try {
|
|
15387
|
-
await supervisor.shutdown();
|
|
15388
|
-
await closeSurfaces(control, dashboard);
|
|
15389
|
-
} catch {
|
|
15390
|
-
}
|
|
15391
|
-
};
|
|
15531
|
+
const active = supervisor;
|
|
15392
15532
|
restartForUpdate = async () => {
|
|
15393
15533
|
await stop2();
|
|
15394
15534
|
process.exit(0);
|
|
15395
15535
|
};
|
|
15536
|
+
void active.start().catch(async (err) => {
|
|
15537
|
+
log2.error({ err }, "Couldn't initialize the companion.");
|
|
15538
|
+
recordCrash({ reason: String(err), origin: "startup", at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
15539
|
+
await stop2();
|
|
15540
|
+
process.exitCode = 1;
|
|
15541
|
+
});
|
|
15396
15542
|
return {
|
|
15397
15543
|
ok: true,
|
|
15398
15544
|
runtime: {
|
|
@@ -15400,15 +15546,38 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15400
15546
|
socketPath: control.path,
|
|
15401
15547
|
config: cfg,
|
|
15402
15548
|
stop: stop2,
|
|
15403
|
-
heartbeatNow: () =>
|
|
15549
|
+
heartbeatNow: () => active.heartbeatNow(),
|
|
15404
15550
|
harnesses: () => hub.statusJson().harnesses ?? [],
|
|
15405
|
-
detect: () =>
|
|
15551
|
+
detect: () => active.redetect()
|
|
15406
15552
|
}
|
|
15407
15553
|
};
|
|
15408
15554
|
}
|
|
15409
|
-
|
|
15410
|
-
|
|
15411
|
-
|
|
15555
|
+
|
|
15556
|
+
// src/start-observation.ts
|
|
15557
|
+
init_cabane_command();
|
|
15558
|
+
async function observeStart(deps = {}) {
|
|
15559
|
+
const read2 = deps.readState ?? readLiveRuntimeState;
|
|
15560
|
+
const verify = deps.verify ?? verifyRuntime;
|
|
15561
|
+
const now = deps.now ?? Date.now;
|
|
15562
|
+
const sleep3 = deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
15563
|
+
const deadline = now() + 8e3;
|
|
15564
|
+
for (; ; ) {
|
|
15565
|
+
const state = await read2();
|
|
15566
|
+
if (state?.socket && await verify(state) === "ours") return state;
|
|
15567
|
+
if (now() >= deadline) return null;
|
|
15568
|
+
await sleep3(Math.min(150, Math.max(0, deadline - now())));
|
|
15569
|
+
}
|
|
15570
|
+
}
|
|
15571
|
+
function markLaunchRequester() {
|
|
15572
|
+
process.title = "cabane-launcher";
|
|
15573
|
+
}
|
|
15574
|
+
function unconfirmedStartLines(service2) {
|
|
15575
|
+
return [
|
|
15576
|
+
"Cabane's start was requested, but readiness isn't confirmed yet. It may still finish starting in the background.",
|
|
15577
|
+
`Check: ${cabane("status")}`,
|
|
15578
|
+
...service2 ? [`Service: ${cabane("service status")}`] : [],
|
|
15579
|
+
`Log: ${cabane("logs")}`
|
|
15580
|
+
];
|
|
15412
15581
|
}
|
|
15413
15582
|
|
|
15414
15583
|
// src/commands/start.ts
|
|
@@ -15817,15 +15986,11 @@ init_config();
|
|
|
15817
15986
|
import { spawn as spawn7 } from "child_process";
|
|
15818
15987
|
import { closeSync as closeSync3, mkdirSync as mkdirSync17, openSync as openSync4 } from "fs";
|
|
15819
15988
|
init_config();
|
|
15820
|
-
var STARTUP_TIMEOUT_MS = 8e3;
|
|
15821
|
-
var POLL_INTERVAL_MS = 150;
|
|
15822
15989
|
async function startDaemon(opts = {}, deps = {}) {
|
|
15823
15990
|
const ensureRuntime = deps.ensureRuntime ?? warnAboutHarnessReadiness;
|
|
15824
15991
|
const readState = deps.readState ?? readLiveRuntimeState;
|
|
15825
15992
|
const verify = deps.verify ?? ((s) => verifyRuntime(s));
|
|
15826
15993
|
const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
|
|
15827
|
-
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15828
|
-
const now = deps.now ?? (() => Date.now());
|
|
15829
15994
|
const quiet = opts.report === "failures" || opts.report === "silent";
|
|
15830
15995
|
const cfg = (deps.requireCfg ?? requireConfig)();
|
|
15831
15996
|
ensureRuntime(cfg, {
|
|
@@ -15837,7 +16002,18 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
15837
16002
|
});
|
|
15838
16003
|
const existing = readState();
|
|
15839
16004
|
if (existing) {
|
|
15840
|
-
|
|
16005
|
+
const verdict = await verify(existing);
|
|
16006
|
+
if (verdict !== "stale") {
|
|
16007
|
+
if (verdict !== "ours") {
|
|
16008
|
+
const state2 = await observeStart({ ...deps, readState, verify });
|
|
16009
|
+
if (!state2) {
|
|
16010
|
+
if (opts.report !== "silent")
|
|
16011
|
+
process.stdout.write(unconfirmedStartLines(false).join("\n") + "\n");
|
|
16012
|
+
process.exitCode = 1;
|
|
16013
|
+
return { started: false, state: null, unconfirmed: true };
|
|
16014
|
+
}
|
|
16015
|
+
return { started: false, state: state2 };
|
|
16016
|
+
}
|
|
15841
16017
|
if (opts.report !== "silent")
|
|
15842
16018
|
process.stdout.write(alreadyRunningLines(existing.pid).join("\n") + "\n");
|
|
15843
16019
|
return { started: false, state: existing };
|
|
@@ -15855,28 +16031,21 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
15855
16031
|
spawned.unref();
|
|
15856
16032
|
return spawned;
|
|
15857
16033
|
};
|
|
15858
|
-
|
|
15859
|
-
const waitForReady = async () => {
|
|
15860
|
-
const deadline = now() + STARTUP_TIMEOUT_MS;
|
|
15861
|
-
let seen = readState();
|
|
15862
|
-
while (!ready(seen) && now() < deadline) {
|
|
15863
|
-
await sleep3(POLL_INTERVAL_MS);
|
|
15864
|
-
seen = readState();
|
|
15865
|
-
}
|
|
15866
|
-
return ready(seen) ? seen : null;
|
|
15867
|
-
};
|
|
16034
|
+
markLaunchRequester();
|
|
15868
16035
|
const child = launchDetached();
|
|
15869
|
-
const state = await
|
|
16036
|
+
const state = await observeStart({ ...deps, readState, verify });
|
|
15870
16037
|
if (!state) {
|
|
15871
|
-
const error =
|
|
16038
|
+
const error = child.failure?.();
|
|
16039
|
+
if (error) {
|
|
16040
|
+
if (opts.report !== "silent") process.stdout.write(`Cabane couldn't start: ${error}
|
|
16041
|
+
`);
|
|
16042
|
+
process.exitCode = 1;
|
|
16043
|
+
return { started: false, state: null, error };
|
|
16044
|
+
}
|
|
15872
16045
|
if (opts.report !== "silent")
|
|
15873
|
-
process.stdout.write(
|
|
15874
|
-
`Cabane Companion was launched (pid ${child?.pid ?? "?"}) but didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s.
|
|
15875
|
-
Check ${companionLogPath()} for errors, or run \`${cabane("status")}\`.
|
|
15876
|
-
`
|
|
15877
|
-
);
|
|
16046
|
+
process.stdout.write(unconfirmedStartLines(false).join("\n") + "\n");
|
|
15878
16047
|
process.exitCode = 1;
|
|
15879
|
-
return { started: false, state: null,
|
|
16048
|
+
return { started: false, state: null, unconfirmed: true };
|
|
15880
16049
|
}
|
|
15881
16050
|
if (quiet) return { started: true, state };
|
|
15882
16051
|
process.stdout.write(
|
|
@@ -15896,11 +16065,19 @@ function defaultSpawnDetached(args) {
|
|
|
15896
16065
|
mkdirSync17(cabaneDir(), { recursive: true });
|
|
15897
16066
|
const logFd = openSync4(companionLogPath(), "a");
|
|
15898
16067
|
try {
|
|
15899
|
-
|
|
16068
|
+
const child = spawn7(process.execPath, [cliPath, ...args], {
|
|
15900
16069
|
detached: true,
|
|
15901
16070
|
stdio: ["ignore", logFd, logFd],
|
|
15902
16071
|
env: { ...process.env, CABANE_COMPANION_DAEMON: "1" }
|
|
15903
16072
|
});
|
|
16073
|
+
let failure;
|
|
16074
|
+
child.once("error", (err) => {
|
|
16075
|
+
failure = err.message;
|
|
16076
|
+
});
|
|
16077
|
+
child.once("exit", (code, signal) => {
|
|
16078
|
+
failure = `The background process exited (${signal ?? code}). Check cabane logs.`;
|
|
16079
|
+
});
|
|
16080
|
+
return { pid: child.pid, unref: () => child.unref(), failure: () => failure };
|
|
15904
16081
|
} finally {
|
|
15905
16082
|
closeSync3(logFd);
|
|
15906
16083
|
}
|
|
@@ -16020,8 +16197,10 @@ async function start(opts = {}, deps = {}) {
|
|
|
16020
16197
|
try {
|
|
16021
16198
|
return await startScript(opts, deps);
|
|
16022
16199
|
} catch (err) {
|
|
16023
|
-
|
|
16024
|
-
|
|
16200
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
16201
|
+
if (process.env.CABANE_COMPANION_SUPERVISED === "1")
|
|
16202
|
+
getLogger().error({ err }, `Cabane couldn't start: ${reason}`);
|
|
16203
|
+
for (const line of startFailure(reason, reloadLine())) write(line);
|
|
16025
16204
|
process.exitCode = 1;
|
|
16026
16205
|
return null;
|
|
16027
16206
|
} finally {
|
|
@@ -16057,23 +16236,41 @@ async function startScript(opts, deps) {
|
|
|
16057
16236
|
mode = "attached";
|
|
16058
16237
|
}
|
|
16059
16238
|
} else if (installed) {
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16239
|
+
markLaunchRequester();
|
|
16240
|
+
const before = await serviceState();
|
|
16241
|
+
if (!serviceLaunchPending(before)) {
|
|
16242
|
+
const result = await startService();
|
|
16243
|
+
if (!result.ok) {
|
|
16244
|
+
running = await observeStart({ ...deps, readState: deps.live });
|
|
16245
|
+
const current2 = await serviceState();
|
|
16246
|
+
if (!running && !serviceLaunchPending(current2)) throw new Error(result.lines.join(" "));
|
|
16247
|
+
}
|
|
16066
16248
|
}
|
|
16067
|
-
|
|
16068
|
-
|
|
16249
|
+
running ??= await observeStart({ ...deps, readState: deps.live });
|
|
16250
|
+
if (!running) {
|
|
16251
|
+
const current2 = await serviceState();
|
|
16252
|
+
if (!current2.nodeOk || current2.state === "failed")
|
|
16253
|
+
throw new Error(
|
|
16254
|
+
!current2.nodeOk ? "The service Node executable is missing." : "The service failed. Check cabane logs."
|
|
16255
|
+
);
|
|
16256
|
+
return reportUnconfirmed(true);
|
|
16257
|
+
}
|
|
16258
|
+
const current = await serviceState();
|
|
16259
|
+
mode = current.state === "running" && current.pid === running.pid ? "service" : "existing";
|
|
16069
16260
|
} else {
|
|
16070
16261
|
const result = await startDaemon({ ...opts, report: "silent" });
|
|
16262
|
+
if (result.unconfirmed) return reportUnconfirmed(false);
|
|
16071
16263
|
if (!result.state)
|
|
16072
16264
|
throw new Error(result.error ?? "The background process did not report ready.");
|
|
16073
16265
|
running = result.state;
|
|
16074
16266
|
mode = result.started ? "background" : "existing";
|
|
16075
16267
|
}
|
|
16076
16268
|
}
|
|
16269
|
+
if (!runtime && mode === "existing") {
|
|
16270
|
+
running = await observeStart({ ...deps, readState: deps.live });
|
|
16271
|
+
if (!running) return reportUnconfirmed(installed);
|
|
16272
|
+
}
|
|
16273
|
+
const attached = runtime ? runAttached(runtime) : null;
|
|
16077
16274
|
const socket = runtime?.socketPath ?? running?.socket;
|
|
16078
16275
|
let status2 = await (deps.waitAck ?? waitForAcknowledgement)(socket);
|
|
16079
16276
|
let detection = status2.detection ?? null;
|
|
@@ -16087,6 +16284,26 @@ async function startScript(opts, deps) {
|
|
|
16087
16284
|
const latest = await readTerminalStatus(socket);
|
|
16088
16285
|
if (latest.lastHeartbeatAckAt || latest.lastHeartbeatRefused) status2 = latest;
|
|
16089
16286
|
}
|
|
16287
|
+
if (!runtime) {
|
|
16288
|
+
const current = await (deps.live ?? liveCompanionState)();
|
|
16289
|
+
if (!current || await (deps.verify ?? verifyRuntime)(current) !== "ours") {
|
|
16290
|
+
if (installed) {
|
|
16291
|
+
const service2 = await serviceState();
|
|
16292
|
+
if (!service2.pid && ["not loaded", "dead", "inactive", "failed"].includes(service2.state))
|
|
16293
|
+
throw new Error("The service stopped before startup completed.");
|
|
16294
|
+
}
|
|
16295
|
+
return reportUnconfirmed(installed);
|
|
16296
|
+
}
|
|
16297
|
+
if (current.pid !== running?.pid || current.instanceId !== running?.instanceId) {
|
|
16298
|
+
status2 = await readTerminalStatus(current.socket);
|
|
16299
|
+
detection = status2.detection ?? null;
|
|
16300
|
+
}
|
|
16301
|
+
running = current;
|
|
16302
|
+
if (mode === "service") {
|
|
16303
|
+
const service2 = await serviceState();
|
|
16304
|
+
if (service2.state !== "running" || service2.pid !== current.pid) mode = "existing";
|
|
16305
|
+
}
|
|
16306
|
+
}
|
|
16090
16307
|
const personalUrl = `${cfg.baseUrl.replace(/\/$/, "")}/settings/devices`;
|
|
16091
16308
|
const address = opts.pairing ? opts.pairing.devicesUrl : personalUrl;
|
|
16092
16309
|
if (!daemon)
|
|
@@ -16103,10 +16320,18 @@ async function startScript(opts, deps) {
|
|
|
16103
16320
|
write(line);
|
|
16104
16321
|
if (runtime) {
|
|
16105
16322
|
setConsoleLogging(true);
|
|
16106
|
-
await
|
|
16323
|
+
await attached;
|
|
16107
16324
|
}
|
|
16108
16325
|
return detection;
|
|
16109
16326
|
}
|
|
16327
|
+
function serviceLaunchPending(state) {
|
|
16328
|
+
return !!state.pid || ["running", "activating", "auto-restart"].includes(state.state) || state.platform === "launchd" && state.installed && !["not loaded", "unknown", "failed"].includes(state.state);
|
|
16329
|
+
}
|
|
16330
|
+
function reportUnconfirmed(service2) {
|
|
16331
|
+
for (const line of unconfirmedStartLines(service2)) write(line);
|
|
16332
|
+
process.exitCode = 1;
|
|
16333
|
+
return null;
|
|
16334
|
+
}
|
|
16110
16335
|
async function detectOverSocket(socket) {
|
|
16111
16336
|
const body = await controlRequest(socket, { cmd: "detect" }, 15e3);
|
|
16112
16337
|
return body.detection;
|
|
@@ -16480,7 +16705,7 @@ init_term();
|
|
|
16480
16705
|
|
|
16481
16706
|
// src/commands/stop.ts
|
|
16482
16707
|
var TERM_GRACE_MS = 6e3;
|
|
16483
|
-
var
|
|
16708
|
+
var POLL_INTERVAL_MS = 150;
|
|
16484
16709
|
async function stop(deps = {}) {
|
|
16485
16710
|
if ((deps.serviceInstalled ?? serviceInstalled)()) {
|
|
16486
16711
|
const outcome = await (deps.stopService ?? stopService)();
|
|
@@ -16526,7 +16751,7 @@ async function stop(deps = {}) {
|
|
|
16526
16751
|
process.stdout.write("companion: stopped.\n");
|
|
16527
16752
|
return;
|
|
16528
16753
|
}
|
|
16529
|
-
await sleep3(
|
|
16754
|
+
await sleep3(POLL_INTERVAL_MS);
|
|
16530
16755
|
}
|
|
16531
16756
|
process.stdout.write(
|
|
16532
16757
|
`companion: didn't exit within ${TERM_GRACE_MS / 1e3}s, sending SIGKILL.
|
|
@@ -16683,7 +16908,10 @@ function row2(label, value) {
|
|
|
16683
16908
|
async function status(deps = {}) {
|
|
16684
16909
|
const out = (line) => void process.stdout.write(`${line}
|
|
16685
16910
|
`);
|
|
16686
|
-
const
|
|
16911
|
+
const candidate = readLiveRuntimeState({ sweep: false });
|
|
16912
|
+
const verdict = candidate ? await verifyRuntime(candidate) : null;
|
|
16913
|
+
const running = verdict === "stale" ? null : candidate;
|
|
16914
|
+
const service2 = await serviceState();
|
|
16687
16915
|
for (const line of pathLines(reloadLine())) out(line);
|
|
16688
16916
|
const strays = strayStatusLines(running, deps.strays);
|
|
16689
16917
|
out(` ${versionLine()}`);
|
|
@@ -16705,7 +16933,7 @@ async function status(deps = {}) {
|
|
|
16705
16933
|
out(row2("Paired with", `${cfg.baseUrl}${cfg.deviceLabel ? ` as ${cfg.deviceLabel}` : ""}`));
|
|
16706
16934
|
let detection = null;
|
|
16707
16935
|
if (running) {
|
|
16708
|
-
const mode =
|
|
16936
|
+
const mode = verdict !== "ours" ? "identity unconfirmed" : runningMode(running, service2);
|
|
16709
16937
|
const uptime = formatUptime(running.startedAt);
|
|
16710
16938
|
out(row2("Running", `${mode} (pid ${running.pid}${uptime ? `, up ${uptime}` : ""})`));
|
|
16711
16939
|
detection = await runningDetection(running);
|
|
@@ -16723,7 +16951,6 @@ async function status(deps = {}) {
|
|
|
16723
16951
|
)
|
|
16724
16952
|
);
|
|
16725
16953
|
for (const line of strays) out(line);
|
|
16726
|
-
const service2 = await serviceState();
|
|
16727
16954
|
for (const line of serviceRows(service2)) out(line);
|
|
16728
16955
|
for (const line of updateRows(readUpdateState(), cfg.update?.auto, {
|
|
16729
16956
|
version: false,
|
|
@@ -16793,10 +17020,13 @@ function otherInstall(install, own) {
|
|
|
16793
17020
|
const ownAlias = own ? real(join30(dirname13(own), "cabane-companion")) : null;
|
|
16794
17021
|
return real(found) === ownAlias ? null : dirname13(found);
|
|
16795
17022
|
}
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
17023
|
+
function runningMode(running, service2) {
|
|
17024
|
+
if (service2.installed) {
|
|
17025
|
+
if (service2.state === "running" && service2.pid === running.pid) return "as a service";
|
|
17026
|
+
if (service2.state === "unknown" || service2.pid === running.pid || service2.state === "running" && !service2.pid)
|
|
17027
|
+
return "run mode unconfirmed";
|
|
17028
|
+
}
|
|
17029
|
+
return running.daemon ? "in the background" : "in a terminal";
|
|
16800
17030
|
}
|
|
16801
17031
|
async function runningDetection(running) {
|
|
16802
17032
|
if (!running.socket) return null;
|
|
@@ -16994,7 +17224,7 @@ init_errors();
|
|
|
16994
17224
|
init_src();
|
|
16995
17225
|
init_transcript_writer();
|
|
16996
17226
|
import { existsSync as existsSync19, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "fs";
|
|
16997
|
-
import { isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
17227
|
+
import { basename as basename4, isAbsolute as isAbsolute2, join as join31 } from "path";
|
|
16998
17228
|
async function transcript(opts = {}) {
|
|
16999
17229
|
const dir2 = transcriptsDir();
|
|
17000
17230
|
if (opts.follow) {
|
|
@@ -17117,13 +17347,28 @@ async function followTranscripts(dir2) {
|
|
|
17117
17347
|
follower.tick();
|
|
17118
17348
|
});
|
|
17119
17349
|
}
|
|
17120
|
-
function
|
|
17350
|
+
function listTranscriptFiles(dir2) {
|
|
17351
|
+
let entries;
|
|
17121
17352
|
try {
|
|
17122
|
-
|
|
17353
|
+
entries = readdirSync6(dir2, { withFileTypes: true });
|
|
17123
17354
|
} catch {
|
|
17124
17355
|
return [];
|
|
17125
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);
|
|
17126
17370
|
}
|
|
17371
|
+
var listFiles = listTranscriptFiles;
|
|
17127
17372
|
function printList(dir2) {
|
|
17128
17373
|
const files = listFiles(dir2);
|
|
17129
17374
|
if (files.length === 0) {
|
|
@@ -17170,6 +17415,7 @@ function peek(path) {
|
|
|
17170
17415
|
function resolveTarget(dir2, target) {
|
|
17171
17416
|
if (isAbsolute2(target) || target.includes("/")) {
|
|
17172
17417
|
if (existsSync19(target)) return target;
|
|
17418
|
+
if (!isAbsolute2(target) && existsSync19(join31(dir2, target))) return join31(dir2, target);
|
|
17173
17419
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
17174
17420
|
}
|
|
17175
17421
|
const exact = join31(dir2, target);
|