@cabane/companion 0.6.158 → 0.6.160
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/README.md +4 -4
- package/dist/cli.js +601 -457
- package/dist/launch.js +10 -17
- package/dist/pairing-config.js +19 -17
- package/dist/postinstall.js +141 -28
- package/dist/runtime.js +440 -372
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,166 +9,11 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
import { realpathSync
|
|
14
|
-
import { createRequire } from "module";
|
|
12
|
+
// src/install-kind.ts
|
|
13
|
+
import { realpathSync } from "fs";
|
|
15
14
|
import { homedir } from "os";
|
|
16
15
|
import { dirname, join, sep } from "path";
|
|
17
16
|
import { fileURLToPath } from "url";
|
|
18
|
-
function nodeResolveSelf(specifier) {
|
|
19
|
-
const here = fileURLToPath(import.meta.url);
|
|
20
|
-
if (typeof import.meta.resolve === "function") {
|
|
21
|
-
try {
|
|
22
|
-
return fileURLToPath(import.meta.resolve(specifier));
|
|
23
|
-
} catch {
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
return createRequire(here).resolve(specifier);
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
return walkNodeModules(specifier, here);
|
|
31
|
-
}
|
|
32
|
-
function walkNodeModules(pkg2, fromPath) {
|
|
33
|
-
let dir2 = dirname(fromPath);
|
|
34
|
-
for (; ; ) {
|
|
35
|
-
const manifest = join(dir2, "node_modules", pkg2, "package.json");
|
|
36
|
-
if (nodeIsFile(manifest)) {
|
|
37
|
-
try {
|
|
38
|
-
return realpathSync(manifest);
|
|
39
|
-
} catch {
|
|
40
|
-
return manifest;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const parent = dirname(dir2);
|
|
44
|
-
if (parent === dir2) return null;
|
|
45
|
-
dir2 = parent;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function nodeResolve(specifier, fromPath) {
|
|
49
|
-
try {
|
|
50
|
-
return createRequire(fromPath).resolve(specifier);
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function nodeIsFile(path) {
|
|
56
|
-
try {
|
|
57
|
-
return statSync(path).isFile();
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function detectPreferMusl(platform = process.platform) {
|
|
63
|
-
if (platform !== "linux") return false;
|
|
64
|
-
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
65
|
-
const header = report?.header;
|
|
66
|
-
return report != null && header?.glibcVersionRuntime === void 0;
|
|
67
|
-
}
|
|
68
|
-
function defaultResolveEnv() {
|
|
69
|
-
return {
|
|
70
|
-
platform: process.platform,
|
|
71
|
-
arch: process.arch,
|
|
72
|
-
preferMusl: detectPreferMusl(),
|
|
73
|
-
// Both resolutions start from this module, which tsup bundles into
|
|
74
|
-
// `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
|
|
75
|
-
// root — so one environment serves the runtime checks and the install
|
|
76
|
-
// verifier alike.
|
|
77
|
-
resolveSelf: nodeResolveSelf,
|
|
78
|
-
resolve: nodeResolve,
|
|
79
|
-
isFile: nodeIsFile
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function claudePlatformPackages(platform, arch, preferMusl) {
|
|
83
|
-
if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
|
|
84
|
-
if (platform === "linux") {
|
|
85
|
-
return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
|
|
86
|
-
}
|
|
87
|
-
return [`${CLAUDE_SDK}-${platform}-${arch}`];
|
|
88
|
-
}
|
|
89
|
-
function resolveClaude(env) {
|
|
90
|
-
const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
|
|
91
|
-
const base = {
|
|
92
|
-
runtime: "claude-code",
|
|
93
|
-
packageName: candidates[0] ?? null,
|
|
94
|
-
platform: env.platform,
|
|
95
|
-
arch: env.arch
|
|
96
|
-
};
|
|
97
|
-
const sdkEntry = env.resolveSelf(CLAUDE_SDK);
|
|
98
|
-
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
99
|
-
let sawPackage = null;
|
|
100
|
-
for (const pkg2 of candidates) {
|
|
101
|
-
const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
|
|
102
|
-
if (!manifest) continue;
|
|
103
|
-
sawPackage ??= pkg2;
|
|
104
|
-
const exe = join(dirname(manifest), env.platform === "win32" ? "claude.exe" : "claude");
|
|
105
|
-
if (env.isFile(exe)) {
|
|
106
|
-
return { ...base, packageName: pkg2, status: "present", path: exe };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
|
|
110
|
-
}
|
|
111
|
-
function codexTargetTriple(platform, arch) {
|
|
112
|
-
if (platform === "linux" || platform === "android") {
|
|
113
|
-
if (arch === "x64") return "x86_64-unknown-linux-musl";
|
|
114
|
-
if (arch === "arm64") return "aarch64-unknown-linux-musl";
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
if (platform === "darwin") {
|
|
118
|
-
if (arch === "x64") return "x86_64-apple-darwin";
|
|
119
|
-
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
if (platform === "win32") {
|
|
123
|
-
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
124
|
-
if (arch === "arm64") return "aarch64-pc-windows-msvc";
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
function resolveCodex(env) {
|
|
130
|
-
const triple = codexTargetTriple(env.platform, env.arch);
|
|
131
|
-
const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
|
|
132
|
-
const base = {
|
|
133
|
-
runtime: "codex",
|
|
134
|
-
packageName: pkg2 ?? null,
|
|
135
|
-
platform: env.platform,
|
|
136
|
-
arch: env.arch
|
|
137
|
-
};
|
|
138
|
-
if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
|
|
139
|
-
const sdkEntry = env.resolveSelf(CODEX_SDK);
|
|
140
|
-
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
141
|
-
const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
|
|
142
|
-
if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
143
|
-
const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
|
|
144
|
-
if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
145
|
-
const packageRoot = join(dirname(platformManifest), "vendor", triple);
|
|
146
|
-
const exeName = env.platform === "win32" ? "codex.exe" : "codex";
|
|
147
|
-
const current = join(packageRoot, "bin", exeName);
|
|
148
|
-
if (env.isFile(current) && env.isFile(join(packageRoot, "codex-package.json"))) {
|
|
149
|
-
return { ...base, status: "present", path: current };
|
|
150
|
-
}
|
|
151
|
-
const legacy = join(packageRoot, "codex", exeName);
|
|
152
|
-
if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
|
|
153
|
-
return { ...base, status: "executable_missing", path: null };
|
|
154
|
-
}
|
|
155
|
-
function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
|
|
156
|
-
try {
|
|
157
|
-
return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
|
|
158
|
-
} catch {
|
|
159
|
-
return {
|
|
160
|
-
runtime,
|
|
161
|
-
status: "platform_package_missing",
|
|
162
|
-
path: null,
|
|
163
|
-
packageName: null,
|
|
164
|
-
platform: env.platform,
|
|
165
|
-
arch: env.arch
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function missingNoun(status2) {
|
|
170
|
-
return status2 === "executable_missing" ? "executable" : "platform package";
|
|
171
|
-
}
|
|
172
17
|
function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
173
18
|
const real = (path) => {
|
|
174
19
|
try {
|
|
@@ -180,47 +25,21 @@ function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
|
180
25
|
const scriptNode = join(real(home), ".cabane", "node") + sep;
|
|
181
26
|
return real(execPath).startsWith(scriptNode) ? "script" : "npm";
|
|
182
27
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const label = HARNESS_LABEL[r.runtime];
|
|
191
|
-
return `${label} is ${HARNESS_INTENT_WORD[r.runtime]}, but its installation is incomplete (${missingNoun(r.status)} is missing). It cannot run turns until repaired. ${repairAction()} Then restart the companion.`;
|
|
192
|
-
}
|
|
193
|
-
function harnessIssueNote(runtime, status2, install = companionInstall()) {
|
|
194
|
-
const intent = HARNESS_INTENT_WORD[runtime];
|
|
195
|
-
const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
|
|
196
|
-
return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction(install)} Then restart the companion.`;
|
|
28
|
+
function repairRoute(packageRoot = ownRoot(), install = companionInstall()) {
|
|
29
|
+
let root = packageRoot;
|
|
30
|
+
try {
|
|
31
|
+
root = realpathSync(packageRoot);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
return root.includes(`${sep}.cabane${sep}versions${sep}`) ? "managed" : install;
|
|
197
35
|
}
|
|
198
|
-
function
|
|
199
|
-
return
|
|
36
|
+
function ownRoot() {
|
|
37
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
200
38
|
}
|
|
201
|
-
var
|
|
202
|
-
var
|
|
203
|
-
"src/
|
|
39
|
+
var INSTALL_SCRIPT_COMMAND;
|
|
40
|
+
var init_install_kind = __esm({
|
|
41
|
+
"src/install-kind.ts"() {
|
|
204
42
|
"use strict";
|
|
205
|
-
CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
|
|
206
|
-
CODEX_SDK = "@openai/codex-sdk";
|
|
207
|
-
CODEX_CLI = "@openai/codex";
|
|
208
|
-
CODEX_PACKAGE_BY_TRIPLE = {
|
|
209
|
-
"x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
|
|
210
|
-
"aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
|
|
211
|
-
"x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
|
|
212
|
-
"aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
|
|
213
|
-
"x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
|
|
214
|
-
"aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
|
|
215
|
-
};
|
|
216
|
-
HARNESS_LABEL = {
|
|
217
|
-
"claude-code": "Claude Code",
|
|
218
|
-
codex: "Codex"
|
|
219
|
-
};
|
|
220
|
-
HARNESS_INTENT_WORD = {
|
|
221
|
-
"claude-code": "connected",
|
|
222
|
-
codex: "enabled"
|
|
223
|
-
};
|
|
224
43
|
INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
|
|
225
44
|
}
|
|
226
45
|
});
|
|
@@ -358,16 +177,37 @@ var init_term = __esm({
|
|
|
358
177
|
// src/cabane-command.ts
|
|
359
178
|
import { accessSync, constants, realpathSync as realpathSync2 } from "fs";
|
|
360
179
|
import { homedir as homedir3 } from "os";
|
|
361
|
-
import { delimiter, join as join2, sep as sep2 } from "path";
|
|
180
|
+
import { basename, delimiter, dirname as dirname2, join as join2, sep as sep2 } from "path";
|
|
181
|
+
function invokedCommand(argv1) {
|
|
182
|
+
if (!argv1 || argv1.includes(VERSIONS_SEGMENT)) return null;
|
|
183
|
+
const name = basename(argv1);
|
|
184
|
+
if (name === "cabane" || name === "cabane-companion") return join2(dirname2(argv1), "cabane");
|
|
185
|
+
return npmBinFor(argv1);
|
|
186
|
+
}
|
|
187
|
+
function rememberInvokedCommand(env = process.env, argv1 = process.argv[1]) {
|
|
188
|
+
if (env[INVOKED_COMMAND_ENV]) return;
|
|
189
|
+
const invoked = invokedCommand(argv1);
|
|
190
|
+
if (invoked) env[INVOKED_COMMAND_ENV] = invoked;
|
|
191
|
+
}
|
|
362
192
|
function installedCommandPath(deps = {}) {
|
|
363
193
|
const install = deps.install ?? companionInstall();
|
|
364
194
|
const home = deps.home ?? homedir3();
|
|
365
195
|
if (install === "script") return join2(home, ".cabane", "bin", "cabane");
|
|
366
196
|
const entry = deps.cliEntry ?? companionCliEntry();
|
|
197
|
+
const own = npmBinFor(entry);
|
|
198
|
+
if (own) return own;
|
|
199
|
+
if (!entry.includes(VERSIONS_SEGMENT)) return null;
|
|
200
|
+
const isExecutable = deps.isExecutable ?? executable;
|
|
201
|
+
const invoked = "invoked" in deps ? deps.invoked : process.env[INVOKED_COMMAND_ENV];
|
|
202
|
+
if (invoked && isExecutable(invoked)) return invoked;
|
|
203
|
+
const besideNode = join2(dirname2(dirname2(deps.execPath ?? process.execPath)), "bin", "cabane");
|
|
204
|
+
return isExecutable(besideNode) ? besideNode : null;
|
|
205
|
+
}
|
|
206
|
+
function npmBinFor(path) {
|
|
367
207
|
const marker = `${sep2}lib${sep2}node_modules${sep2}@cabane${sep2}companion${sep2}`;
|
|
368
|
-
const at =
|
|
208
|
+
const at = path.indexOf(marker);
|
|
369
209
|
if (at < 0) return null;
|
|
370
|
-
return join2(
|
|
210
|
+
return join2(path.slice(0, at), "bin", "cabane");
|
|
371
211
|
}
|
|
372
212
|
function cabaneCommand(deps = {}) {
|
|
373
213
|
const own = installedCommandPath(deps);
|
|
@@ -383,6 +223,9 @@ function cabaneCommand(deps = {}) {
|
|
|
383
223
|
function cabane(rest, deps) {
|
|
384
224
|
return `${cabaneCommand(deps)} ${rest}`;
|
|
385
225
|
}
|
|
226
|
+
function mayNeedUpdate(deps) {
|
|
227
|
+
return `This companion may need an update: run ${cabane("update", deps)}`;
|
|
228
|
+
}
|
|
386
229
|
function firstOnPath(name, path, isExecutable = executable) {
|
|
387
230
|
for (const dir2 of (path ?? "").split(delimiter)) {
|
|
388
231
|
if (!dir2) continue;
|
|
@@ -406,12 +249,15 @@ function safeRealpath(path) {
|
|
|
406
249
|
return null;
|
|
407
250
|
}
|
|
408
251
|
}
|
|
252
|
+
var INVOKED_COMMAND_ENV, VERSIONS_SEGMENT;
|
|
409
253
|
var init_cabane_command = __esm({
|
|
410
254
|
"src/cabane-command.ts"() {
|
|
411
255
|
"use strict";
|
|
412
|
-
|
|
256
|
+
init_install_kind();
|
|
413
257
|
init_cli_entry();
|
|
414
258
|
init_term();
|
|
259
|
+
INVOKED_COMMAND_ENV = "CABANE_INVOKED_COMMAND";
|
|
260
|
+
VERSIONS_SEGMENT = `${sep2}.cabane${sep2}versions${sep2}`;
|
|
415
261
|
}
|
|
416
262
|
});
|
|
417
263
|
|
|
@@ -641,7 +487,7 @@ import {
|
|
|
641
487
|
existsSync as existsSync2
|
|
642
488
|
} from "fs";
|
|
643
489
|
import { homedir as homedir4, userInfo } from "os";
|
|
644
|
-
import { dirname as
|
|
490
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
645
491
|
import { z as z2 } from "zod";
|
|
646
492
|
function realAccountHome() {
|
|
647
493
|
if (realHomeCache === void 0) {
|
|
@@ -835,7 +681,7 @@ function loadConfigTolerant() {
|
|
|
835
681
|
}
|
|
836
682
|
function saveConfig(cfg) {
|
|
837
683
|
const path = configPath();
|
|
838
|
-
mkdirSync(
|
|
684
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
839
685
|
try {
|
|
840
686
|
chmodSync(cabaneDir(), 448);
|
|
841
687
|
} catch {
|
|
@@ -1032,6 +878,226 @@ var init_config = __esm({
|
|
|
1032
878
|
}
|
|
1033
879
|
});
|
|
1034
880
|
|
|
881
|
+
// src/bundled-binaries.ts
|
|
882
|
+
import { realpathSync as realpathSync3, statSync } from "fs";
|
|
883
|
+
import { createRequire } from "module";
|
|
884
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
885
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
886
|
+
function nodeResolveSelf(specifier) {
|
|
887
|
+
const here = fileURLToPath3(import.meta.url);
|
|
888
|
+
if (typeof import.meta.resolve === "function") {
|
|
889
|
+
try {
|
|
890
|
+
return fileURLToPath3(import.meta.resolve(specifier));
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
try {
|
|
895
|
+
return createRequire(here).resolve(specifier);
|
|
896
|
+
} catch {
|
|
897
|
+
}
|
|
898
|
+
return walkNodeModules(specifier, here);
|
|
899
|
+
}
|
|
900
|
+
function walkNodeModules(pkg2, fromPath) {
|
|
901
|
+
let dir2 = dirname4(fromPath);
|
|
902
|
+
for (; ; ) {
|
|
903
|
+
const manifest = join4(dir2, "node_modules", pkg2, "package.json");
|
|
904
|
+
if (nodeIsFile(manifest)) {
|
|
905
|
+
try {
|
|
906
|
+
return realpathSync3(manifest);
|
|
907
|
+
} catch {
|
|
908
|
+
return manifest;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
const parent = dirname4(dir2);
|
|
912
|
+
if (parent === dir2) return null;
|
|
913
|
+
dir2 = parent;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function nodeResolve(specifier, fromPath) {
|
|
917
|
+
try {
|
|
918
|
+
return createRequire(fromPath).resolve(specifier);
|
|
919
|
+
} catch {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function nodeIsFile(path) {
|
|
924
|
+
try {
|
|
925
|
+
return statSync(path).isFile();
|
|
926
|
+
} catch {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function detectPreferMusl(platform = process.platform) {
|
|
931
|
+
if (platform !== "linux") return false;
|
|
932
|
+
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
933
|
+
const header = report?.header;
|
|
934
|
+
return report != null && header?.glibcVersionRuntime === void 0;
|
|
935
|
+
}
|
|
936
|
+
function defaultResolveEnv() {
|
|
937
|
+
return {
|
|
938
|
+
platform: process.platform,
|
|
939
|
+
arch: process.arch,
|
|
940
|
+
preferMusl: detectPreferMusl(),
|
|
941
|
+
// Both resolutions start from this module, which tsup bundles into
|
|
942
|
+
// `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
|
|
943
|
+
// root — so one environment serves the runtime checks and the install
|
|
944
|
+
// verifier alike.
|
|
945
|
+
resolveSelf: nodeResolveSelf,
|
|
946
|
+
resolve: nodeResolve,
|
|
947
|
+
isFile: nodeIsFile
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function claudePlatformPackages(platform, arch, preferMusl) {
|
|
951
|
+
if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
|
|
952
|
+
if (platform === "linux") {
|
|
953
|
+
return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
|
|
954
|
+
}
|
|
955
|
+
return [`${CLAUDE_SDK}-${platform}-${arch}`];
|
|
956
|
+
}
|
|
957
|
+
function resolveClaude(env) {
|
|
958
|
+
const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
|
|
959
|
+
const base = {
|
|
960
|
+
runtime: "claude-code",
|
|
961
|
+
packageName: candidates[0] ?? null,
|
|
962
|
+
platform: env.platform,
|
|
963
|
+
arch: env.arch
|
|
964
|
+
};
|
|
965
|
+
const sdkEntry = env.resolveSelf(CLAUDE_SDK);
|
|
966
|
+
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
967
|
+
let sawPackage = null;
|
|
968
|
+
for (const pkg2 of candidates) {
|
|
969
|
+
const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
|
|
970
|
+
if (!manifest) continue;
|
|
971
|
+
sawPackage ??= pkg2;
|
|
972
|
+
const exe = join4(dirname4(manifest), env.platform === "win32" ? "claude.exe" : "claude");
|
|
973
|
+
if (env.isFile(exe)) {
|
|
974
|
+
return { ...base, packageName: pkg2, status: "present", path: exe };
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
|
|
978
|
+
}
|
|
979
|
+
function codexTargetTriple(platform, arch) {
|
|
980
|
+
if (platform === "linux" || platform === "android") {
|
|
981
|
+
if (arch === "x64") return "x86_64-unknown-linux-musl";
|
|
982
|
+
if (arch === "arm64") return "aarch64-unknown-linux-musl";
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
if (platform === "darwin") {
|
|
986
|
+
if (arch === "x64") return "x86_64-apple-darwin";
|
|
987
|
+
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
if (platform === "win32") {
|
|
991
|
+
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
992
|
+
if (arch === "arm64") return "aarch64-pc-windows-msvc";
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
function codexPlatformPackage(platform, arch) {
|
|
998
|
+
const triple = codexTargetTriple(platform, arch);
|
|
999
|
+
return triple ? CODEX_PACKAGE_BY_TRIPLE[triple] ?? null : null;
|
|
1000
|
+
}
|
|
1001
|
+
function resolveCodex(env) {
|
|
1002
|
+
const triple = codexTargetTriple(env.platform, env.arch);
|
|
1003
|
+
const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
|
|
1004
|
+
const base = {
|
|
1005
|
+
runtime: "codex",
|
|
1006
|
+
packageName: pkg2 ?? null,
|
|
1007
|
+
platform: env.platform,
|
|
1008
|
+
arch: env.arch
|
|
1009
|
+
};
|
|
1010
|
+
if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
|
|
1011
|
+
const sdkEntry = env.resolveSelf(CODEX_SDK);
|
|
1012
|
+
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
1013
|
+
const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
|
|
1014
|
+
if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
1015
|
+
const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
|
|
1016
|
+
if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
1017
|
+
const packageRoot = join4(dirname4(platformManifest), "vendor", triple);
|
|
1018
|
+
const exeName = env.platform === "win32" ? "codex.exe" : "codex";
|
|
1019
|
+
const current = join4(packageRoot, "bin", exeName);
|
|
1020
|
+
if (env.isFile(current) && env.isFile(join4(packageRoot, "codex-package.json"))) {
|
|
1021
|
+
return { ...base, status: "present", path: current };
|
|
1022
|
+
}
|
|
1023
|
+
const legacy = join4(packageRoot, "codex", exeName);
|
|
1024
|
+
if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
|
|
1025
|
+
return { ...base, status: "executable_missing", path: null };
|
|
1026
|
+
}
|
|
1027
|
+
function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
|
|
1028
|
+
try {
|
|
1029
|
+
return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
|
|
1030
|
+
} catch {
|
|
1031
|
+
return {
|
|
1032
|
+
runtime,
|
|
1033
|
+
status: "platform_package_missing",
|
|
1034
|
+
path: null,
|
|
1035
|
+
packageName: null,
|
|
1036
|
+
platform: env.platform,
|
|
1037
|
+
arch: env.arch
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function missingNoun(status2) {
|
|
1042
|
+
return status2 === "executable_missing" ? "executable" : "platform package";
|
|
1043
|
+
}
|
|
1044
|
+
function repairCommands(route = repairRoute(), spell = cabane) {
|
|
1045
|
+
if (route === "managed") return [spell("stop"), spell("update")];
|
|
1046
|
+
if (route === "script") return [spell("stop"), INSTALL_SCRIPT_COMMAND];
|
|
1047
|
+
return ["npm i -g @cabane/companion --include=optional"];
|
|
1048
|
+
}
|
|
1049
|
+
function repairAction(route = repairRoute(), spell = cabane) {
|
|
1050
|
+
const [first, ...rest] = repairCommands(route, spell);
|
|
1051
|
+
if (rest.length === 0) return `Reinstall with \`${first}\`.`;
|
|
1052
|
+
return `Run \`${first}\`, then \`${rest.join("`, then `")}\`. ${STOP_WARNING}`;
|
|
1053
|
+
}
|
|
1054
|
+
function repairSteps(route = repairRoute()) {
|
|
1055
|
+
return repairCommands(route).join(", then ");
|
|
1056
|
+
}
|
|
1057
|
+
function repairCost(route = repairRoute()) {
|
|
1058
|
+
return repairCommands(route).length > 1 ? STOP_WARNING : "";
|
|
1059
|
+
}
|
|
1060
|
+
function startupWarning(r) {
|
|
1061
|
+
const label = HARNESS_LABEL[r.runtime];
|
|
1062
|
+
return `${label} is ${HARNESS_INTENT_WORD[r.runtime]}, but its installation is incomplete (${missingNoun(r.status)} is missing). It cannot run turns until repaired. ${repairAction()} Then restart the companion.`;
|
|
1063
|
+
}
|
|
1064
|
+
function harnessIssueNote(runtime, status2, route = repairRoute(), spell = cabane) {
|
|
1065
|
+
const intent = HARNESS_INTENT_WORD[runtime];
|
|
1066
|
+
const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
|
|
1067
|
+
return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction(route, spell)} Then restart the companion.`;
|
|
1068
|
+
}
|
|
1069
|
+
function turnIncompleteCopy(runtime) {
|
|
1070
|
+
return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
|
|
1071
|
+
}
|
|
1072
|
+
var CLAUDE_SDK, CODEX_SDK, CODEX_CLI, CODEX_PACKAGE_BY_TRIPLE, HARNESS_LABEL, HARNESS_INTENT_WORD, STOP_WARNING;
|
|
1073
|
+
var init_bundled_binaries = __esm({
|
|
1074
|
+
"src/bundled-binaries.ts"() {
|
|
1075
|
+
"use strict";
|
|
1076
|
+
init_cabane_command();
|
|
1077
|
+
init_install_kind();
|
|
1078
|
+
CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
|
|
1079
|
+
CODEX_SDK = "@openai/codex-sdk";
|
|
1080
|
+
CODEX_CLI = "@openai/codex";
|
|
1081
|
+
CODEX_PACKAGE_BY_TRIPLE = {
|
|
1082
|
+
"x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
|
|
1083
|
+
"aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
|
|
1084
|
+
"x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
|
|
1085
|
+
"aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
|
|
1086
|
+
"x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
|
|
1087
|
+
"aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
|
|
1088
|
+
};
|
|
1089
|
+
HARNESS_LABEL = {
|
|
1090
|
+
"claude-code": "Claude Code",
|
|
1091
|
+
codex: "Codex"
|
|
1092
|
+
};
|
|
1093
|
+
HARNESS_INTENT_WORD = {
|
|
1094
|
+
"claude-code": "connected",
|
|
1095
|
+
codex: "enabled"
|
|
1096
|
+
};
|
|
1097
|
+
STOP_WARNING = "Stopping ends any turn in progress.";
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1035
1101
|
// packages/agent-runtime/src/failure.ts
|
|
1036
1102
|
import { z as z4 } from "zod";
|
|
1037
1103
|
function encodeFailureReason(failure) {
|
|
@@ -1302,6 +1368,7 @@ var init_api = __esm({
|
|
|
1302
1368
|
"src/api.ts"() {
|
|
1303
1369
|
"use strict";
|
|
1304
1370
|
init_api_error_shape();
|
|
1371
|
+
init_cabane_command();
|
|
1305
1372
|
init_errors();
|
|
1306
1373
|
DEVICE_HEADER = "x-cabane-device";
|
|
1307
1374
|
RETRY_BACKOFF_MS = [250, 750];
|
|
@@ -1436,7 +1503,7 @@ var init_api = __esm({
|
|
|
1436
1503
|
}
|
|
1437
1504
|
this.opts.log?.warn(
|
|
1438
1505
|
{ kind: entry.kind, turnId: entry.turnId, seq: entry.seq, ...apiErrorLogFields(err) },
|
|
1439
|
-
|
|
1506
|
+
`Cabane rejected a saved update; it could not be delivered. ${mayNeedUpdate()}`
|
|
1440
1507
|
);
|
|
1441
1508
|
outbox.remove(entry.turnId, entry.seq);
|
|
1442
1509
|
progressed = true;
|
|
@@ -7503,10 +7570,10 @@ var init_turn_containment = __esm({
|
|
|
7503
7570
|
|
|
7504
7571
|
// src/secrets.ts
|
|
7505
7572
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
7506
|
-
import { join as
|
|
7573
|
+
import { join as join13 } from "path";
|
|
7507
7574
|
import { z as z14 } from "zod";
|
|
7508
7575
|
function secretsPath() {
|
|
7509
|
-
return
|
|
7576
|
+
return join13(cabaneDir(), "secrets.json");
|
|
7510
7577
|
}
|
|
7511
7578
|
function loadSecretStore() {
|
|
7512
7579
|
const path = secretsPath();
|
|
@@ -8556,10 +8623,10 @@ var init_build_options = __esm({
|
|
|
8556
8623
|
// src/codex-instructions.ts
|
|
8557
8624
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
8558
8625
|
import { tmpdir } from "os";
|
|
8559
|
-
import { join as
|
|
8626
|
+
import { join as join14 } from "path";
|
|
8560
8627
|
async function writeCodexInstructionsFile(contents) {
|
|
8561
|
-
const dir2 = await mkdtemp(
|
|
8562
|
-
const path =
|
|
8628
|
+
const dir2 = await mkdtemp(join14(tmpdir(), PREFIX));
|
|
8629
|
+
const path = join14(dir2, "instructions.md");
|
|
8563
8630
|
await writeFile(path, contents, { encoding: "utf8", mode: 384 });
|
|
8564
8631
|
return {
|
|
8565
8632
|
path,
|
|
@@ -8579,13 +8646,13 @@ var init_codex_instructions = __esm({
|
|
|
8579
8646
|
// src/codex-images.ts
|
|
8580
8647
|
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
8581
8648
|
import { tmpdir as tmpdir2 } from "os";
|
|
8582
|
-
import { join as
|
|
8649
|
+
import { join as join15 } from "path";
|
|
8583
8650
|
function createCodexImagePreparer(opts) {
|
|
8584
8651
|
const doFetch = opts.fetch ?? fetch;
|
|
8585
8652
|
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
8586
8653
|
return async (urls, signal) => {
|
|
8587
8654
|
signal.throwIfAborted();
|
|
8588
|
-
const dir2 = await mkdtemp2(
|
|
8655
|
+
const dir2 = await mkdtemp2(join15(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
|
|
8589
8656
|
const cleanup = async () => {
|
|
8590
8657
|
try {
|
|
8591
8658
|
await rm2(dir2, { recursive: true, force: true });
|
|
@@ -8606,7 +8673,7 @@ function createCodexImagePreparer(opts) {
|
|
|
8606
8673
|
const ext = EXTENSIONS[type];
|
|
8607
8674
|
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
8608
8675
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
8609
|
-
const path =
|
|
8676
|
+
const path = join15(dir2, `image-${index + 1}.${ext}`);
|
|
8610
8677
|
await writeFile2(path, bytes, { mode: 384 });
|
|
8611
8678
|
return path;
|
|
8612
8679
|
} catch (err) {
|
|
@@ -8752,15 +8819,15 @@ var init_prepare_failure = __esm({
|
|
|
8752
8819
|
|
|
8753
8820
|
// src/prepared.ts
|
|
8754
8821
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "fs";
|
|
8755
|
-
import { join as
|
|
8822
|
+
import { join as join16 } from "path";
|
|
8756
8823
|
function dirFor(workspaceId) {
|
|
8757
|
-
return
|
|
8824
|
+
return join16(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
8758
8825
|
}
|
|
8759
8826
|
function conversationDir(workspaceId, conversationId) {
|
|
8760
|
-
return
|
|
8827
|
+
return join16(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
8761
8828
|
}
|
|
8762
8829
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
8763
|
-
return
|
|
8830
|
+
return join16(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
8764
8831
|
}
|
|
8765
8832
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
8766
8833
|
const path = pathFor2(workspaceId, conversationId, agentId);
|
|
@@ -8798,9 +8865,9 @@ var init_prepared = __esm({
|
|
|
8798
8865
|
|
|
8799
8866
|
// src/transcript-writer.ts
|
|
8800
8867
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
8801
|
-
import { basename, dirname as
|
|
8868
|
+
import { basename as basename2, dirname as dirname8, join as join17 } from "path";
|
|
8802
8869
|
function transcriptsDir() {
|
|
8803
|
-
return
|
|
8870
|
+
return join17(runnerStateDir(), "transcripts");
|
|
8804
8871
|
}
|
|
8805
8872
|
function fileName(meta) {
|
|
8806
8873
|
const d = new Date(meta.ts);
|
|
@@ -8826,7 +8893,7 @@ function pruneOld(dir2, retain) {
|
|
|
8826
8893
|
const drop = files.sort().slice(0, files.length - retain);
|
|
8827
8894
|
for (const f of drop) {
|
|
8828
8895
|
try {
|
|
8829
|
-
rmSync6(
|
|
8896
|
+
rmSync6(join17(dir2, f), { force: true });
|
|
8830
8897
|
} catch {
|
|
8831
8898
|
}
|
|
8832
8899
|
}
|
|
@@ -8844,7 +8911,7 @@ var init_transcript_writer = __esm({
|
|
|
8844
8911
|
onWarn;
|
|
8845
8912
|
constructor(dir2, meta, onWarn) {
|
|
8846
8913
|
this.onWarn = onWarn;
|
|
8847
|
-
this.path =
|
|
8914
|
+
this.path = join17(dir2, fileName(meta));
|
|
8848
8915
|
try {
|
|
8849
8916
|
mkdirSync8(dir2, { recursive: true });
|
|
8850
8917
|
try {
|
|
@@ -8873,10 +8940,10 @@ var init_transcript_writer = __esm({
|
|
|
8873
8940
|
preserveAnomaly() {
|
|
8874
8941
|
if (this.broken) return;
|
|
8875
8942
|
try {
|
|
8876
|
-
const dir2 =
|
|
8943
|
+
const dir2 = join17(dirname8(this.path), "anomalies");
|
|
8877
8944
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
8878
8945
|
chmodSync4(dir2, 448);
|
|
8879
|
-
const target =
|
|
8946
|
+
const target = join17(dir2, basename2(this.path));
|
|
8880
8947
|
copyFileSync(this.path, target);
|
|
8881
8948
|
chmodSync4(target, 384);
|
|
8882
8949
|
pruneOld(dir2, ANOMALY_RETAIN);
|
|
@@ -8906,6 +8973,7 @@ var TurnCommitter;
|
|
|
8906
8973
|
var init_turn_committer = __esm({
|
|
8907
8974
|
"src/turn-committer.ts"() {
|
|
8908
8975
|
"use strict";
|
|
8976
|
+
init_cabane_command();
|
|
8909
8977
|
init_src();
|
|
8910
8978
|
init_api_error_shape();
|
|
8911
8979
|
TurnCommitter = class {
|
|
@@ -8915,7 +8983,7 @@ var init_turn_committer = __esm({
|
|
|
8915
8983
|
this.onError = (err, hook) => {
|
|
8916
8984
|
deps.log.warn(
|
|
8917
8985
|
{ ...apiErrorLogFields(err), hook },
|
|
8918
|
-
`Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane.
|
|
8986
|
+
`Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane. ${mayNeedUpdate()}`
|
|
8919
8987
|
);
|
|
8920
8988
|
deps.onCommitFailed?.(err);
|
|
8921
8989
|
};
|
|
@@ -9057,7 +9125,7 @@ async function postIncompleteNotice(ctx2, harness, resolution) {
|
|
|
9057
9125
|
} catch (err) {
|
|
9058
9126
|
ctx2.log.warn(
|
|
9059
9127
|
{ ...apiErrorLogFields(err) },
|
|
9060
|
-
|
|
9128
|
+
`Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
|
|
9061
9129
|
);
|
|
9062
9130
|
return false;
|
|
9063
9131
|
}
|
|
@@ -9119,6 +9187,7 @@ var init_turn_runtime_integrity = __esm({
|
|
|
9119
9187
|
"src/turn-runtime-integrity.ts"() {
|
|
9120
9188
|
"use strict";
|
|
9121
9189
|
init_api_error_shape();
|
|
9190
|
+
init_cabane_command();
|
|
9122
9191
|
init_bundled_binaries();
|
|
9123
9192
|
}
|
|
9124
9193
|
});
|
|
@@ -9167,6 +9236,7 @@ var init_turn_execution = __esm({
|
|
|
9167
9236
|
"src/turn-execution.ts"() {
|
|
9168
9237
|
"use strict";
|
|
9169
9238
|
init_term();
|
|
9239
|
+
init_cabane_command();
|
|
9170
9240
|
init_boot_id();
|
|
9171
9241
|
init_src();
|
|
9172
9242
|
init_build_options();
|
|
@@ -9405,7 +9475,7 @@ var init_turn_execution = __esm({
|
|
|
9405
9475
|
} else {
|
|
9406
9476
|
turnLog.warn(
|
|
9407
9477
|
{ ...apiErrorLogFields(err), turnId },
|
|
9408
|
-
|
|
9478
|
+
`Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
|
|
9409
9479
|
);
|
|
9410
9480
|
}
|
|
9411
9481
|
}
|
|
@@ -9883,7 +9953,7 @@ var init_turn_execution = __esm({
|
|
|
9883
9953
|
} catch (postErr) {
|
|
9884
9954
|
turnLog.warn(
|
|
9885
9955
|
{ ...apiErrorLogFields(postErr) },
|
|
9886
|
-
|
|
9956
|
+
`Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
|
|
9887
9957
|
);
|
|
9888
9958
|
const reason = `runtime_unavailable:${err.runtime}`;
|
|
9889
9959
|
throw this.concluded(reason, reason);
|
|
@@ -10092,7 +10162,7 @@ var init_turn_execution = __esm({
|
|
|
10092
10162
|
stateLength: event.state.length,
|
|
10093
10163
|
runtime: turnRuntime
|
|
10094
10164
|
},
|
|
10095
|
-
|
|
10165
|
+
`Couldn't save the agent's memory in Cabane. The next turn will start a new session. ${mayNeedUpdate()}`
|
|
10096
10166
|
);
|
|
10097
10167
|
} else {
|
|
10098
10168
|
turnLog.warn(
|
|
@@ -10256,7 +10326,7 @@ var init_turn_execution = __esm({
|
|
|
10256
10326
|
} else {
|
|
10257
10327
|
turnLog.warn(
|
|
10258
10328
|
{ ...apiErrorLogFields(err) },
|
|
10259
|
-
|
|
10329
|
+
`Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
|
|
10260
10330
|
);
|
|
10261
10331
|
}
|
|
10262
10332
|
}
|
|
@@ -10310,7 +10380,7 @@ var init_turn_execution = __esm({
|
|
|
10310
10380
|
// src/dispatcher.ts
|
|
10311
10381
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10312
10382
|
import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
10313
|
-
import { join as
|
|
10383
|
+
import { join as join18 } from "path";
|
|
10314
10384
|
function checkoutState(cwd) {
|
|
10315
10385
|
if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
|
|
10316
10386
|
if (!existsSync11(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
|
|
@@ -10323,7 +10393,7 @@ function checkoutState(cwd) {
|
|
|
10323
10393
|
if (entries.length === 0) {
|
|
10324
10394
|
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
10325
10395
|
}
|
|
10326
|
-
const gitPath =
|
|
10396
|
+
const gitPath = join18(cwd, ".git");
|
|
10327
10397
|
if (!existsSync11(gitPath)) return { ok: true, reason: "usable" };
|
|
10328
10398
|
let stat;
|
|
10329
10399
|
try {
|
|
@@ -10331,7 +10401,7 @@ function checkoutState(cwd) {
|
|
|
10331
10401
|
} catch (error) {
|
|
10332
10402
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
10333
10403
|
}
|
|
10334
|
-
if (stat.isDirectory() && !existsSync11(
|
|
10404
|
+
if (stat.isDirectory() && !existsSync11(join18(gitPath, "HEAD")))
|
|
10335
10405
|
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
10336
10406
|
return { ok: true, reason: "usable" };
|
|
10337
10407
|
}
|
|
@@ -10703,15 +10773,15 @@ import {
|
|
|
10703
10773
|
mkdirSync as mkdirSync11,
|
|
10704
10774
|
mkdtempSync,
|
|
10705
10775
|
readFileSync as readFileSync10,
|
|
10706
|
-
realpathSync as
|
|
10776
|
+
realpathSync as realpathSync5,
|
|
10707
10777
|
rmSync as rmSync10,
|
|
10708
10778
|
writeFileSync as writeFileSync7
|
|
10709
10779
|
} from "fs";
|
|
10710
10780
|
import { createRequire as createRequire2 } from "module";
|
|
10711
10781
|
import { isIP } from "net";
|
|
10712
10782
|
import { homedir as homedir6, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
10713
|
-
import { dirname as
|
|
10714
|
-
import { fileURLToPath as
|
|
10783
|
+
import { dirname as dirname10, join as join22, sep as sep4 } from "path";
|
|
10784
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
10715
10785
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
10716
10786
|
function accountHome() {
|
|
10717
10787
|
try {
|
|
@@ -10742,23 +10812,23 @@ async function resolvedAddresses(baseUrl) {
|
|
|
10742
10812
|
}
|
|
10743
10813
|
}
|
|
10744
10814
|
function companionPackageRoot(modulePath) {
|
|
10745
|
-
for (let dir2 =
|
|
10815
|
+
for (let dir2 = dirname10(modulePath); ; dir2 = dirname10(dir2)) {
|
|
10746
10816
|
try {
|
|
10747
|
-
const manifest = JSON.parse(readFileSync10(
|
|
10817
|
+
const manifest = JSON.parse(readFileSync10(join22(dir2, "package.json"), "utf8"));
|
|
10748
10818
|
if (manifest.name === "@cabane/companion") return dir2;
|
|
10749
10819
|
} catch {
|
|
10750
10820
|
}
|
|
10751
|
-
if (
|
|
10821
|
+
if (dirname10(dir2) === dir2) return null;
|
|
10752
10822
|
}
|
|
10753
10823
|
}
|
|
10754
10824
|
function harnessRuntimePaths() {
|
|
10755
|
-
const paths = [
|
|
10756
|
-
const root = companionPackageRoot(
|
|
10825
|
+
const paths = [dirname10(dirname10(process.execPath))];
|
|
10826
|
+
const root = companionPackageRoot(fileURLToPath6(import.meta.url));
|
|
10757
10827
|
if (root) paths.push(root);
|
|
10758
10828
|
try {
|
|
10759
10829
|
const require2 = createRequire2(import.meta.url);
|
|
10760
10830
|
paths.push(
|
|
10761
|
-
|
|
10831
|
+
dirname10(realpathSync5(require2.resolve("@anthropic-ai/sandbox-runtime/package.json")))
|
|
10762
10832
|
);
|
|
10763
10833
|
} catch {
|
|
10764
10834
|
}
|
|
@@ -10766,7 +10836,7 @@ function harnessRuntimePaths() {
|
|
|
10766
10836
|
const bundled = resolveBundledBinary(runtime).path;
|
|
10767
10837
|
if (bundled) {
|
|
10768
10838
|
try {
|
|
10769
|
-
paths.push(
|
|
10839
|
+
paths.push(dirname10(realpathSync5(bundled)));
|
|
10770
10840
|
} catch {
|
|
10771
10841
|
}
|
|
10772
10842
|
}
|
|
@@ -10777,9 +10847,9 @@ function ownAddresses() {
|
|
|
10777
10847
|
return Object.values(networkInterfaces()).flat().flatMap((i) => i ? [i.address] : []);
|
|
10778
10848
|
}
|
|
10779
10849
|
function storeWalkRoots(homes, cabaneDir2) {
|
|
10780
|
-
const companionHome =
|
|
10781
|
-
const parent =
|
|
10782
|
-
const beside = homes.includes(companionHome) ||
|
|
10850
|
+
const companionHome = dirname10(cabaneDir2);
|
|
10851
|
+
const parent = dirname10(companionHome);
|
|
10852
|
+
const beside = homes.includes(companionHome) || dirname10(parent) === parent ? [] : [parent];
|
|
10783
10853
|
const candidates = [.../* @__PURE__ */ new Set([...homes, ...beside])];
|
|
10784
10854
|
return candidates.filter(
|
|
10785
10855
|
(root) => !candidates.some((other) => other !== root && isAtOrUnder(root, other))
|
|
@@ -10817,19 +10887,19 @@ async function walkCompanionStores(roots, opts = {}) {
|
|
|
10817
10887
|
}
|
|
10818
10888
|
function companionStoreDenies(input) {
|
|
10819
10889
|
const { homes, cabaneDir: cabaneDir2, foundStores } = input;
|
|
10820
|
-
const parent =
|
|
10890
|
+
const parent = dirname10(dirname10(cabaneDir2));
|
|
10821
10891
|
const always = [
|
|
10822
10892
|
cabaneDir2,
|
|
10823
10893
|
...homes.flatMap((home) => [
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10894
|
+
join22(home, ".cabane"),
|
|
10895
|
+
join22(home, "*", ".cabane"),
|
|
10896
|
+
join22(home, "*", "*", ".cabane")
|
|
10827
10897
|
]),
|
|
10828
10898
|
// A glob anchored at the root has no directory to start from.
|
|
10829
|
-
...
|
|
10899
|
+
...dirname10(parent) === parent ? [] : [join22(parent, "*", ".cabane")]
|
|
10830
10900
|
];
|
|
10831
|
-
const companionHome =
|
|
10832
|
-
const otherLogins = foundStores.map((store) =>
|
|
10901
|
+
const companionHome = dirname10(cabaneDir2);
|
|
10902
|
+
const otherLogins = foundStores.map((store) => dirname10(store)).filter((home) => home !== companionHome).flatMap((home) => [join22(home, ".claude"), join22(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
|
|
10833
10903
|
return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
|
|
10834
10904
|
}
|
|
10835
10905
|
function companionStoreWatch(log) {
|
|
@@ -10846,7 +10916,7 @@ function isAtOrUnder(path, dir2) {
|
|
|
10846
10916
|
}
|
|
10847
10917
|
function expandHome(path, home) {
|
|
10848
10918
|
if (path === "~") return home;
|
|
10849
|
-
return path.startsWith("~/") ?
|
|
10919
|
+
return path.startsWith("~/") ? join22(home, path.slice(2)) : path;
|
|
10850
10920
|
}
|
|
10851
10921
|
function stripPort(pattern) {
|
|
10852
10922
|
return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
|
|
@@ -10864,8 +10934,8 @@ function patternsOverlap(a, b) {
|
|
|
10864
10934
|
}
|
|
10865
10935
|
function harnessSandboxConfig(input) {
|
|
10866
10936
|
const { policy } = input;
|
|
10867
|
-
const claudeDir = input.claudeConfigDir ??
|
|
10868
|
-
const codexDir = input.codexHome ??
|
|
10937
|
+
const claudeDir = input.claudeConfigDir ?? join22(input.home, ".claude");
|
|
10938
|
+
const codexDir = input.codexHome ?? join22(input.home, ".codex");
|
|
10869
10939
|
const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
|
|
10870
10940
|
const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
|
|
10871
10941
|
const cabane2 = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
|
|
@@ -10882,7 +10952,7 @@ function harnessSandboxConfig(input) {
|
|
|
10882
10952
|
foundStores: input.foundStores,
|
|
10883
10953
|
ownState: [claudeDir, codexDir]
|
|
10884
10954
|
}),
|
|
10885
|
-
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) =>
|
|
10955
|
+
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join22(home, p))),
|
|
10886
10956
|
...expand(policy.filesystem?.denyRead)
|
|
10887
10957
|
];
|
|
10888
10958
|
const strict = policy.filesystem?.mode === "strict";
|
|
@@ -10890,7 +10960,7 @@ function harnessSandboxConfig(input) {
|
|
|
10890
10960
|
const allowRead = strict ? [
|
|
10891
10961
|
...input.cwd ? [input.cwd] : [],
|
|
10892
10962
|
claudeDir,
|
|
10893
|
-
|
|
10963
|
+
join22(input.home, ".claude.json"),
|
|
10894
10964
|
codexDir,
|
|
10895
10965
|
...input.runtimePaths ?? [],
|
|
10896
10966
|
...expand(policy.filesystem?.allowRead)
|
|
@@ -10930,7 +11000,7 @@ function harnessSandboxConfig(input) {
|
|
|
10930
11000
|
...input.cwd ? [input.cwd] : [],
|
|
10931
11001
|
input.tmp,
|
|
10932
11002
|
claudeDir,
|
|
10933
|
-
|
|
11003
|
+
join22(input.home, ".claude.json"),
|
|
10934
11004
|
codexDir,
|
|
10935
11005
|
...expand(policy.filesystem?.allowWrite)
|
|
10936
11006
|
],
|
|
@@ -11032,9 +11102,9 @@ async function prepareHarnessSandbox(input) {
|
|
|
11032
11102
|
} finally {
|
|
11033
11103
|
for (const name of held) delete process.env[name];
|
|
11034
11104
|
}
|
|
11035
|
-
mkdirSync11(
|
|
11036
|
-
const dir2 = mkdtempSync(
|
|
11037
|
-
const script =
|
|
11105
|
+
mkdirSync11(join22(cabaneDir(), "sandbox"), { recursive: true });
|
|
11106
|
+
const dir2 = mkdtempSync(join22(cabaneDir(), "sandbox", "turn-"));
|
|
11107
|
+
const script = join22(dir2, "exec-sandboxed");
|
|
11038
11108
|
writeFileSync7(
|
|
11039
11109
|
script,
|
|
11040
11110
|
[
|
|
@@ -11050,7 +11120,7 @@ async function prepareHarnessSandbox(input) {
|
|
|
11050
11120
|
return {
|
|
11051
11121
|
wrap: (command, args) => ({ command: script, args: [command, ...args] }),
|
|
11052
11122
|
executableFor: (real) => {
|
|
11053
|
-
const shim =
|
|
11123
|
+
const shim = join22(dir2, `exec-${++shims}`);
|
|
11054
11124
|
writeFileSync7(shim, `#!/bin/bash
|
|
11055
11125
|
exec ${shellQuote(script)} ${shellQuote(real)} "$@"
|
|
11056
11126
|
`);
|
|
@@ -11143,7 +11213,7 @@ import {
|
|
|
11143
11213
|
rmSync as rmSync11,
|
|
11144
11214
|
writeFileSync as writeFileSync8
|
|
11145
11215
|
} from "fs";
|
|
11146
|
-
import { join as
|
|
11216
|
+
import { join as join23 } from "path";
|
|
11147
11217
|
var MAX_ENTRIES, MAX_AGE_MS, Outbox;
|
|
11148
11218
|
var init_outbox = __esm({
|
|
11149
11219
|
"src/outbox.ts"() {
|
|
@@ -11161,10 +11231,10 @@ var init_outbox = __esm({
|
|
|
11161
11231
|
// Resolved lazily (per call) so tests that swap HOME between
|
|
11162
11232
|
// cases route writes at the right tmpdir.
|
|
11163
11233
|
dir() {
|
|
11164
|
-
return
|
|
11234
|
+
return join23(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
11165
11235
|
}
|
|
11166
11236
|
fileFor(turnId, seq) {
|
|
11167
|
-
return
|
|
11237
|
+
return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
11168
11238
|
}
|
|
11169
11239
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
11170
11240
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -11206,7 +11276,7 @@ var init_outbox = __esm({
|
|
|
11206
11276
|
const entries = [];
|
|
11207
11277
|
for (const name of names) {
|
|
11208
11278
|
if (!name.endsWith(".json")) continue;
|
|
11209
|
-
const full =
|
|
11279
|
+
const full = join23(dir2, name);
|
|
11210
11280
|
try {
|
|
11211
11281
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
11212
11282
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -11346,7 +11416,7 @@ var init_image_manifest = __esm({
|
|
|
11346
11416
|
|
|
11347
11417
|
// src/runner/exec-shim.ts
|
|
11348
11418
|
import { chmodSync as chmodSync8, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "fs";
|
|
11349
|
-
import { join as
|
|
11419
|
+
import { join as join32 } from "path";
|
|
11350
11420
|
function shellQuote2(value) {
|
|
11351
11421
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11352
11422
|
}
|
|
@@ -11356,10 +11426,10 @@ function createShimDir() {
|
|
|
11356
11426
|
return {
|
|
11357
11427
|
write(argv) {
|
|
11358
11428
|
if (!dir2) {
|
|
11359
|
-
mkdirSync20(
|
|
11360
|
-
dir2 = mkdtempSync2(
|
|
11429
|
+
mkdirSync20(join32(cabaneDir(), "runner"), { recursive: true });
|
|
11430
|
+
dir2 = mkdtempSync2(join32(cabaneDir(), "runner", "turn-"));
|
|
11361
11431
|
}
|
|
11362
|
-
const path =
|
|
11432
|
+
const path = join32(dir2, `exec-${++count}`);
|
|
11363
11433
|
writeFileSync13(path, `#!/bin/sh
|
|
11364
11434
|
exec ${argv.map(shellQuote2).join(" ")} "$@"
|
|
11365
11435
|
`);
|
|
@@ -11716,13 +11786,13 @@ init_config();
|
|
|
11716
11786
|
import { createHash } from "crypto";
|
|
11717
11787
|
import { chmodSync as chmodSync2, existsSync as existsSync3, rmSync as rmSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
11718
11788
|
import { createServer, connect } from "net";
|
|
11719
|
-
import { join as
|
|
11789
|
+
import { join as join6 } from "path";
|
|
11720
11790
|
|
|
11721
11791
|
// src/harness-copy.ts
|
|
11722
11792
|
init_bundled_binaries();
|
|
11723
11793
|
init_cabane_command();
|
|
11724
11794
|
import { homedir as homedir5 } from "os";
|
|
11725
|
-
import { join as
|
|
11795
|
+
import { join as join5 } from "path";
|
|
11726
11796
|
|
|
11727
11797
|
// src/harness-status.ts
|
|
11728
11798
|
init_bundled_binaries();
|
|
@@ -11883,7 +11953,12 @@ function runtimeIssuesFor(gates) {
|
|
|
11883
11953
|
for (const name of ["claude-code", "codex"]) {
|
|
11884
11954
|
const gate = name === "codex" ? gates.codex : gates.claudeCode;
|
|
11885
11955
|
if (!gate.intended || gate.bundled === "present") continue;
|
|
11886
|
-
issues.push({
|
|
11956
|
+
issues.push({
|
|
11957
|
+
name,
|
|
11958
|
+
reason: gate.bundled,
|
|
11959
|
+
install: gates.install,
|
|
11960
|
+
...gates.managed ? { managed: true } : {}
|
|
11961
|
+
});
|
|
11887
11962
|
}
|
|
11888
11963
|
return issues;
|
|
11889
11964
|
}
|
|
@@ -12152,16 +12227,16 @@ function failureText(runtime, reason) {
|
|
|
12152
12227
|
case "timeout":
|
|
12153
12228
|
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
12154
12229
|
case "spawn":
|
|
12155
|
-
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${
|
|
12230
|
+
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
12156
12231
|
case "config":
|
|
12157
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${
|
|
12232
|
+
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${join5(homedir5(), ".codex", "config.toml").replace(homedir5(), "~")})` : ""}. Fix it, then try again.`;
|
|
12158
12233
|
case "unreadable":
|
|
12159
12234
|
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
12160
12235
|
}
|
|
12161
12236
|
}
|
|
12162
12237
|
function bundleMissingText(runtime) {
|
|
12163
12238
|
const label = HARNESS_LABELS[runtime];
|
|
12164
|
-
return `This Cabane install is missing its bundled ${label}. Repair it with ${
|
|
12239
|
+
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
12165
12240
|
}
|
|
12166
12241
|
function unusableReason(runtime, check) {
|
|
12167
12242
|
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
@@ -12235,6 +12310,9 @@ function connectLines(outcome, opts = {}) {
|
|
|
12235
12310
|
]
|
|
12236
12311
|
};
|
|
12237
12312
|
}
|
|
12313
|
+
function spaced(sentence) {
|
|
12314
|
+
return sentence ? ` ${sentence}` : "";
|
|
12315
|
+
}
|
|
12238
12316
|
|
|
12239
12317
|
// src/control-socket.ts
|
|
12240
12318
|
var CONTROL_TIMEOUT_MS = 1e3;
|
|
@@ -12245,11 +12323,11 @@ function controlSocketPath() {
|
|
|
12245
12323
|
if (process.platform === "win32") {
|
|
12246
12324
|
return `\\\\.\\pipe\\cabane-companion-${key}`;
|
|
12247
12325
|
}
|
|
12248
|
-
const besideRuntime =
|
|
12326
|
+
const besideRuntime = join6(dir2, "companion.sock");
|
|
12249
12327
|
if (Buffer.byteLength(besideRuntime) <= MAX_UNIX_SOCKET_PATH_BYTES) return besideRuntime;
|
|
12250
12328
|
const filename = `cabane-companion-${key}.sock`;
|
|
12251
|
-
const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync3(process.env.XDG_RUNTIME_DIR) ?
|
|
12252
|
-
return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath :
|
|
12329
|
+
const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync3(process.env.XDG_RUNTIME_DIR) ? join6(process.env.XDG_RUNTIME_DIR, filename) : null;
|
|
12330
|
+
return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath : join6("/tmp", filename);
|
|
12253
12331
|
}
|
|
12254
12332
|
async function startControlServer(handlers) {
|
|
12255
12333
|
const path = controlSocketPath();
|
|
@@ -12424,10 +12502,10 @@ import {
|
|
|
12424
12502
|
openSync,
|
|
12425
12503
|
closeSync
|
|
12426
12504
|
} from "fs";
|
|
12427
|
-
import { join as
|
|
12505
|
+
import { join as join7 } from "path";
|
|
12428
12506
|
var PROBE_TIMEOUT_MS2 = 1e3;
|
|
12429
12507
|
function runtimePath() {
|
|
12430
|
-
return
|
|
12508
|
+
return join7(cabaneDir(), "runtime.json");
|
|
12431
12509
|
}
|
|
12432
12510
|
function serialize(state) {
|
|
12433
12511
|
return JSON.stringify(state, null, 2) + "\n";
|
|
@@ -12642,10 +12720,10 @@ import {
|
|
|
12642
12720
|
rmSync as rmSync4,
|
|
12643
12721
|
writeFileSync as writeFileSync3
|
|
12644
12722
|
} from "fs";
|
|
12645
|
-
import { dirname as
|
|
12723
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
12646
12724
|
import { z as z3 } from "zod";
|
|
12647
12725
|
function credentialsPath() {
|
|
12648
|
-
return
|
|
12726
|
+
return join8(cabaneDir(), "credentials.json");
|
|
12649
12727
|
}
|
|
12650
12728
|
var credentialStoreSchema = z3.record(z3.string(), z3.string());
|
|
12651
12729
|
function load() {
|
|
@@ -12667,7 +12745,7 @@ function load() {
|
|
|
12667
12745
|
}
|
|
12668
12746
|
function save(map) {
|
|
12669
12747
|
const path = credentialsPath();
|
|
12670
|
-
mkdirSync4(
|
|
12748
|
+
mkdirSync4(dirname5(path), { recursive: true });
|
|
12671
12749
|
try {
|
|
12672
12750
|
chmodSync3(cabaneDir(), 448);
|
|
12673
12751
|
} catch {
|
|
@@ -12962,11 +13040,11 @@ import {
|
|
|
12962
13040
|
renameSync as renameSync3,
|
|
12963
13041
|
statSync as statSync2
|
|
12964
13042
|
} from "fs";
|
|
12965
|
-
import { dirname as
|
|
13043
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
12966
13044
|
import { stripVTControlCharacters } from "util";
|
|
12967
13045
|
import pino from "pino";
|
|
12968
13046
|
function companionLogPath() {
|
|
12969
|
-
return
|
|
13047
|
+
return join9(cabaneDir(), "companion.log");
|
|
12970
13048
|
}
|
|
12971
13049
|
function humanText(value) {
|
|
12972
13050
|
return stripVTControlCharacters(String(value ?? "")).replace(
|
|
@@ -13080,7 +13158,7 @@ function configureLogger(log, config) {
|
|
|
13080
13158
|
}
|
|
13081
13159
|
function createLogger(destinations = {}, options = {}) {
|
|
13082
13160
|
const path = companionLogPath();
|
|
13083
|
-
if (!destinations.file) mkdirSync5(
|
|
13161
|
+
if (!destinations.file) mkdirSync5(dirname6(path), { recursive: true });
|
|
13084
13162
|
const file = destinations.file ?? new RotatingLog(path);
|
|
13085
13163
|
const state = { format: options.logFormat ?? "human" };
|
|
13086
13164
|
const streams = [];
|
|
@@ -13133,15 +13211,15 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
13133
13211
|
|
|
13134
13212
|
// src/dashboard/server.ts
|
|
13135
13213
|
init_errors();
|
|
13136
|
-
import { dirname as
|
|
13137
|
-
import { fileURLToPath as
|
|
13214
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
13215
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13138
13216
|
import { serve } from "@hono/node-server";
|
|
13139
13217
|
import { Hono } from "hono";
|
|
13140
13218
|
|
|
13141
13219
|
// src/dashboard/routes.ts
|
|
13142
13220
|
import { openSync as openSync3, readSync, closeSync as closeSync2, fstatSync, existsSync as existsSync6 } from "fs";
|
|
13143
13221
|
import { readFile } from "fs/promises";
|
|
13144
|
-
import { extname, join as
|
|
13222
|
+
import { extname, join as join10, normalize } from "path";
|
|
13145
13223
|
import { streamSSE } from "hono/streaming";
|
|
13146
13224
|
|
|
13147
13225
|
// src/state.ts
|
|
@@ -13390,13 +13468,13 @@ var CONTENT_TYPES = {
|
|
|
13390
13468
|
function registerRoutes(app, deps) {
|
|
13391
13469
|
const { supervisor, hub, staticDir } = deps;
|
|
13392
13470
|
app.get("/", async (c) => {
|
|
13393
|
-
const html = await readFile(
|
|
13471
|
+
const html = await readFile(join10(staticDir, "index.html"), "utf8");
|
|
13394
13472
|
return c.html(html);
|
|
13395
13473
|
});
|
|
13396
13474
|
app.get("/static/:file", async (c) => {
|
|
13397
13475
|
const file = c.req.param("file");
|
|
13398
13476
|
const safe2 = normalize(file).replace(/^(\.\.[/\\])+/, "");
|
|
13399
|
-
const full =
|
|
13477
|
+
const full = join10(staticDir, safe2);
|
|
13400
13478
|
if (!full.startsWith(staticDir) || !existsSync6(full)) return c.notFound();
|
|
13401
13479
|
const body = await readFile(full);
|
|
13402
13480
|
const type = CONTENT_TYPES[extname(full).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -13608,7 +13686,7 @@ function isAddrInUse(err) {
|
|
|
13608
13686
|
return Boolean(err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE");
|
|
13609
13687
|
}
|
|
13610
13688
|
function resolveStaticDir() {
|
|
13611
|
-
return
|
|
13689
|
+
return join11(dirname7(fileURLToPath4(import.meta.url)), "static");
|
|
13612
13690
|
}
|
|
13613
13691
|
|
|
13614
13692
|
// src/supervisor.ts
|
|
@@ -13617,6 +13695,7 @@ import { hostname as hostname2 } from "os";
|
|
|
13617
13695
|
|
|
13618
13696
|
// src/log-copy.ts
|
|
13619
13697
|
init_failure();
|
|
13698
|
+
init_bundled_binaries();
|
|
13620
13699
|
function turnFailureCopy(reason, runtime) {
|
|
13621
13700
|
const failure = decodeFailureReason(reason);
|
|
13622
13701
|
if (failure?.kind === "auth_expired") {
|
|
@@ -13635,7 +13714,7 @@ function turnFailureCopy(reason, runtime) {
|
|
|
13635
13714
|
if (reason?.startsWith("missing_secret:"))
|
|
13636
13715
|
return `A required secret is missing: ${humanText(reason.slice(15))}. Add it to ~/.cabane/secrets.json, then reply in Cabane to try again.`;
|
|
13637
13716
|
if (reason?.startsWith("runtime_incomplete:"))
|
|
13638
|
-
return
|
|
13717
|
+
return `The agent installation is incomplete. ${repairAction()} Then restart the companion.`;
|
|
13639
13718
|
if (/^(runtime|codex|opencode)_unavailable:/.test(reason ?? ""))
|
|
13640
13719
|
return "The agent is unavailable on this machine. Enable it here or choose another connector in Cabane.";
|
|
13641
13720
|
if (reason?.startsWith("model_unavailable:"))
|
|
@@ -13775,13 +13854,13 @@ init_config();
|
|
|
13775
13854
|
// src/dispatch-dedupe.ts
|
|
13776
13855
|
init_config();
|
|
13777
13856
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, renameSync as renameSync4, existsSync as existsSync7 } from "fs";
|
|
13778
|
-
import { join as
|
|
13857
|
+
import { join as join12 } from "path";
|
|
13779
13858
|
var MAX_IDS = 256;
|
|
13780
13859
|
function dir(log) {
|
|
13781
|
-
return
|
|
13860
|
+
return join12(cabaneDir(), log);
|
|
13782
13861
|
}
|
|
13783
13862
|
function pathFor(log, workspaceId) {
|
|
13784
|
-
return
|
|
13863
|
+
return join12(dir(log), encodeURIComponent(workspaceId));
|
|
13785
13864
|
}
|
|
13786
13865
|
function readIds(log, workspaceId) {
|
|
13787
13866
|
const path = pathFor(log, workspaceId);
|
|
@@ -13818,10 +13897,10 @@ function markCompleted(workspaceId, eventId) {
|
|
|
13818
13897
|
}
|
|
13819
13898
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
13820
13899
|
function resumeDir() {
|
|
13821
|
-
return
|
|
13900
|
+
return join12(cabaneDir(), "resume-attempts");
|
|
13822
13901
|
}
|
|
13823
13902
|
function resumePathFor(workspaceId) {
|
|
13824
|
-
return
|
|
13903
|
+
return join12(resumeDir(), encodeURIComponent(workspaceId));
|
|
13825
13904
|
}
|
|
13826
13905
|
function readResumeCounts(workspaceId) {
|
|
13827
13906
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13857,10 +13936,10 @@ function bumpResumeAttempt(workspaceId, eventId) {
|
|
|
13857
13936
|
return next;
|
|
13858
13937
|
}
|
|
13859
13938
|
function turnDir() {
|
|
13860
|
-
return
|
|
13939
|
+
return join12(cabaneDir(), "turns");
|
|
13861
13940
|
}
|
|
13862
13941
|
function turnPathFor(workspaceId) {
|
|
13863
|
-
return
|
|
13942
|
+
return join12(turnDir(), encodeURIComponent(workspaceId));
|
|
13864
13943
|
}
|
|
13865
13944
|
var TURN_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13866
13945
|
function readTurnIds(workspaceId) {
|
|
@@ -13912,6 +13991,7 @@ function noResume() {
|
|
|
13912
13991
|
|
|
13913
13992
|
// src/supervisor.ts
|
|
13914
13993
|
init_bundled_binaries();
|
|
13994
|
+
init_install_kind();
|
|
13915
13995
|
|
|
13916
13996
|
// src/connector-health.ts
|
|
13917
13997
|
init_src();
|
|
@@ -14093,7 +14173,7 @@ import {
|
|
|
14093
14173
|
statSync as statSync4,
|
|
14094
14174
|
writeFileSync as writeFileSync6
|
|
14095
14175
|
} from "fs";
|
|
14096
|
-
import { join as
|
|
14176
|
+
import { join as join21 } from "path";
|
|
14097
14177
|
import { Writable } from "stream";
|
|
14098
14178
|
|
|
14099
14179
|
// src/providers/docker-cli.ts
|
|
@@ -14166,7 +14246,7 @@ import {
|
|
|
14166
14246
|
renameSync as renameSync5,
|
|
14167
14247
|
rmSync as rmSync8
|
|
14168
14248
|
} from "fs";
|
|
14169
|
-
import { join as
|
|
14249
|
+
import { join as join19 } from "path";
|
|
14170
14250
|
function routes(t) {
|
|
14171
14251
|
const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
|
|
14172
14252
|
const agent = `${conv}/participants/agents/${t.agentId}`;
|
|
@@ -14212,7 +14292,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14212
14292
|
const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
|
|
14213
14293
|
let entry = {};
|
|
14214
14294
|
try {
|
|
14215
|
-
entry = JSON.parse(readFileSync8(
|
|
14295
|
+
entry = JSON.parse(readFileSync8(join19(from, name), "utf8"));
|
|
14216
14296
|
} catch {
|
|
14217
14297
|
}
|
|
14218
14298
|
return { name, entry };
|
|
@@ -14225,7 +14305,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14225
14305
|
let published = 0;
|
|
14226
14306
|
for (const { name } of staged) {
|
|
14227
14307
|
try {
|
|
14228
|
-
move(
|
|
14308
|
+
move(join19(from, name), join19(to, name));
|
|
14229
14309
|
} catch {
|
|
14230
14310
|
return { published, complete: false };
|
|
14231
14311
|
}
|
|
@@ -14237,9 +14317,9 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14237
14317
|
|
|
14238
14318
|
// src/providers/runner-base.ts
|
|
14239
14319
|
import { createHash as createHash3 } from "crypto";
|
|
14240
|
-
import { existsSync as existsSync13, realpathSync as
|
|
14241
|
-
import { dirname as
|
|
14242
|
-
import { fileURLToPath as
|
|
14320
|
+
import { existsSync as existsSync13, realpathSync as realpathSync4 } from "fs";
|
|
14321
|
+
import { dirname as dirname9, join as join20, sep as sep3 } from "path";
|
|
14322
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14243
14323
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
14244
14324
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
14245
14325
|
RUN apt-get update \\
|
|
@@ -14283,17 +14363,17 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
14283
14363
|
}
|
|
14284
14364
|
return true;
|
|
14285
14365
|
}
|
|
14286
|
-
function ownInstall(moduleFile =
|
|
14287
|
-
let dir2 =
|
|
14366
|
+
function ownInstall(moduleFile = fileURLToPath5(import.meta.url)) {
|
|
14367
|
+
let dir2 = dirname9(moduleFile);
|
|
14288
14368
|
for (let i = 0; i < 4; i++) {
|
|
14289
|
-
if (existsSync13(
|
|
14290
|
-
dir2 =
|
|
14369
|
+
if (existsSync13(join20(dir2, "dist", "cli.js")) && existsSync13(join20(dir2, "package.json"))) break;
|
|
14370
|
+
dir2 = dirname9(dir2);
|
|
14291
14371
|
}
|
|
14292
|
-
if (!existsSync13(
|
|
14293
|
-
const sdk =
|
|
14372
|
+
if (!existsSync13(join20(dir2, "dist", "cli.js"))) return null;
|
|
14373
|
+
const sdk = join20(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
14294
14374
|
if (!existsSync13(sdk)) return null;
|
|
14295
|
-
const root =
|
|
14296
|
-
return
|
|
14375
|
+
const root = realpathSync4(dir2);
|
|
14376
|
+
return realpathSync4(sdk).startsWith(root + sep3) ? root : null;
|
|
14297
14377
|
}
|
|
14298
14378
|
function companionSource(input) {
|
|
14299
14379
|
if (input.configured) {
|
|
@@ -14429,39 +14509,39 @@ function networkCreateError(stderr) {
|
|
|
14429
14509
|
}
|
|
14430
14510
|
function sessionArchivePath(deviceDir, attemptId) {
|
|
14431
14511
|
if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
|
|
14432
|
-
return
|
|
14512
|
+
return join21(deviceDir, "attempts", attemptId, "session.tar");
|
|
14433
14513
|
}
|
|
14434
14514
|
function pruneOtherAttempts(deviceDir, pair2, keep) {
|
|
14435
|
-
const root =
|
|
14515
|
+
const root = join21(deviceDir, "attempts");
|
|
14436
14516
|
if (!existsSync14(root)) return;
|
|
14437
14517
|
for (const id of readdirSync4(root)) {
|
|
14438
14518
|
if (id === keep || !UUID_RE.test(id)) continue;
|
|
14439
14519
|
let pairOf = "";
|
|
14440
14520
|
try {
|
|
14441
|
-
pairOf = readFileSync9(
|
|
14521
|
+
pairOf = readFileSync9(join21(root, id, "pair"), "utf8");
|
|
14442
14522
|
} catch {
|
|
14443
14523
|
continue;
|
|
14444
14524
|
}
|
|
14445
14525
|
if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
|
|
14446
|
-
rmSync9(
|
|
14526
|
+
rmSync9(join21(root, id), { recursive: true, force: true });
|
|
14447
14527
|
}
|
|
14448
14528
|
}
|
|
14449
14529
|
}
|
|
14450
14530
|
var IDLE_SESSION_ARCHIVE_MS = 30 * 24 * 60 * 6e4;
|
|
14451
14531
|
function pruneIdleSessionArchives(deviceDir, now = Date.now(), maxAgeMs = IDLE_SESSION_ARCHIVE_MS) {
|
|
14452
|
-
const root =
|
|
14532
|
+
const root = join21(deviceDir, "attempts");
|
|
14453
14533
|
if (!existsSync14(root)) return [];
|
|
14454
14534
|
const removed = [];
|
|
14455
14535
|
for (const id of readdirSync4(root)) {
|
|
14456
14536
|
if (!UUID_RE.test(id)) continue;
|
|
14457
14537
|
let kept;
|
|
14458
14538
|
try {
|
|
14459
|
-
kept = statSync4(
|
|
14539
|
+
kept = statSync4(join21(root, id)).mtimeMs;
|
|
14460
14540
|
} catch {
|
|
14461
14541
|
continue;
|
|
14462
14542
|
}
|
|
14463
14543
|
if (now - kept < maxAgeMs) continue;
|
|
14464
|
-
rmSync9(
|
|
14544
|
+
rmSync9(join21(root, id), { recursive: true, force: true });
|
|
14465
14545
|
removed.push(id);
|
|
14466
14546
|
}
|
|
14467
14547
|
return removed;
|
|
@@ -14662,11 +14742,11 @@ function createDockerProvider(opts) {
|
|
|
14662
14742
|
await must(["network", "disconnect", "-f", other, name], "moving the container");
|
|
14663
14743
|
if (others.includes(sharedNetwork)) await retireSharedNetwork();
|
|
14664
14744
|
};
|
|
14665
|
-
const socketRoot = opts.socketRoot ??
|
|
14745
|
+
const socketRoot = opts.socketRoot ?? join21(
|
|
14666
14746
|
process.env.XDG_RUNTIME_DIR && existsSync14(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
|
|
14667
14747
|
`cabane-runners-${opts.deviceKey}`
|
|
14668
14748
|
);
|
|
14669
|
-
const socketDir = (name) =>
|
|
14749
|
+
const socketDir = (name) => join21(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
|
|
14670
14750
|
const create = async (name, attempt) => {
|
|
14671
14751
|
mkdirSync10(socketRoot, { recursive: true, mode: 448 });
|
|
14672
14752
|
mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
|
|
@@ -14808,7 +14888,7 @@ function createDockerProvider(opts) {
|
|
|
14808
14888
|
}
|
|
14809
14889
|
};
|
|
14810
14890
|
const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
|
|
14811
|
-
const pendingPath = (name) =>
|
|
14891
|
+
const pendingPath = (name) => join21(opts.deviceDir, "runners-pending", `${name}.json`);
|
|
14812
14892
|
const readPending = (name) => {
|
|
14813
14893
|
try {
|
|
14814
14894
|
const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
|
|
@@ -14822,12 +14902,12 @@ function createDockerProvider(opts) {
|
|
|
14822
14902
|
rmSync9(pendingPath(name), { force: true });
|
|
14823
14903
|
return;
|
|
14824
14904
|
}
|
|
14825
|
-
mkdirSync10(
|
|
14905
|
+
mkdirSync10(join21(opts.deviceDir, "runners-pending"), { recursive: true });
|
|
14826
14906
|
writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
|
|
14827
14907
|
};
|
|
14828
14908
|
const keepSession = async (name, attempt, attemptId) => {
|
|
14829
14909
|
const archive = sessionArchivePath(opts.deviceDir, attemptId);
|
|
14830
|
-
mkdirSync10(
|
|
14910
|
+
mkdirSync10(join21(archive, ".."), { recursive: true });
|
|
14831
14911
|
const partial = `${archive}.${process.pid}.partial`;
|
|
14832
14912
|
const file = new CappedFile(partial, sessionCap);
|
|
14833
14913
|
const r = await execIn(
|
|
@@ -14844,7 +14924,7 @@ function createDockerProvider(opts) {
|
|
|
14844
14924
|
);
|
|
14845
14925
|
if (r.code === 0 && !file.over) {
|
|
14846
14926
|
renameSync6(partial, archive);
|
|
14847
|
-
writeFileSync6(
|
|
14927
|
+
writeFileSync6(join21(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
|
|
14848
14928
|
pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
|
|
14849
14929
|
await writeMarker(name, attemptId);
|
|
14850
14930
|
log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
|
|
@@ -14858,7 +14938,7 @@ function createDockerProvider(opts) {
|
|
|
14858
14938
|
file.over ? "runners: the session files are over the size cap and weren't kept; the next new container starts the session over" : "runners: couldn't keep the session files; the next new container starts the session over"
|
|
14859
14939
|
);
|
|
14860
14940
|
};
|
|
14861
|
-
const stagingDir = (name) =>
|
|
14941
|
+
const stagingDir = (name) => join21(opts.deviceDir, "runners-pending", `${name}.outbox`);
|
|
14862
14942
|
const ensureRunning = async (name) => {
|
|
14863
14943
|
const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
|
|
14864
14944
|
if (seen.code !== 0) return false;
|
|
@@ -14866,7 +14946,7 @@ function createDockerProvider(opts) {
|
|
|
14866
14946
|
return (await docker.run(["start", name])).code === 0;
|
|
14867
14947
|
};
|
|
14868
14948
|
const dropArchive = (attemptId) => {
|
|
14869
|
-
rmSync9(
|
|
14949
|
+
rmSync9(join21(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
|
|
14870
14950
|
recursive: true,
|
|
14871
14951
|
force: true
|
|
14872
14952
|
});
|
|
@@ -14935,7 +15015,7 @@ function createDockerProvider(opts) {
|
|
|
14935
15015
|
if (seen.stdout.trim().split(" ")[0] !== "true")
|
|
14936
15016
|
await must(["start", name], "starting the container");
|
|
14937
15017
|
const git = await serveGitCredentials({
|
|
14938
|
-
socketPath:
|
|
15018
|
+
socketPath: join21(socketDir(name), "git.sock"),
|
|
14939
15019
|
hosts: opts.gitHosts,
|
|
14940
15020
|
fill: opts.gitFill,
|
|
14941
15021
|
log
|
|
@@ -15084,15 +15164,15 @@ function createDockerProvider(opts) {
|
|
|
15084
15164
|
const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
|
|
15085
15165
|
if (transcripts) {
|
|
15086
15166
|
const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
|
|
15087
|
-
if (kept.length) mkdirSync10(
|
|
15088
|
-
for (const f of kept) writeFileSync6(
|
|
15167
|
+
if (kept.length) mkdirSync10(join21(opts.hostStateDir, "transcripts"), { recursive: true });
|
|
15168
|
+
for (const f of kept) writeFileSync6(join21(opts.hostStateDir, "transcripts", f.name), f.data);
|
|
15089
15169
|
await removeIn(
|
|
15090
15170
|
name,
|
|
15091
15171
|
`${CONTAINER_STATE}/transcripts`,
|
|
15092
15172
|
kept.map((f) => f.name)
|
|
15093
15173
|
);
|
|
15094
15174
|
}
|
|
15095
|
-
const outboxRel =
|
|
15175
|
+
const outboxRel = join21("outbox", encodeURIComponent(attempt.agentId));
|
|
15096
15176
|
const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
|
|
15097
15177
|
let unpublished = false;
|
|
15098
15178
|
if (outbox) {
|
|
@@ -15118,7 +15198,7 @@ function createDockerProvider(opts) {
|
|
|
15118
15198
|
const staged = stagingDir(name);
|
|
15119
15199
|
mkdirSync10(staged, { recursive: true });
|
|
15120
15200
|
writeFileSync6(
|
|
15121
|
-
|
|
15201
|
+
join21(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
|
|
15122
15202
|
`${JSON.stringify(entry)}
|
|
15123
15203
|
`
|
|
15124
15204
|
);
|
|
@@ -15131,7 +15211,7 @@ function createDockerProvider(opts) {
|
|
|
15131
15211
|
if (outbox.complete) {
|
|
15132
15212
|
const published = publishStaged(
|
|
15133
15213
|
stagingDir(name),
|
|
15134
|
-
|
|
15214
|
+
join21(opts.hostStateDir, outboxRel),
|
|
15135
15215
|
opts.moveFile
|
|
15136
15216
|
);
|
|
15137
15217
|
queued = published.published;
|
|
@@ -15433,6 +15513,8 @@ var SseSubscriber = class {
|
|
|
15433
15513
|
opts;
|
|
15434
15514
|
aborted = false;
|
|
15435
15515
|
controller = null;
|
|
15516
|
+
// Ends the reconnect backoff early, so a stop never waits out a sleep of up to 30 s.
|
|
15517
|
+
wake = null;
|
|
15436
15518
|
lastEventId;
|
|
15437
15519
|
// Set after `start()`; resolved when the subscriber loop exits (either
|
|
15438
15520
|
// because `stop()` was called or because an auth failure tore it down).
|
|
@@ -15443,12 +15525,7 @@ var SseSubscriber = class {
|
|
|
15443
15525
|
stop() {
|
|
15444
15526
|
this.aborted = true;
|
|
15445
15527
|
this.controller?.abort();
|
|
15446
|
-
|
|
15447
|
-
// Connect again after a `stop()`, once its loop has exited.
|
|
15448
|
-
async resume() {
|
|
15449
|
-
await this.finished;
|
|
15450
|
-
this.aborted = false;
|
|
15451
|
-
this.start();
|
|
15528
|
+
this.wake?.();
|
|
15452
15529
|
}
|
|
15453
15530
|
setCursor(eventId) {
|
|
15454
15531
|
this.lastEventId = eventId;
|
|
@@ -15461,7 +15538,7 @@ var SseSubscriber = class {
|
|
|
15461
15538
|
backoff = 500;
|
|
15462
15539
|
if (!this.aborted) {
|
|
15463
15540
|
this.opts.log.warn("Lost the connection to Cabane; reconnecting");
|
|
15464
|
-
await
|
|
15541
|
+
await this.backoff(backoff);
|
|
15465
15542
|
}
|
|
15466
15543
|
} catch (err) {
|
|
15467
15544
|
if (this.aborted) return;
|
|
@@ -15481,11 +15558,21 @@ var SseSubscriber = class {
|
|
|
15481
15558
|
},
|
|
15482
15559
|
"Lost the connection to Cabane; reconnecting"
|
|
15483
15560
|
);
|
|
15484
|
-
await
|
|
15561
|
+
await this.backoff(backoff);
|
|
15485
15562
|
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
15486
15563
|
}
|
|
15487
15564
|
}
|
|
15488
15565
|
}
|
|
15566
|
+
backoff(ms) {
|
|
15567
|
+
return new Promise((resolve2) => {
|
|
15568
|
+
const timer = setTimeout(done, ms);
|
|
15569
|
+
this.wake = done;
|
|
15570
|
+
function done() {
|
|
15571
|
+
clearTimeout(timer);
|
|
15572
|
+
resolve2();
|
|
15573
|
+
}
|
|
15574
|
+
});
|
|
15575
|
+
}
|
|
15489
15576
|
async connect() {
|
|
15490
15577
|
const base = this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
|
|
15491
15578
|
const url = `${base}${this.opts.path}`;
|
|
@@ -15532,9 +15619,6 @@ var SseSubscriber = class {
|
|
|
15532
15619
|
}
|
|
15533
15620
|
}
|
|
15534
15621
|
};
|
|
15535
|
-
function sleep4(ms) {
|
|
15536
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
15537
|
-
}
|
|
15538
15622
|
|
|
15539
15623
|
// src/supervisor.ts
|
|
15540
15624
|
init_transcript_writer();
|
|
@@ -15542,19 +15626,19 @@ init_transcript_writer();
|
|
|
15542
15626
|
// src/updater.ts
|
|
15543
15627
|
import { spawn as spawn8 } from "child_process";
|
|
15544
15628
|
import { copyFileSync as copyFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
|
|
15545
|
-
import { dirname as
|
|
15629
|
+
import { dirname as dirname14, join as join27 } from "path";
|
|
15546
15630
|
|
|
15547
15631
|
// src/boot-rule.ts
|
|
15548
15632
|
import {
|
|
15549
15633
|
mkdirSync as mkdirSync13,
|
|
15550
15634
|
readFileSync as readFileSync12,
|
|
15551
|
-
realpathSync as
|
|
15635
|
+
realpathSync as realpathSync6,
|
|
15552
15636
|
renameSync as renameSync8,
|
|
15553
15637
|
rmSync as rmSync12,
|
|
15554
15638
|
symlinkSync,
|
|
15555
15639
|
writeFileSync as writeFileSync9
|
|
15556
15640
|
} from "fs";
|
|
15557
|
-
import { dirname as
|
|
15641
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
15558
15642
|
var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
|
|
15559
15643
|
var MAX_STAGE_ATTEMPTS = 3;
|
|
15560
15644
|
function superseded(pending, ...running) {
|
|
@@ -15583,37 +15667,37 @@ import {
|
|
|
15583
15667
|
mkdirSync as mkdirSync14,
|
|
15584
15668
|
readFileSync as readFileSync13,
|
|
15585
15669
|
readdirSync as readdirSync6,
|
|
15586
|
-
realpathSync as
|
|
15670
|
+
realpathSync as realpathSync7,
|
|
15587
15671
|
renameSync as renameSync9,
|
|
15588
15672
|
rmSync as rmSync13,
|
|
15589
15673
|
symlinkSync as symlinkSync2
|
|
15590
15674
|
} from "fs";
|
|
15591
|
-
import { dirname as
|
|
15592
|
-
import { fileURLToPath as
|
|
15675
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
15676
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
15593
15677
|
function versionsDir() {
|
|
15594
|
-
return
|
|
15678
|
+
return join25(cabaneDir(), "versions");
|
|
15595
15679
|
}
|
|
15596
15680
|
function currentLink() {
|
|
15597
|
-
return
|
|
15681
|
+
return join25(cabaneDir(), "current");
|
|
15598
15682
|
}
|
|
15599
15683
|
function bootGuardPath() {
|
|
15600
|
-
return
|
|
15684
|
+
return join25(cabaneDir(), "boot-guard.mjs");
|
|
15601
15685
|
}
|
|
15602
15686
|
function pinnedNodeDir() {
|
|
15603
|
-
return
|
|
15687
|
+
return join25(cabaneDir(), "node");
|
|
15604
15688
|
}
|
|
15605
15689
|
function pinnedNodePath() {
|
|
15606
|
-
return
|
|
15690
|
+
return join25(pinnedNodeDir(), "bin", "node");
|
|
15607
15691
|
}
|
|
15608
15692
|
function versionPackageRoot(version) {
|
|
15609
|
-
return
|
|
15693
|
+
return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
|
|
15610
15694
|
}
|
|
15611
15695
|
function ownPackageRoot(moduleUrl = import.meta.url) {
|
|
15612
|
-
return
|
|
15696
|
+
return dirname12(dirname12(fileURLToPath7(moduleUrl)));
|
|
15613
15697
|
}
|
|
15614
15698
|
function packageVersion(root) {
|
|
15615
15699
|
try {
|
|
15616
|
-
const pkg2 = JSON.parse(readFileSync13(
|
|
15700
|
+
const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
|
|
15617
15701
|
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
15618
15702
|
} catch {
|
|
15619
15703
|
return null;
|
|
@@ -15621,15 +15705,15 @@ function packageVersion(root) {
|
|
|
15621
15705
|
}
|
|
15622
15706
|
function currentRoot() {
|
|
15623
15707
|
try {
|
|
15624
|
-
const root =
|
|
15625
|
-
return existsSync16(
|
|
15708
|
+
const root = realpathSync7(currentLink());
|
|
15709
|
+
return existsSync16(join25(root, "dist", "cli.js")) ? root : null;
|
|
15626
15710
|
} catch {
|
|
15627
15711
|
return null;
|
|
15628
15712
|
}
|
|
15629
15713
|
}
|
|
15630
15714
|
function pointCurrentAt(root) {
|
|
15631
15715
|
const link = currentLink();
|
|
15632
|
-
mkdirSync14(
|
|
15716
|
+
mkdirSync14(dirname12(link), { recursive: true });
|
|
15633
15717
|
const fresh = `${link}.${process.pid}.${Date.now()}`;
|
|
15634
15718
|
symlinkSync2(root, fresh);
|
|
15635
15719
|
try {
|
|
@@ -15645,7 +15729,7 @@ function pruneVersions(keep) {
|
|
|
15645
15729
|
const removed = [];
|
|
15646
15730
|
for (const name of readdirSync6(dir2)) {
|
|
15647
15731
|
if (keep.includes(name)) continue;
|
|
15648
|
-
rmSync13(
|
|
15732
|
+
rmSync13(join25(dir2, name), { recursive: true, force: true });
|
|
15649
15733
|
removed.push(name);
|
|
15650
15734
|
}
|
|
15651
15735
|
return removed;
|
|
@@ -15665,7 +15749,7 @@ function compareVersions(a, b) {
|
|
|
15665
15749
|
// src/update-state.ts
|
|
15666
15750
|
init_config();
|
|
15667
15751
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
15668
|
-
import { dirname as
|
|
15752
|
+
import { dirname as dirname13, join as join26 } from "path";
|
|
15669
15753
|
import { z as z18 } from "zod";
|
|
15670
15754
|
var resultSchema = z18.object({
|
|
15671
15755
|
kind: z18.enum(["updated", "rolled_back", "failed"]),
|
|
@@ -15689,7 +15773,7 @@ var stateSchema = z18.object({
|
|
|
15689
15773
|
failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
|
|
15690
15774
|
});
|
|
15691
15775
|
function updateStatePath(dir2 = cabaneDir()) {
|
|
15692
|
-
return
|
|
15776
|
+
return join26(dir2, "update.json");
|
|
15693
15777
|
}
|
|
15694
15778
|
function readUpdateState(path = updateStatePath()) {
|
|
15695
15779
|
try {
|
|
@@ -15700,7 +15784,7 @@ function readUpdateState(path = updateStatePath()) {
|
|
|
15700
15784
|
}
|
|
15701
15785
|
}
|
|
15702
15786
|
function writeUpdateState(state, path = updateStatePath()) {
|
|
15703
|
-
mkdirSync15(
|
|
15787
|
+
mkdirSync15(dirname13(path), { recursive: true });
|
|
15704
15788
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
15705
15789
|
writeFileSync10(tmp, `${JSON.stringify(state, null, 2)}
|
|
15706
15790
|
`, { mode: 384 });
|
|
@@ -15717,7 +15801,7 @@ function updateMode(env = process.env) {
|
|
|
15717
15801
|
return env.CABANE_COMPANION_SUPERVISED === "1" ? "supervised" : "attached";
|
|
15718
15802
|
}
|
|
15719
15803
|
var STAGE_RETRY_MS = 60 * 6e4;
|
|
15720
|
-
var
|
|
15804
|
+
var SWAP_WAIT_MS = 10 * 6e4;
|
|
15721
15805
|
var SOURCE_BUILD_VERSION = "0.0.0";
|
|
15722
15806
|
function versionToStage(input) {
|
|
15723
15807
|
const { own, state, now, force } = input;
|
|
@@ -15738,10 +15822,10 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
|
|
|
15738
15822
|
return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
|
|
15739
15823
|
}
|
|
15740
15824
|
function writeBootGuard(root = ownPackageRoot()) {
|
|
15741
|
-
const built =
|
|
15825
|
+
const built = join27(root, "dist", "boot-guard.js");
|
|
15742
15826
|
if (!existsSync17(built)) return false;
|
|
15743
15827
|
const target = bootGuardPath();
|
|
15744
|
-
mkdirSync16(
|
|
15828
|
+
mkdirSync16(dirname14(target), { recursive: true });
|
|
15745
15829
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
15746
15830
|
copyFileSync3(built, tmp);
|
|
15747
15831
|
renameSync11(tmp, target);
|
|
@@ -15778,17 +15862,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve2) => {
|
|
|
15778
15862
|
async function stageVersion(version, harnesses, deps = {}) {
|
|
15779
15863
|
const run = deps.run ?? runProcess;
|
|
15780
15864
|
const node = deps.execPath ?? process.execPath;
|
|
15781
|
-
const target =
|
|
15865
|
+
const target = join27(versionsDir(), version);
|
|
15782
15866
|
const placed = versionPackageRoot(version);
|
|
15783
15867
|
if (existsSync17(placed)) {
|
|
15784
15868
|
if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
|
|
15785
15869
|
return { ok: true, root: placed };
|
|
15786
15870
|
}
|
|
15787
|
-
const npm =
|
|
15871
|
+
const npm = join27(dirname14(node), "npm");
|
|
15788
15872
|
if (!existsSync17(npm))
|
|
15789
|
-
return { ok: false, reason: `there is no npm beside Node at ${
|
|
15873
|
+
return { ok: false, reason: `there is no npm beside Node at ${dirname14(node)}` };
|
|
15790
15874
|
mkdirSync16(versionsDir(), { recursive: true });
|
|
15791
|
-
const staging =
|
|
15875
|
+
const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
|
|
15792
15876
|
rmSync14(staging, { recursive: true, force: true });
|
|
15793
15877
|
const installed = await run(
|
|
15794
15878
|
npm,
|
|
@@ -15805,7 +15889,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
15805
15889
|
],
|
|
15806
15890
|
{
|
|
15807
15891
|
// npm's install scripts run on this Node, not whichever `node` is first on PATH.
|
|
15808
|
-
env: { ...process.env, PATH: `${
|
|
15892
|
+
env: { ...process.env, PATH: `${dirname14(node)}:${process.env.PATH ?? ""}` },
|
|
15809
15893
|
timeoutMs: 15 * 6e4
|
|
15810
15894
|
}
|
|
15811
15895
|
);
|
|
@@ -15817,7 +15901,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
15817
15901
|
return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
|
|
15818
15902
|
return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
|
|
15819
15903
|
}
|
|
15820
|
-
const root =
|
|
15904
|
+
const root = join27(staging, "node_modules", "@cabane", "companion");
|
|
15821
15905
|
const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
|
|
15822
15906
|
if (verdict) {
|
|
15823
15907
|
rmSync14(staging, { recursive: true, force: true });
|
|
@@ -15829,7 +15913,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
15829
15913
|
}
|
|
15830
15914
|
async function verifyInstall(root, version, harnesses, deps) {
|
|
15831
15915
|
if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
|
|
15832
|
-
const answered = await deps.run(deps.execPath, [
|
|
15916
|
+
const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
|
|
15833
15917
|
timeoutMs: 6e4,
|
|
15834
15918
|
env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
|
|
15835
15919
|
});
|
|
@@ -15837,7 +15921,7 @@ async function verifyInstall(root, version, harnesses, deps) {
|
|
|
15837
15921
|
return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
|
|
15838
15922
|
}
|
|
15839
15923
|
if (harnesses.length === 0) return null;
|
|
15840
|
-
const probed = await deps.run(deps.execPath, [
|
|
15924
|
+
const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
|
|
15841
15925
|
timeoutMs: 6e4
|
|
15842
15926
|
});
|
|
15843
15927
|
let resolutions = {};
|
|
@@ -15867,6 +15951,8 @@ var CompanionUpdater = class {
|
|
|
15867
15951
|
deps;
|
|
15868
15952
|
staging = null;
|
|
15869
15953
|
ready = null;
|
|
15954
|
+
// When `ready` verified; `SWAP_WAIT_MS` counts from here.
|
|
15955
|
+
readyAt = 0;
|
|
15870
15956
|
swapping = false;
|
|
15871
15957
|
watchdog = null;
|
|
15872
15958
|
now;
|
|
@@ -15965,7 +16051,7 @@ var CompanionUpdater = class {
|
|
|
15965
16051
|
return { ok: true, message: `Cabane ${this.deps.own} is up to date.` };
|
|
15966
16052
|
if (state.pending?.to === latest) return { ok: true, message: this.waitingMessage(latest) };
|
|
15967
16053
|
if (this.ready === latest) {
|
|
15968
|
-
void this.trySwap();
|
|
16054
|
+
void this.trySwap(true);
|
|
15969
16055
|
return { ok: true, message: this.waitingMessage(latest) };
|
|
15970
16056
|
}
|
|
15971
16057
|
this.maybeStage(true);
|
|
@@ -15981,13 +16067,14 @@ var CompanionUpdater = class {
|
|
|
15981
16067
|
message: failed?.kind === "failed" && failed.to === latest ? `Couldn't update to ${latest}: ${failed.reason ?? "see cabane logs"}.` : `Couldn't update to ${latest}. cabane logs says why.`
|
|
15982
16068
|
};
|
|
15983
16069
|
}
|
|
15984
|
-
void this.trySwap();
|
|
16070
|
+
void this.trySwap(true);
|
|
15985
16071
|
return { ok: true, message: this.waitingMessage(latest) };
|
|
15986
16072
|
}
|
|
16073
|
+
// Only the person's `cabane update` asks, and that swaps at once.
|
|
15987
16074
|
waitingMessage(version) {
|
|
15988
16075
|
if (this.deps.mode === "attached")
|
|
15989
16076
|
return `Cabane ${version} is installed. It runs from the next cabane start.`;
|
|
15990
|
-
return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version}
|
|
16077
|
+
return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version} now. The turns running now carry on after the restart.`;
|
|
15991
16078
|
}
|
|
15992
16079
|
maybeStage(force) {
|
|
15993
16080
|
if (this.staging) return;
|
|
@@ -16005,6 +16092,7 @@ var CompanionUpdater = class {
|
|
|
16005
16092
|
const outcome = await stage(version, this.deps.harnesses());
|
|
16006
16093
|
if (outcome.ok) {
|
|
16007
16094
|
this.ready = version;
|
|
16095
|
+
this.readyAt = this.now();
|
|
16008
16096
|
updateUpdateState((s) => {
|
|
16009
16097
|
const { failed: _failed, ...rest } = s;
|
|
16010
16098
|
return rest;
|
|
@@ -16043,8 +16131,10 @@ var CompanionUpdater = class {
|
|
|
16043
16131
|
});
|
|
16044
16132
|
void this.staging.then(() => this.trySwap());
|
|
16045
16133
|
}
|
|
16046
|
-
// Swap into the ready version
|
|
16047
|
-
|
|
16134
|
+
// Swap into the ready version: when nothing is running, when it has waited `SWAP_WAIT_MS` for
|
|
16135
|
+
// that, when this version is below the server's minimum, or `now` (the person asked). Called on
|
|
16136
|
+
// each heartbeat until it does.
|
|
16137
|
+
async trySwap(now = false) {
|
|
16048
16138
|
const to = this.ready;
|
|
16049
16139
|
if (!to || this.swapping) return;
|
|
16050
16140
|
if (readUpdateState().pending) return;
|
|
@@ -16053,18 +16143,9 @@ var CompanionUpdater = class {
|
|
|
16053
16143
|
this.deps.log.info({ version: to }, `Cabane ${to} is installed; it runs from the next start`);
|
|
16054
16144
|
return;
|
|
16055
16145
|
}
|
|
16056
|
-
if (!this.deps.isIdle()) return;
|
|
16146
|
+
if (!now && !this.deps.isIdle() && !this.waitedLongEnough() && !this.belowMinimum()) return;
|
|
16057
16147
|
this.swapping = true;
|
|
16058
16148
|
try {
|
|
16059
|
-
await this.deps.pauseStream();
|
|
16060
|
-
const sleep5 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16061
|
-
const deadline = this.now() + SWAP_DRAIN_MS;
|
|
16062
|
-
await sleep5(250);
|
|
16063
|
-
while (!this.deps.isIdle() && this.now() < deadline) await sleep5(250);
|
|
16064
|
-
if (!this.deps.isIdle()) {
|
|
16065
|
-
this.deps.resumeStream();
|
|
16066
|
-
return;
|
|
16067
|
-
}
|
|
16068
16149
|
this.select(to);
|
|
16069
16150
|
this.deps.log.info({ from: this.deps.own, to }, `Restarting into Cabane ${to}`);
|
|
16070
16151
|
await this.deps.restart();
|
|
@@ -16072,6 +16153,14 @@ var CompanionUpdater = class {
|
|
|
16072
16153
|
this.swapping = false;
|
|
16073
16154
|
}
|
|
16074
16155
|
}
|
|
16156
|
+
waitedLongEnough() {
|
|
16157
|
+
return this.now() - this.readyAt >= SWAP_WAIT_MS;
|
|
16158
|
+
}
|
|
16159
|
+
// Cabane refuses this version's turns: waiting would only turn them away.
|
|
16160
|
+
belowMinimum() {
|
|
16161
|
+
const minimum = readUpdateState().server?.minimum;
|
|
16162
|
+
return !!minimum && compareVersions(this.deps.own, minimum) < 0;
|
|
16163
|
+
}
|
|
16075
16164
|
// Record the swap and point `current` at `to`, leaving the guard that counts its starts.
|
|
16076
16165
|
select(to) {
|
|
16077
16166
|
const fromRoot = currentRoot() ?? ownPackageRoot();
|
|
@@ -16096,6 +16185,7 @@ init_version2();
|
|
|
16096
16185
|
init_src();
|
|
16097
16186
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
16098
16187
|
var ARCHIVE_PRUNE_MS = 24 * 60 * 6e4;
|
|
16188
|
+
var FORCE_EXIT_MS = 4e3;
|
|
16099
16189
|
var SHUTDOWN_HEARTBEAT_WAIT_MS = 1e3;
|
|
16100
16190
|
function positiveIntEnv(name) {
|
|
16101
16191
|
const raw = process.env[name];
|
|
@@ -16316,14 +16406,7 @@ var CompanionSupervisor = class {
|
|
|
16316
16406
|
autoUpdate: () => this.config.update?.auto === true,
|
|
16317
16407
|
harnesses: () => this.connectedBundledHarnesses(),
|
|
16318
16408
|
isIdle: () => this.idle(),
|
|
16319
|
-
|
|
16320
|
-
const sub = this.deviceSub;
|
|
16321
|
-
if (!sub) return;
|
|
16322
|
-
sub.stop();
|
|
16323
|
-
await sub.finished;
|
|
16324
|
-
},
|
|
16325
|
-
resumeStream: () => void this.deviceSub?.resume(),
|
|
16326
|
-
restart: () => this.restartForUpdate(),
|
|
16409
|
+
restart: () => this.restartBounded(),
|
|
16327
16410
|
log: this.log
|
|
16328
16411
|
});
|
|
16329
16412
|
this.updater.start();
|
|
@@ -16416,7 +16499,7 @@ var CompanionSupervisor = class {
|
|
|
16416
16499
|
this.versionSkewWarned = true;
|
|
16417
16500
|
this.log.warn(
|
|
16418
16501
|
{ companionVersion: COMPANION_VERSION, serverVersion },
|
|
16419
|
-
|
|
16502
|
+
`This companion and Cabane are on different versions. If turns fail, update with ${cabane("update")}`
|
|
16420
16503
|
);
|
|
16421
16504
|
}
|
|
16422
16505
|
// Pull assignments and reconcile the live runner set against them. Re-entrancy
|
|
@@ -16645,7 +16728,8 @@ var CompanionSupervisor = class {
|
|
|
16645
16728
|
bundled: this.bundledStatus("claude-code")
|
|
16646
16729
|
},
|
|
16647
16730
|
codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
|
|
16648
|
-
install: companionInstall()
|
|
16731
|
+
install: companionInstall(),
|
|
16732
|
+
managed: repairRoute() === "managed"
|
|
16649
16733
|
});
|
|
16650
16734
|
}
|
|
16651
16735
|
// A missing binary is reported ONCE, and again only when the answer CHANGES,
|
|
@@ -17445,8 +17529,22 @@ var CompanionSupervisor = class {
|
|
|
17445
17529
|
);
|
|
17446
17530
|
}
|
|
17447
17531
|
}
|
|
17532
|
+
// The update's restart, held to the same `FORCE_EXIT_MS` a signal's stop gets. Exit 0: the
|
|
17533
|
+
// swap is recorded and `current` names the new version, so the service starts it either way.
|
|
17534
|
+
async restartBounded() {
|
|
17535
|
+
const force = setTimeout(() => {
|
|
17536
|
+
this.log.warn({ ms: FORCE_EXIT_MS }, "Restarting for the update: shutdown took too long");
|
|
17537
|
+
this.exitFn(0);
|
|
17538
|
+
}, FORCE_EXIT_MS);
|
|
17539
|
+
force.unref?.();
|
|
17540
|
+
try {
|
|
17541
|
+
await this.restartForUpdate();
|
|
17542
|
+
} finally {
|
|
17543
|
+
clearTimeout(force);
|
|
17544
|
+
}
|
|
17545
|
+
}
|
|
17448
17546
|
// Nothing running and nothing about to: no dispatch claimed, no turn chained, no recovery
|
|
17449
|
-
// in progress. What a self-update swap waits for
|
|
17547
|
+
// in progress. What a self-update swap waits for, up to `SWAP_WAIT_MS`.
|
|
17450
17548
|
idle() {
|
|
17451
17549
|
if (this.claims.size > 0 || this.recovering) return false;
|
|
17452
17550
|
for (const wr of this.workspaces.values()) if (wr.chains.size > 0) return false;
|
|
@@ -17468,7 +17566,7 @@ var CompanionSupervisor = class {
|
|
|
17468
17566
|
if (auto) this.kickHeartbeat();
|
|
17469
17567
|
return {
|
|
17470
17568
|
ok: true,
|
|
17471
|
-
message: auto ? "Cabane will keep itself up to date. Updates install between turns and roll back if they fail." : "Cabane won't update itself. Run cabane update to update."
|
|
17569
|
+
message: auto ? "Cabane will keep itself up to date. Updates install between turns when they can, and roll back if they fail." : "Cabane won't update itself. Run cabane update to update."
|
|
17472
17570
|
};
|
|
17473
17571
|
}
|
|
17474
17572
|
if (!this.updater) return { ok: false, message: "This computer isn't paired yet." };
|
|
@@ -17590,9 +17688,9 @@ function handleUncaught(log, err, origin) {
|
|
|
17590
17688
|
// src/crash-marker.ts
|
|
17591
17689
|
init_config();
|
|
17592
17690
|
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync15, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "fs";
|
|
17593
|
-
import { join as
|
|
17691
|
+
import { join as join28 } from "path";
|
|
17594
17692
|
function crashMarkerPath() {
|
|
17595
|
-
return
|
|
17693
|
+
return join28(cabaneDir(), "last-error.json");
|
|
17596
17694
|
}
|
|
17597
17695
|
function recordCrash(rec2) {
|
|
17598
17696
|
try {
|
|
@@ -17783,7 +17881,7 @@ import {
|
|
|
17783
17881
|
writeFileSync as writeFileSync12
|
|
17784
17882
|
} from "fs";
|
|
17785
17883
|
import { homedir as homedir7, userInfo as userInfo3 } from "os";
|
|
17786
|
-
import { dirname as
|
|
17884
|
+
import { dirname as dirname15, join as join29, sep as sep5 } from "path";
|
|
17787
17885
|
function servicePlatform(platform = process.platform) {
|
|
17788
17886
|
if (platform === "linux") return "systemd";
|
|
17789
17887
|
if (platform === "darwin") return "launchd";
|
|
@@ -17801,15 +17899,15 @@ function serviceNode(execPath = process.execPath) {
|
|
|
17801
17899
|
return existsSync19(pinnedNodePath()) ? pinnedNodePath() : execPath;
|
|
17802
17900
|
}
|
|
17803
17901
|
function servicePaths(platform, env = process.env, home = homedir7(), node = serviceNode()) {
|
|
17804
|
-
const unitFile = platform === "systemd" ?
|
|
17805
|
-
env.XDG_CONFIG_HOME ||
|
|
17902
|
+
const unitFile = platform === "systemd" ? join29(
|
|
17903
|
+
env.XDG_CONFIG_HOME || join29(home, ".config"),
|
|
17806
17904
|
"systemd",
|
|
17807
17905
|
"user",
|
|
17808
17906
|
`${serviceName(env)}.service`
|
|
17809
|
-
) :
|
|
17907
|
+
) : join29(home, "Library", "LaunchAgents", `${launchdLabel(env)}.plist`);
|
|
17810
17908
|
return {
|
|
17811
17909
|
unitFile,
|
|
17812
|
-
envFile:
|
|
17910
|
+
envFile: join29(cabaneDir(), "service.env"),
|
|
17813
17911
|
node,
|
|
17814
17912
|
entry: bootGuardPath()
|
|
17815
17913
|
};
|
|
@@ -17876,7 +17974,7 @@ function renderLaunchdPlist(paths, env = process.env, servicePath) {
|
|
|
17876
17974
|
" <key>ThrottleInterval</key><integer>5</integer>",
|
|
17877
17975
|
// The companion writes its own log; the console copy would only grow a second one.
|
|
17878
17976
|
" <key>StandardOutPath</key><string>/dev/null</string>",
|
|
17879
|
-
` <key>StandardErrorPath</key><string>${xml(
|
|
17977
|
+
` <key>StandardErrorPath</key><string>${xml(join29(cabaneDir(), "service-error.log"))}</string>`,
|
|
17880
17978
|
"</dict>",
|
|
17881
17979
|
"</plist>",
|
|
17882
17980
|
""
|
|
@@ -17931,11 +18029,11 @@ async function installService(deps = {}) {
|
|
|
17931
18029
|
rewriteScriptWrappers();
|
|
17932
18030
|
const paths = servicePaths(platform, env, deps.home, node);
|
|
17933
18031
|
const servicePath = await loginShellPath(run, env);
|
|
17934
|
-
mkdirSync18(
|
|
18032
|
+
mkdirSync18(dirname15(paths.envFile), { recursive: true });
|
|
17935
18033
|
const existing = existsSync19(paths.envFile) ? readFileSync16(paths.envFile, "utf8") : "";
|
|
17936
18034
|
writeFileSync12(paths.envFile, withServicePath(existing, servicePath), { mode: 384 });
|
|
17937
18035
|
chmodSync7(paths.envFile, 384);
|
|
17938
|
-
mkdirSync18(
|
|
18036
|
+
mkdirSync18(dirname15(paths.unitFile), { recursive: true });
|
|
17939
18037
|
const outcome = await writeAndStartUnit(platform, paths, env, servicePath, run);
|
|
17940
18038
|
if (!outcome.ok) await removeUnit(platform, paths, env, run);
|
|
17941
18039
|
return outcome;
|
|
@@ -18016,15 +18114,15 @@ async function seedManagedInstall(deps) {
|
|
|
18016
18114
|
}
|
|
18017
18115
|
const target = versionPackageRoot(version);
|
|
18018
18116
|
if (!existsSync19(target)) {
|
|
18019
|
-
const staging =
|
|
18117
|
+
const staging = join29(versionsDir(), `.${version}.seed-${process.pid}`);
|
|
18020
18118
|
rmSync16(staging, { recursive: true, force: true });
|
|
18021
18119
|
try {
|
|
18022
|
-
cpSync(own,
|
|
18120
|
+
cpSync(own, join29(staging, "node_modules", "@cabane", "companion"), {
|
|
18023
18121
|
recursive: true,
|
|
18024
18122
|
verbatimSymlinks: true
|
|
18025
18123
|
});
|
|
18026
|
-
rmSync16(
|
|
18027
|
-
renameSync12(staging,
|
|
18124
|
+
rmSync16(join29(versionsDir(), version), { recursive: true, force: true });
|
|
18125
|
+
renameSync12(staging, join29(versionsDir(), version));
|
|
18028
18126
|
} catch (err) {
|
|
18029
18127
|
rmSync16(staging, { recursive: true, force: true });
|
|
18030
18128
|
return {
|
|
@@ -18035,7 +18133,7 @@ async function seedManagedInstall(deps) {
|
|
|
18035
18133
|
}
|
|
18036
18134
|
const unrunnable = await verifyInstall(target, version, [], deps);
|
|
18037
18135
|
if (unrunnable) {
|
|
18038
|
-
rmSync16(
|
|
18136
|
+
rmSync16(join29(versionsDir(), version), { recursive: true, force: true });
|
|
18039
18137
|
return { ok: false, reason: `The copy of Cabane for the service can't run: ${unrunnable}.` };
|
|
18040
18138
|
}
|
|
18041
18139
|
pointCurrentAt(target);
|
|
@@ -18117,9 +18215,9 @@ function userName() {
|
|
|
18117
18215
|
return process.env.USER ?? "";
|
|
18118
18216
|
}
|
|
18119
18217
|
}
|
|
18120
|
-
function rewriteScriptWrappers(dir2 =
|
|
18218
|
+
function rewriteScriptWrappers(dir2 = join29(cabaneDir(), "bin")) {
|
|
18121
18219
|
for (const name of ["cabane", "cabane-companion"]) {
|
|
18122
|
-
const path =
|
|
18220
|
+
const path = join29(dir2, name);
|
|
18123
18221
|
let text;
|
|
18124
18222
|
try {
|
|
18125
18223
|
text = readFileSync16(path, "utf8");
|
|
@@ -18178,7 +18276,7 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
18178
18276
|
const readState = deps.readState ?? readLiveRuntimeState;
|
|
18179
18277
|
const verify = deps.verify ?? ((s) => verifyRuntime(s));
|
|
18180
18278
|
const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
|
|
18181
|
-
const
|
|
18279
|
+
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
18182
18280
|
const now = deps.now ?? (() => Date.now());
|
|
18183
18281
|
const quiet = opts.report === "failures";
|
|
18184
18282
|
const cfg = requireStartConfig({
|
|
@@ -18226,7 +18324,7 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
18226
18324
|
const deadline = now() + STARTUP_TIMEOUT_MS;
|
|
18227
18325
|
let seen = readState();
|
|
18228
18326
|
while (!ready(seen) && now() < deadline) {
|
|
18229
|
-
await
|
|
18327
|
+
await sleep4(POLL_INTERVAL_MS);
|
|
18230
18328
|
seen = readState();
|
|
18231
18329
|
}
|
|
18232
18330
|
return ready(seen) ? seen : null;
|
|
@@ -18299,7 +18397,7 @@ async function stop(deps = {}) {
|
|
|
18299
18397
|
const readState = deps.readState ?? readLiveRuntimeState;
|
|
18300
18398
|
const verify = deps.verify ?? ((s) => verifyRuntime(s));
|
|
18301
18399
|
const kill = deps.kill ?? ((pid2, signal) => process.kill(pid2, signal));
|
|
18302
|
-
const
|
|
18400
|
+
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
18303
18401
|
const now = deps.now ?? (() => Date.now());
|
|
18304
18402
|
const state = readState();
|
|
18305
18403
|
if (!state) {
|
|
@@ -18327,7 +18425,7 @@ async function stop(deps = {}) {
|
|
|
18327
18425
|
process.stdout.write("companion: stopped.\n");
|
|
18328
18426
|
return;
|
|
18329
18427
|
}
|
|
18330
|
-
await
|
|
18428
|
+
await sleep4(POLL_INTERVAL_MS2);
|
|
18331
18429
|
}
|
|
18332
18430
|
process.stdout.write(
|
|
18333
18431
|
`companion: didn't exit within ${TERM_GRACE_MS / 1e3}s, sending SIGKILL.
|
|
@@ -18353,7 +18451,7 @@ function isAlive(kill, pid) {
|
|
|
18353
18451
|
var STARTUP_WAIT_MS = 2e4;
|
|
18354
18452
|
async function installAndStartService(deps = {}) {
|
|
18355
18453
|
const readState = deps.readState ?? readLiveRuntimeState;
|
|
18356
|
-
const
|
|
18454
|
+
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
18357
18455
|
const now = deps.now ?? Date.now;
|
|
18358
18456
|
const before = await serviceState(deps);
|
|
18359
18457
|
const running = readState();
|
|
@@ -18369,7 +18467,7 @@ async function installAndStartService(deps = {}) {
|
|
|
18369
18467
|
while (now() < deadline) {
|
|
18370
18468
|
const state = readState();
|
|
18371
18469
|
if (state?.socket) return true;
|
|
18372
|
-
await
|
|
18470
|
+
await sleep4(250);
|
|
18373
18471
|
}
|
|
18374
18472
|
write(
|
|
18375
18473
|
`${INDENT}The service is installed but Cabane hasn't come up yet. ${cabane("service status")} and ${cabane("logs")} say why.`
|
|
@@ -18383,7 +18481,7 @@ async function serviceInstall(deps = {}) {
|
|
|
18383
18481
|
}
|
|
18384
18482
|
write(
|
|
18385
18483
|
INDENT + tick(
|
|
18386
|
-
"Cabane runs as a service. It starts at boot, restarts if it stops, and updates itself between turns."
|
|
18484
|
+
"Cabane runs as a service. It starts at boot, restarts if it stops, and updates itself, usually between turns."
|
|
18387
18485
|
)
|
|
18388
18486
|
);
|
|
18389
18487
|
write(`${INDENT} Status: ${cabane("service status")} Logs: ${cabane("logs")}`);
|
|
@@ -18446,7 +18544,7 @@ function updateRows(state, auto, opts = {}) {
|
|
|
18446
18544
|
rows.push(
|
|
18447
18545
|
row(
|
|
18448
18546
|
"Auto-update",
|
|
18449
|
-
!opts.service ? `off \u2014 runs with the service: ${cabane("service install")}` : auto === true ? "on \u2014 updates install between turns and roll back if they fail" : `off \u2014 ${cabane("update --auto on")} turns it on`
|
|
18547
|
+
!opts.service ? `off \u2014 runs with the service: ${cabane("service install")}` : auto === true ? "on \u2014 updates install between turns when they can, and roll back if they fail" : `off \u2014 ${cabane("update --auto on")} turns it on`
|
|
18450
18548
|
)
|
|
18451
18549
|
);
|
|
18452
18550
|
const last = state.lastResult;
|
|
@@ -18477,7 +18575,6 @@ function setRunsAsService(on) {
|
|
|
18477
18575
|
}
|
|
18478
18576
|
|
|
18479
18577
|
// src/commands/start.ts
|
|
18480
|
-
var FORCE_EXIT_MS = 4e3;
|
|
18481
18578
|
var MAX_CODES = 3;
|
|
18482
18579
|
async function start(opts = {}) {
|
|
18483
18580
|
setLogOverrides(opts);
|
|
@@ -18943,7 +19040,7 @@ async function askHowToRun(deps = {}) {
|
|
|
18943
19040
|
return { asService: false };
|
|
18944
19041
|
blank();
|
|
18945
19042
|
const asService = await ask(
|
|
18946
|
-
"Keep Cabane running and up to date on this computer? It starts at boot, restarts if it stops, and updates itself between turns."
|
|
19043
|
+
"Keep Cabane running and up to date on this computer? It starts at boot, restarts if it stops, and updates itself, usually between turns."
|
|
18947
19044
|
);
|
|
18948
19045
|
if (!asService) saveConfig({ ...cfg, serviceDeclined: true });
|
|
18949
19046
|
blank();
|
|
@@ -19113,10 +19210,11 @@ async function logs(opts = {}) {
|
|
|
19113
19210
|
|
|
19114
19211
|
// src/commands/status.ts
|
|
19115
19212
|
init_bundled_binaries();
|
|
19213
|
+
init_install_kind();
|
|
19116
19214
|
init_cabane_command();
|
|
19117
|
-
import { readdirSync as readdirSync7, realpathSync as
|
|
19215
|
+
import { readdirSync as readdirSync7, realpathSync as realpathSync8, existsSync as existsSync20 } from "fs";
|
|
19118
19216
|
import { homedir as homedir8 } from "os";
|
|
19119
|
-
import { dirname as
|
|
19217
|
+
import { dirname as dirname16, join as join30 } from "path";
|
|
19120
19218
|
init_config();
|
|
19121
19219
|
init_sandbox_policy();
|
|
19122
19220
|
init_secrets();
|
|
@@ -19182,10 +19280,10 @@ function versionLine() {
|
|
|
19182
19280
|
const install = companionInstall();
|
|
19183
19281
|
const own = installedCommandPath({ install });
|
|
19184
19282
|
if (install === "script") {
|
|
19185
|
-
return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(
|
|
19283
|
+
return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join30(homedir8(), ".cabane"))})`;
|
|
19186
19284
|
}
|
|
19187
19285
|
if (own)
|
|
19188
|
-
return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(
|
|
19286
|
+
return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(dirname16(dirname16(own)))})`;
|
|
19189
19287
|
return `Cabane ${COMPANION_VERSION}`;
|
|
19190
19288
|
}
|
|
19191
19289
|
function otherInstallLines(running) {
|
|
@@ -19202,13 +19300,13 @@ function otherInstallLines(running) {
|
|
|
19202
19300
|
function otherInstall(install, own) {
|
|
19203
19301
|
const real = (p) => {
|
|
19204
19302
|
try {
|
|
19205
|
-
return
|
|
19303
|
+
return realpathSync8(p);
|
|
19206
19304
|
} catch {
|
|
19207
19305
|
return null;
|
|
19208
19306
|
}
|
|
19209
19307
|
};
|
|
19210
19308
|
if (install === "npm") {
|
|
19211
|
-
const script =
|
|
19309
|
+
const script = join30(
|
|
19212
19310
|
homedir8(),
|
|
19213
19311
|
".cabane",
|
|
19214
19312
|
"node",
|
|
@@ -19217,12 +19315,12 @@ function otherInstall(install, own) {
|
|
|
19217
19315
|
"@cabane",
|
|
19218
19316
|
"companion"
|
|
19219
19317
|
);
|
|
19220
|
-
return existsSync20(script) ?
|
|
19318
|
+
return existsSync20(script) ? join30(homedir8(), ".cabane") : null;
|
|
19221
19319
|
}
|
|
19222
19320
|
const found = firstOnPath("cabane-companion", process.env.PATH);
|
|
19223
19321
|
if (!found) return null;
|
|
19224
|
-
const ownAlias = own ? real(
|
|
19225
|
-
return real(found) === ownAlias ? null :
|
|
19322
|
+
const ownAlias = own ? real(join30(dirname16(own), "cabane-companion")) : null;
|
|
19323
|
+
return real(found) === ownAlias ? null : dirname16(found);
|
|
19226
19324
|
}
|
|
19227
19325
|
async function liveState() {
|
|
19228
19326
|
const running = readLiveRuntimeState();
|
|
@@ -19282,7 +19380,7 @@ function signInText(runtime, status2, version) {
|
|
|
19282
19380
|
case "signed_out":
|
|
19283
19381
|
return `signed out \u2014 run ${loginCommand(runtime)}`;
|
|
19284
19382
|
case "bundle_missing":
|
|
19285
|
-
return `this install is missing it \u2014 repair with ${
|
|
19383
|
+
return `this install is missing it \u2014 repair with ${repairSteps()}${repairCost() ? `. ${repairCost()}` : ""}`;
|
|
19286
19384
|
case "check_failed":
|
|
19287
19385
|
return "connected, but its sign-in couldn't be checked";
|
|
19288
19386
|
default:
|
|
@@ -19341,6 +19439,8 @@ init_config();
|
|
|
19341
19439
|
init_term();
|
|
19342
19440
|
init_version2();
|
|
19343
19441
|
init_cabane_command();
|
|
19442
|
+
init_bundled_binaries();
|
|
19443
|
+
init_install_kind();
|
|
19344
19444
|
var UPDATE_TIMEOUT_MS = 20 * 6e4;
|
|
19345
19445
|
async function update(opts = {}) {
|
|
19346
19446
|
const running = readLiveRuntimeState();
|
|
@@ -19358,20 +19458,43 @@ async function update(opts = {}) {
|
|
|
19358
19458
|
write(`${INDENT}${r.ok ? tick(r.message) : r.message}`);
|
|
19359
19459
|
return;
|
|
19360
19460
|
}
|
|
19361
|
-
const
|
|
19362
|
-
if (!
|
|
19461
|
+
const cfg = loadConfig();
|
|
19462
|
+
if (!cfg) {
|
|
19363
19463
|
write(`${INDENT}This computer isn't set up yet. Run ${cabane("start")} first.`);
|
|
19364
19464
|
process.exitCode = 1;
|
|
19365
19465
|
return;
|
|
19366
19466
|
}
|
|
19367
|
-
saveConfig({ ...
|
|
19467
|
+
saveConfig({ ...cfg, update: { auto } });
|
|
19368
19468
|
write(
|
|
19369
19469
|
INDENT + tick(
|
|
19370
|
-
auto ? "Cabane will keep itself up to date. Updates install between turns and roll back if they fail." : "Cabane won't update itself."
|
|
19470
|
+
auto ? "Cabane will keep itself up to date. Updates install between turns when they can, and roll back if they fail." : "Cabane won't update itself."
|
|
19371
19471
|
)
|
|
19372
19472
|
);
|
|
19373
19473
|
return;
|
|
19374
19474
|
}
|
|
19475
|
+
const harnesses = connectedHarnesses();
|
|
19476
|
+
const latest = readUpdateState().server?.latest;
|
|
19477
|
+
if (!latest || compareVersions(latest, COMPANION_VERSION) <= 0) {
|
|
19478
|
+
const repair = await repairOwnCopy({ running: Boolean(running) });
|
|
19479
|
+
if (repair.kind === "running") {
|
|
19480
|
+
write(
|
|
19481
|
+
`${INDENT}This copy of Cabane ${COMPANION_VERSION} is incomplete: ${repair.reason}. Run ${cabane("stop")}, then ${cabane("update")} again to repair it. Stopping ends any turn in progress.`
|
|
19482
|
+
);
|
|
19483
|
+
process.exitCode = 1;
|
|
19484
|
+
return;
|
|
19485
|
+
}
|
|
19486
|
+
if (repair.kind === "failed") {
|
|
19487
|
+
write(`${INDENT}Couldn't repair Cabane ${COMPANION_VERSION}: ${repair.reason}.`);
|
|
19488
|
+
process.exitCode = 1;
|
|
19489
|
+
return;
|
|
19490
|
+
}
|
|
19491
|
+
if (repair.kind === "repaired") {
|
|
19492
|
+
write(
|
|
19493
|
+
INDENT + tick(`Repaired Cabane ${COMPANION_VERSION}. Start it with ${cabane("start")}`)
|
|
19494
|
+
);
|
|
19495
|
+
return;
|
|
19496
|
+
}
|
|
19497
|
+
}
|
|
19375
19498
|
if (running?.socket) {
|
|
19376
19499
|
const r = await controlRequest(
|
|
19377
19500
|
running.socket,
|
|
@@ -19382,7 +19505,6 @@ async function update(opts = {}) {
|
|
|
19382
19505
|
if (!r.ok) process.exitCode = 1;
|
|
19383
19506
|
return;
|
|
19384
19507
|
}
|
|
19385
|
-
const latest = readUpdateState().server?.latest;
|
|
19386
19508
|
if (!latest) {
|
|
19387
19509
|
write(
|
|
19388
19510
|
`${INDENT}Cabane hasn't said which version is latest yet. Run ${cabane("start")}, and it updates from there.`
|
|
@@ -19394,11 +19516,6 @@ async function update(opts = {}) {
|
|
|
19394
19516
|
write(INDENT + tick(`Cabane ${COMPANION_VERSION} is up to date.`));
|
|
19395
19517
|
return;
|
|
19396
19518
|
}
|
|
19397
|
-
const cfg = loadConfig();
|
|
19398
|
-
const harnesses = cfg ? [
|
|
19399
|
-
...isClaudeCodeConnected(cfg) ? ["claude-code"] : [],
|
|
19400
|
-
...isCodexEnabled(cfg) ? ["codex"] : []
|
|
19401
|
-
] : [];
|
|
19402
19519
|
write(
|
|
19403
19520
|
`${INDENT}Installing Cabane ${latest}. It brings its own copies of the agents, so this can take a few minutes.`
|
|
19404
19521
|
);
|
|
@@ -19438,6 +19555,31 @@ async function update(opts = {}) {
|
|
|
19438
19555
|
pointCurrentAt(outcome.root);
|
|
19439
19556
|
write(INDENT + tick(`Updated to ${latest}. Start it with ${cabane("start")}`));
|
|
19440
19557
|
}
|
|
19558
|
+
function bundledHere(platform, arch) {
|
|
19559
|
+
return ["claude-code", ...codexPlatformPackage(platform, arch) ? ["codex"] : []];
|
|
19560
|
+
}
|
|
19561
|
+
function connectedHarnesses() {
|
|
19562
|
+
const cfg = loadConfig();
|
|
19563
|
+
if (!cfg) return [];
|
|
19564
|
+
return [
|
|
19565
|
+
...isClaudeCodeConnected(cfg) ? ["claude-code"] : [],
|
|
19566
|
+
...isCodexEnabled(cfg) ? ["codex"] : []
|
|
19567
|
+
];
|
|
19568
|
+
}
|
|
19569
|
+
async function repairOwnCopy(deps) {
|
|
19570
|
+
if ((deps.route ?? repairRoute()) !== "managed") return { kind: "healthy" };
|
|
19571
|
+
const harnesses = deps.harnesses ?? bundledHere(deps.platform ?? process.platform, deps.arch ?? process.arch);
|
|
19572
|
+
const root = deps.root ?? ownPackageRoot();
|
|
19573
|
+
const version = deps.version ?? COMPANION_VERSION;
|
|
19574
|
+
const verify = deps.verify ?? ((r, v, h) => verifyInstall(r, v, h, { run: runProcess, execPath: process.execPath }));
|
|
19575
|
+
const broken = await verify(root, version, harnesses);
|
|
19576
|
+
if (!broken) return { kind: "healthy" };
|
|
19577
|
+
if (deps.running) return { kind: "running", reason: broken };
|
|
19578
|
+
const outcome = await (deps.stage ?? stageVersion)(version, harnesses);
|
|
19579
|
+
if (!outcome.ok) return { kind: "failed", reason: outcome.reason };
|
|
19580
|
+
(deps.point ?? pointCurrentAt)(outcome.root);
|
|
19581
|
+
return { kind: "repaired" };
|
|
19582
|
+
}
|
|
19441
19583
|
|
|
19442
19584
|
// src/commands/transcript.ts
|
|
19443
19585
|
init_cabane_command();
|
|
@@ -19445,7 +19587,7 @@ init_errors();
|
|
|
19445
19587
|
init_src();
|
|
19446
19588
|
init_transcript_writer();
|
|
19447
19589
|
import { existsSync as existsSync21, readFileSync as readFileSync17, readdirSync as readdirSync8 } from "fs";
|
|
19448
|
-
import { isAbsolute, join as
|
|
19590
|
+
import { isAbsolute, join as join31 } from "path";
|
|
19449
19591
|
async function transcript(opts = {}) {
|
|
19450
19592
|
const dir2 = transcriptsDir();
|
|
19451
19593
|
if (opts.follow) {
|
|
@@ -19462,7 +19604,7 @@ async function transcript(opts = {}) {
|
|
|
19462
19604
|
process.stdout.write(emptyMessage(dir2));
|
|
19463
19605
|
return;
|
|
19464
19606
|
}
|
|
19465
|
-
process.stdout.write(renderFile(
|
|
19607
|
+
process.stdout.write(renderFile(join31(dir2, newest)) + "\n");
|
|
19466
19608
|
return;
|
|
19467
19609
|
}
|
|
19468
19610
|
printList(dir2);
|
|
@@ -19545,7 +19687,7 @@ function isComplete(content) {
|
|
|
19545
19687
|
async function followTranscripts(dir2) {
|
|
19546
19688
|
const follower = new TranscriptFollower({
|
|
19547
19689
|
listFiles: () => listFiles(dir2),
|
|
19548
|
-
read: (f) => readFileSync17(
|
|
19690
|
+
read: (f) => readFileSync17(join31(dir2, f), "utf8"),
|
|
19549
19691
|
write: (s) => process.stdout.write(s),
|
|
19550
19692
|
// CSI: cursor up `n` lines, then erase from cursor to end of screen.
|
|
19551
19693
|
clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
|
|
@@ -19585,7 +19727,7 @@ function printList(dir2) {
|
|
|
19585
19727
|
|
|
19586
19728
|
`);
|
|
19587
19729
|
for (const f of files.slice(0, 20)) {
|
|
19588
|
-
const { meta, outcome } = peek(
|
|
19730
|
+
const { meta, outcome } = peek(join31(dir2, f));
|
|
19589
19731
|
const when = fmtTime(rec(meta)?.ts);
|
|
19590
19732
|
const ws = str2(rec(meta)?.workspaceSlug);
|
|
19591
19733
|
const o = rec(outcome);
|
|
@@ -19623,10 +19765,10 @@ function resolveTarget(dir2, target) {
|
|
|
19623
19765
|
if (existsSync21(target)) return target;
|
|
19624
19766
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
19625
19767
|
}
|
|
19626
|
-
const exact =
|
|
19768
|
+
const exact = join31(dir2, target);
|
|
19627
19769
|
if (existsSync21(exact)) return exact;
|
|
19628
19770
|
const matches = listFiles(dir2).filter((f) => f.includes(target));
|
|
19629
|
-
if (matches.length === 1) return
|
|
19771
|
+
if (matches.length === 1) return join31(dir2, matches[0]);
|
|
19630
19772
|
if (matches.length === 0) {
|
|
19631
19773
|
throw new CompanionError(
|
|
19632
19774
|
`no transcript matching "${target}" in ${dir2}. Run \`${cabane("transcript")}\` to list them.`
|
|
@@ -19790,6 +19932,8 @@ They are written per dispatch while \`${cabane("start")}\` is running.
|
|
|
19790
19932
|
// src/cli.ts
|
|
19791
19933
|
init_errors();
|
|
19792
19934
|
init_version2();
|
|
19935
|
+
init_cabane_command();
|
|
19936
|
+
rememberInvokedCommand();
|
|
19793
19937
|
var program = new Command();
|
|
19794
19938
|
program.name("cabane").description("Run your Cabane agents on this computer, with Claude Code, Codex or OpenCode.").version(COMPANION_VERSION);
|
|
19795
19939
|
program.command("pair").description(
|