@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/runtime.js
CHANGED
|
@@ -4,187 +4,13 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
4
4
|
// src/cabane-command.ts
|
|
5
5
|
import { accessSync, constants, realpathSync as realpathSync2 } from "fs";
|
|
6
6
|
import { homedir as homedir3 } from "os";
|
|
7
|
-
import { delimiter, join as join2, sep as sep2 } from "path";
|
|
7
|
+
import { basename, delimiter, dirname as dirname2, join as join2, sep as sep2 } from "path";
|
|
8
8
|
|
|
9
|
-
// src/
|
|
10
|
-
import { realpathSync
|
|
11
|
-
import { createRequire } from "module";
|
|
9
|
+
// src/install-kind.ts
|
|
10
|
+
import { realpathSync } from "fs";
|
|
12
11
|
import { homedir } from "os";
|
|
13
12
|
import { dirname, join, sep } from "path";
|
|
14
13
|
import { fileURLToPath } from "url";
|
|
15
|
-
function nodeResolveSelf(specifier) {
|
|
16
|
-
const here = fileURLToPath(import.meta.url);
|
|
17
|
-
if (typeof import.meta.resolve === "function") {
|
|
18
|
-
try {
|
|
19
|
-
return fileURLToPath(import.meta.resolve(specifier));
|
|
20
|
-
} catch {
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
return createRequire(here).resolve(specifier);
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
27
|
-
return walkNodeModules(specifier, here);
|
|
28
|
-
}
|
|
29
|
-
function walkNodeModules(pkg2, fromPath) {
|
|
30
|
-
let dir2 = dirname(fromPath);
|
|
31
|
-
for (; ; ) {
|
|
32
|
-
const manifest = join(dir2, "node_modules", pkg2, "package.json");
|
|
33
|
-
if (nodeIsFile(manifest)) {
|
|
34
|
-
try {
|
|
35
|
-
return realpathSync(manifest);
|
|
36
|
-
} catch {
|
|
37
|
-
return manifest;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const parent = dirname(dir2);
|
|
41
|
-
if (parent === dir2) return null;
|
|
42
|
-
dir2 = parent;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function nodeResolve(specifier, fromPath) {
|
|
46
|
-
try {
|
|
47
|
-
return createRequire(fromPath).resolve(specifier);
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function nodeIsFile(path) {
|
|
53
|
-
try {
|
|
54
|
-
return statSync(path).isFile();
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function detectPreferMusl(platform = process.platform) {
|
|
60
|
-
if (platform !== "linux") return false;
|
|
61
|
-
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
62
|
-
const header = report?.header;
|
|
63
|
-
return report != null && header?.glibcVersionRuntime === void 0;
|
|
64
|
-
}
|
|
65
|
-
function defaultResolveEnv() {
|
|
66
|
-
return {
|
|
67
|
-
platform: process.platform,
|
|
68
|
-
arch: process.arch,
|
|
69
|
-
preferMusl: detectPreferMusl(),
|
|
70
|
-
// Both resolutions start from this module, which tsup bundles into
|
|
71
|
-
// `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
|
|
72
|
-
// root — so one environment serves the runtime checks and the install
|
|
73
|
-
// verifier alike.
|
|
74
|
-
resolveSelf: nodeResolveSelf,
|
|
75
|
-
resolve: nodeResolve,
|
|
76
|
-
isFile: nodeIsFile
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
var CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
|
|
80
|
-
function claudePlatformPackages(platform, arch, preferMusl) {
|
|
81
|
-
if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
|
|
82
|
-
if (platform === "linux") {
|
|
83
|
-
return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
|
|
84
|
-
}
|
|
85
|
-
return [`${CLAUDE_SDK}-${platform}-${arch}`];
|
|
86
|
-
}
|
|
87
|
-
function resolveClaude(env) {
|
|
88
|
-
const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
|
|
89
|
-
const base = {
|
|
90
|
-
runtime: "claude-code",
|
|
91
|
-
packageName: candidates[0] ?? null,
|
|
92
|
-
platform: env.platform,
|
|
93
|
-
arch: env.arch
|
|
94
|
-
};
|
|
95
|
-
const sdkEntry = env.resolveSelf(CLAUDE_SDK);
|
|
96
|
-
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
97
|
-
let sawPackage = null;
|
|
98
|
-
for (const pkg2 of candidates) {
|
|
99
|
-
const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
|
|
100
|
-
if (!manifest) continue;
|
|
101
|
-
sawPackage ??= pkg2;
|
|
102
|
-
const exe = join(dirname(manifest), env.platform === "win32" ? "claude.exe" : "claude");
|
|
103
|
-
if (env.isFile(exe)) {
|
|
104
|
-
return { ...base, packageName: pkg2, status: "present", path: exe };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
|
|
108
|
-
}
|
|
109
|
-
var CODEX_SDK = "@openai/codex-sdk";
|
|
110
|
-
var CODEX_CLI = "@openai/codex";
|
|
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
|
-
var CODEX_PACKAGE_BY_TRIPLE = {
|
|
130
|
-
"x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
|
|
131
|
-
"aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
|
|
132
|
-
"x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
|
|
133
|
-
"aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
|
|
134
|
-
"x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
|
|
135
|
-
"aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
|
|
136
|
-
};
|
|
137
|
-
function resolveCodex(env) {
|
|
138
|
-
const triple = codexTargetTriple(env.platform, env.arch);
|
|
139
|
-
const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
|
|
140
|
-
const base = {
|
|
141
|
-
runtime: "codex",
|
|
142
|
-
packageName: pkg2 ?? null,
|
|
143
|
-
platform: env.platform,
|
|
144
|
-
arch: env.arch
|
|
145
|
-
};
|
|
146
|
-
if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
|
|
147
|
-
const sdkEntry = env.resolveSelf(CODEX_SDK);
|
|
148
|
-
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
149
|
-
const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
|
|
150
|
-
if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
151
|
-
const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
|
|
152
|
-
if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
153
|
-
const packageRoot = join(dirname(platformManifest), "vendor", triple);
|
|
154
|
-
const exeName = env.platform === "win32" ? "codex.exe" : "codex";
|
|
155
|
-
const current = join(packageRoot, "bin", exeName);
|
|
156
|
-
if (env.isFile(current) && env.isFile(join(packageRoot, "codex-package.json"))) {
|
|
157
|
-
return { ...base, status: "present", path: current };
|
|
158
|
-
}
|
|
159
|
-
const legacy = join(packageRoot, "codex", exeName);
|
|
160
|
-
if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
|
|
161
|
-
return { ...base, status: "executable_missing", path: null };
|
|
162
|
-
}
|
|
163
|
-
function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
|
|
164
|
-
try {
|
|
165
|
-
return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
|
|
166
|
-
} catch {
|
|
167
|
-
return {
|
|
168
|
-
runtime,
|
|
169
|
-
status: "platform_package_missing",
|
|
170
|
-
path: null,
|
|
171
|
-
packageName: null,
|
|
172
|
-
platform: env.platform,
|
|
173
|
-
arch: env.arch
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
var HARNESS_LABEL = {
|
|
178
|
-
"claude-code": "Claude Code",
|
|
179
|
-
codex: "Codex"
|
|
180
|
-
};
|
|
181
|
-
var HARNESS_INTENT_WORD = {
|
|
182
|
-
"claude-code": "connected",
|
|
183
|
-
codex: "enabled"
|
|
184
|
-
};
|
|
185
|
-
function missingNoun(status) {
|
|
186
|
-
return status === "executable_missing" ? "executable" : "platform package";
|
|
187
|
-
}
|
|
188
14
|
var INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
|
|
189
15
|
function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
190
16
|
const real = (path) => {
|
|
@@ -197,23 +23,16 @@ function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
|
197
23
|
const scriptNode = join(real(home), ".cabane", "node") + sep;
|
|
198
24
|
return real(execPath).startsWith(scriptNode) ? "script" : "npm";
|
|
199
25
|
}
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const label = HARNESS_LABEL[r.runtime];
|
|
208
|
-
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.`;
|
|
209
|
-
}
|
|
210
|
-
function harnessIssueNote(runtime, status, install = companionInstall()) {
|
|
211
|
-
const intent = HARNESS_INTENT_WORD[runtime];
|
|
212
|
-
const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
|
|
213
|
-
return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status)} is missing. ${repairAction(install)} Then restart the companion.`;
|
|
26
|
+
function repairRoute(packageRoot = ownRoot(), install = companionInstall()) {
|
|
27
|
+
let root = packageRoot;
|
|
28
|
+
try {
|
|
29
|
+
root = realpathSync(packageRoot);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return root.includes(`${sep}.cabane${sep}versions${sep}`) ? "managed" : install;
|
|
214
33
|
}
|
|
215
|
-
function
|
|
216
|
-
return
|
|
34
|
+
function ownRoot() {
|
|
35
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
217
36
|
}
|
|
218
37
|
|
|
219
38
|
// src/cli-entry.ts
|
|
@@ -241,15 +60,27 @@ function tildePath(path, home = homedir2()) {
|
|
|
241
60
|
}
|
|
242
61
|
|
|
243
62
|
// src/cabane-command.ts
|
|
63
|
+
var INVOKED_COMMAND_ENV = "CABANE_INVOKED_COMMAND";
|
|
64
|
+
var VERSIONS_SEGMENT = `${sep2}.cabane${sep2}versions${sep2}`;
|
|
244
65
|
function installedCommandPath(deps = {}) {
|
|
245
66
|
const install = deps.install ?? companionInstall();
|
|
246
67
|
const home = deps.home ?? homedir3();
|
|
247
68
|
if (install === "script") return join2(home, ".cabane", "bin", "cabane");
|
|
248
69
|
const entry = deps.cliEntry ?? companionCliEntry();
|
|
70
|
+
const own = npmBinFor(entry);
|
|
71
|
+
if (own) return own;
|
|
72
|
+
if (!entry.includes(VERSIONS_SEGMENT)) return null;
|
|
73
|
+
const isExecutable = deps.isExecutable ?? executable;
|
|
74
|
+
const invoked = "invoked" in deps ? deps.invoked : process.env[INVOKED_COMMAND_ENV];
|
|
75
|
+
if (invoked && isExecutable(invoked)) return invoked;
|
|
76
|
+
const besideNode = join2(dirname2(dirname2(deps.execPath ?? process.execPath)), "bin", "cabane");
|
|
77
|
+
return isExecutable(besideNode) ? besideNode : null;
|
|
78
|
+
}
|
|
79
|
+
function npmBinFor(path) {
|
|
249
80
|
const marker = `${sep2}lib${sep2}node_modules${sep2}@cabane${sep2}companion${sep2}`;
|
|
250
|
-
const at =
|
|
81
|
+
const at = path.indexOf(marker);
|
|
251
82
|
if (at < 0) return null;
|
|
252
|
-
return join2(
|
|
83
|
+
return join2(path.slice(0, at), "bin", "cabane");
|
|
253
84
|
}
|
|
254
85
|
function cabaneCommand(deps = {}) {
|
|
255
86
|
const own = installedCommandPath(deps);
|
|
@@ -265,6 +96,9 @@ function cabaneCommand(deps = {}) {
|
|
|
265
96
|
function cabane(rest, deps) {
|
|
266
97
|
return `${cabaneCommand(deps)} ${rest}`;
|
|
267
98
|
}
|
|
99
|
+
function mayNeedUpdate(deps) {
|
|
100
|
+
return `This companion may need an update: run ${cabane("update", deps)}`;
|
|
101
|
+
}
|
|
268
102
|
function firstOnPath(name, path, isExecutable = executable) {
|
|
269
103
|
for (const dir2 of (path ?? "").split(delimiter)) {
|
|
270
104
|
if (!dir2) continue;
|
|
@@ -300,7 +134,7 @@ import {
|
|
|
300
134
|
existsSync as existsSync2
|
|
301
135
|
} from "fs";
|
|
302
136
|
import { homedir as homedir4, userInfo } from "os";
|
|
303
|
-
import { dirname as
|
|
137
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
304
138
|
import { z as z2 } from "zod";
|
|
305
139
|
|
|
306
140
|
// src/errors.ts
|
|
@@ -763,7 +597,7 @@ function loadConfig() {
|
|
|
763
597
|
}
|
|
764
598
|
function saveConfig(cfg) {
|
|
765
599
|
const path = configPath();
|
|
766
|
-
mkdirSync(
|
|
600
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
767
601
|
try {
|
|
768
602
|
chmodSync(cabaneDir(), 448);
|
|
769
603
|
} catch {
|
|
@@ -795,20 +629,228 @@ function requireConfig() {
|
|
|
795
629
|
}
|
|
796
630
|
|
|
797
631
|
// src/dashboard/server.ts
|
|
798
|
-
import { dirname as
|
|
799
|
-
import { fileURLToPath as
|
|
632
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
633
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
800
634
|
import { serve } from "@hono/node-server";
|
|
801
635
|
import { Hono } from "hono";
|
|
802
636
|
|
|
803
637
|
// src/dashboard/routes.ts
|
|
804
638
|
import { openSync as openSync2, readSync, closeSync, fstatSync, existsSync as existsSync3 } from "fs";
|
|
805
639
|
import { readFile } from "fs/promises";
|
|
806
|
-
import { extname, join as
|
|
640
|
+
import { extname, join as join7, normalize } from "path";
|
|
807
641
|
import { streamSSE } from "hono/streaming";
|
|
808
642
|
|
|
809
643
|
// src/harness-copy.ts
|
|
810
644
|
import { homedir as homedir5 } from "os";
|
|
811
|
-
import { join as
|
|
645
|
+
import { join as join5 } from "path";
|
|
646
|
+
|
|
647
|
+
// src/bundled-binaries.ts
|
|
648
|
+
import { realpathSync as realpathSync3, statSync } from "fs";
|
|
649
|
+
import { createRequire } from "module";
|
|
650
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
651
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
652
|
+
function nodeResolveSelf(specifier) {
|
|
653
|
+
const here = fileURLToPath3(import.meta.url);
|
|
654
|
+
if (typeof import.meta.resolve === "function") {
|
|
655
|
+
try {
|
|
656
|
+
return fileURLToPath3(import.meta.resolve(specifier));
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
return createRequire(here).resolve(specifier);
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
return walkNodeModules(specifier, here);
|
|
665
|
+
}
|
|
666
|
+
function walkNodeModules(pkg2, fromPath) {
|
|
667
|
+
let dir2 = dirname4(fromPath);
|
|
668
|
+
for (; ; ) {
|
|
669
|
+
const manifest = join4(dir2, "node_modules", pkg2, "package.json");
|
|
670
|
+
if (nodeIsFile(manifest)) {
|
|
671
|
+
try {
|
|
672
|
+
return realpathSync3(manifest);
|
|
673
|
+
} catch {
|
|
674
|
+
return manifest;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const parent = dirname4(dir2);
|
|
678
|
+
if (parent === dir2) return null;
|
|
679
|
+
dir2 = parent;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function nodeResolve(specifier, fromPath) {
|
|
683
|
+
try {
|
|
684
|
+
return createRequire(fromPath).resolve(specifier);
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function nodeIsFile(path) {
|
|
690
|
+
try {
|
|
691
|
+
return statSync(path).isFile();
|
|
692
|
+
} catch {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function detectPreferMusl(platform = process.platform) {
|
|
697
|
+
if (platform !== "linux") return false;
|
|
698
|
+
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
699
|
+
const header = report?.header;
|
|
700
|
+
return report != null && header?.glibcVersionRuntime === void 0;
|
|
701
|
+
}
|
|
702
|
+
function defaultResolveEnv() {
|
|
703
|
+
return {
|
|
704
|
+
platform: process.platform,
|
|
705
|
+
arch: process.arch,
|
|
706
|
+
preferMusl: detectPreferMusl(),
|
|
707
|
+
// Both resolutions start from this module, which tsup bundles into
|
|
708
|
+
// `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
|
|
709
|
+
// root — so one environment serves the runtime checks and the install
|
|
710
|
+
// verifier alike.
|
|
711
|
+
resolveSelf: nodeResolveSelf,
|
|
712
|
+
resolve: nodeResolve,
|
|
713
|
+
isFile: nodeIsFile
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
var CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
|
|
717
|
+
function claudePlatformPackages(platform, arch, preferMusl) {
|
|
718
|
+
if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
|
|
719
|
+
if (platform === "linux") {
|
|
720
|
+
return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
|
|
721
|
+
}
|
|
722
|
+
return [`${CLAUDE_SDK}-${platform}-${arch}`];
|
|
723
|
+
}
|
|
724
|
+
function resolveClaude(env) {
|
|
725
|
+
const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
|
|
726
|
+
const base = {
|
|
727
|
+
runtime: "claude-code",
|
|
728
|
+
packageName: candidates[0] ?? null,
|
|
729
|
+
platform: env.platform,
|
|
730
|
+
arch: env.arch
|
|
731
|
+
};
|
|
732
|
+
const sdkEntry = env.resolveSelf(CLAUDE_SDK);
|
|
733
|
+
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
734
|
+
let sawPackage = null;
|
|
735
|
+
for (const pkg2 of candidates) {
|
|
736
|
+
const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
|
|
737
|
+
if (!manifest) continue;
|
|
738
|
+
sawPackage ??= pkg2;
|
|
739
|
+
const exe = join4(dirname4(manifest), env.platform === "win32" ? "claude.exe" : "claude");
|
|
740
|
+
if (env.isFile(exe)) {
|
|
741
|
+
return { ...base, packageName: pkg2, status: "present", path: exe };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
|
|
745
|
+
}
|
|
746
|
+
var CODEX_SDK = "@openai/codex-sdk";
|
|
747
|
+
var CODEX_CLI = "@openai/codex";
|
|
748
|
+
function codexTargetTriple(platform, arch) {
|
|
749
|
+
if (platform === "linux" || platform === "android") {
|
|
750
|
+
if (arch === "x64") return "x86_64-unknown-linux-musl";
|
|
751
|
+
if (arch === "arm64") return "aarch64-unknown-linux-musl";
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
if (platform === "darwin") {
|
|
755
|
+
if (arch === "x64") return "x86_64-apple-darwin";
|
|
756
|
+
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
if (platform === "win32") {
|
|
760
|
+
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
761
|
+
if (arch === "arm64") return "aarch64-pc-windows-msvc";
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
var CODEX_PACKAGE_BY_TRIPLE = {
|
|
767
|
+
"x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
|
|
768
|
+
"aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
|
|
769
|
+
"x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
|
|
770
|
+
"aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
|
|
771
|
+
"x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
|
|
772
|
+
"aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
|
|
773
|
+
};
|
|
774
|
+
function resolveCodex(env) {
|
|
775
|
+
const triple = codexTargetTriple(env.platform, env.arch);
|
|
776
|
+
const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
|
|
777
|
+
const base = {
|
|
778
|
+
runtime: "codex",
|
|
779
|
+
packageName: pkg2 ?? null,
|
|
780
|
+
platform: env.platform,
|
|
781
|
+
arch: env.arch
|
|
782
|
+
};
|
|
783
|
+
if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
|
|
784
|
+
const sdkEntry = env.resolveSelf(CODEX_SDK);
|
|
785
|
+
if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
|
|
786
|
+
const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
|
|
787
|
+
if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
788
|
+
const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
|
|
789
|
+
if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
790
|
+
const packageRoot = join4(dirname4(platformManifest), "vendor", triple);
|
|
791
|
+
const exeName = env.platform === "win32" ? "codex.exe" : "codex";
|
|
792
|
+
const current = join4(packageRoot, "bin", exeName);
|
|
793
|
+
if (env.isFile(current) && env.isFile(join4(packageRoot, "codex-package.json"))) {
|
|
794
|
+
return { ...base, status: "present", path: current };
|
|
795
|
+
}
|
|
796
|
+
const legacy = join4(packageRoot, "codex", exeName);
|
|
797
|
+
if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
|
|
798
|
+
return { ...base, status: "executable_missing", path: null };
|
|
799
|
+
}
|
|
800
|
+
function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
|
|
801
|
+
try {
|
|
802
|
+
return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
|
|
803
|
+
} catch {
|
|
804
|
+
return {
|
|
805
|
+
runtime,
|
|
806
|
+
status: "platform_package_missing",
|
|
807
|
+
path: null,
|
|
808
|
+
packageName: null,
|
|
809
|
+
platform: env.platform,
|
|
810
|
+
arch: env.arch
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
var HARNESS_LABEL = {
|
|
815
|
+
"claude-code": "Claude Code",
|
|
816
|
+
codex: "Codex"
|
|
817
|
+
};
|
|
818
|
+
var HARNESS_INTENT_WORD = {
|
|
819
|
+
"claude-code": "connected",
|
|
820
|
+
codex: "enabled"
|
|
821
|
+
};
|
|
822
|
+
function missingNoun(status) {
|
|
823
|
+
return status === "executable_missing" ? "executable" : "platform package";
|
|
824
|
+
}
|
|
825
|
+
function repairCommands(route = repairRoute(), spell = cabane) {
|
|
826
|
+
if (route === "managed") return [spell("stop"), spell("update")];
|
|
827
|
+
if (route === "script") return [spell("stop"), INSTALL_SCRIPT_COMMAND];
|
|
828
|
+
return ["npm i -g @cabane/companion --include=optional"];
|
|
829
|
+
}
|
|
830
|
+
function repairAction(route = repairRoute(), spell = cabane) {
|
|
831
|
+
const [first, ...rest] = repairCommands(route, spell);
|
|
832
|
+
if (rest.length === 0) return `Reinstall with \`${first}\`.`;
|
|
833
|
+
return `Run \`${first}\`, then \`${rest.join("`, then `")}\`. ${STOP_WARNING}`;
|
|
834
|
+
}
|
|
835
|
+
var STOP_WARNING = "Stopping ends any turn in progress.";
|
|
836
|
+
function repairSteps(route = repairRoute()) {
|
|
837
|
+
return repairCommands(route).join(", then ");
|
|
838
|
+
}
|
|
839
|
+
function repairCost(route = repairRoute()) {
|
|
840
|
+
return repairCommands(route).length > 1 ? STOP_WARNING : "";
|
|
841
|
+
}
|
|
842
|
+
function startupWarning(r) {
|
|
843
|
+
const label = HARNESS_LABEL[r.runtime];
|
|
844
|
+
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.`;
|
|
845
|
+
}
|
|
846
|
+
function harnessIssueNote(runtime, status, route = repairRoute(), spell = cabane) {
|
|
847
|
+
const intent = HARNESS_INTENT_WORD[runtime];
|
|
848
|
+
const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
|
|
849
|
+
return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status)} is missing. ${repairAction(route, spell)} Then restart the companion.`;
|
|
850
|
+
}
|
|
851
|
+
function turnIncompleteCopy(runtime) {
|
|
852
|
+
return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
|
|
853
|
+
}
|
|
812
854
|
|
|
813
855
|
// src/harness-usable.ts
|
|
814
856
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -964,7 +1006,12 @@ function runtimeIssuesFor(gates) {
|
|
|
964
1006
|
for (const name of ["claude-code", "codex"]) {
|
|
965
1007
|
const gate = name === "codex" ? gates.codex : gates.claudeCode;
|
|
966
1008
|
if (!gate.intended || gate.bundled === "present") continue;
|
|
967
|
-
issues.push({
|
|
1009
|
+
issues.push({
|
|
1010
|
+
name,
|
|
1011
|
+
reason: gate.bundled,
|
|
1012
|
+
install: gates.install,
|
|
1013
|
+
...gates.managed ? { managed: true } : {}
|
|
1014
|
+
});
|
|
968
1015
|
}
|
|
969
1016
|
return issues;
|
|
970
1017
|
}
|
|
@@ -1225,16 +1272,16 @@ function failureText(runtime, reason) {
|
|
|
1225
1272
|
case "timeout":
|
|
1226
1273
|
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
1227
1274
|
case "spawn":
|
|
1228
|
-
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${
|
|
1275
|
+
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())}`;
|
|
1229
1276
|
case "config":
|
|
1230
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${
|
|
1277
|
+
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.`;
|
|
1231
1278
|
case "unreadable":
|
|
1232
1279
|
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.`;
|
|
1233
1280
|
}
|
|
1234
1281
|
}
|
|
1235
1282
|
function bundleMissingText(runtime) {
|
|
1236
1283
|
const label = HARNESS_LABELS[runtime];
|
|
1237
|
-
return `This Cabane install is missing its bundled ${label}. Repair it with ${
|
|
1284
|
+
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
1238
1285
|
}
|
|
1239
1286
|
function unusableReason(runtime, check) {
|
|
1240
1287
|
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
@@ -1271,6 +1318,9 @@ function connectLines(outcome, opts = {}) {
|
|
|
1271
1318
|
]
|
|
1272
1319
|
};
|
|
1273
1320
|
}
|
|
1321
|
+
function spaced(sentence) {
|
|
1322
|
+
return sentence ? ` ${sentence}` : "";
|
|
1323
|
+
}
|
|
1274
1324
|
|
|
1275
1325
|
// src/logger.ts
|
|
1276
1326
|
import {
|
|
@@ -1280,11 +1330,11 @@ import {
|
|
|
1280
1330
|
renameSync as renameSync2,
|
|
1281
1331
|
statSync as statSync2
|
|
1282
1332
|
} from "fs";
|
|
1283
|
-
import { dirname as
|
|
1333
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
1284
1334
|
import { stripVTControlCharacters } from "util";
|
|
1285
1335
|
import pino from "pino";
|
|
1286
1336
|
function companionLogPath() {
|
|
1287
|
-
return
|
|
1337
|
+
return join6(cabaneDir(), "companion.log");
|
|
1288
1338
|
}
|
|
1289
1339
|
function humanText(value) {
|
|
1290
1340
|
return stripVTControlCharacters(String(value ?? "")).replace(
|
|
@@ -1392,7 +1442,7 @@ function configureLogger(log, config) {
|
|
|
1392
1442
|
}
|
|
1393
1443
|
function createLogger(destinations = {}, options = {}) {
|
|
1394
1444
|
const path = companionLogPath();
|
|
1395
|
-
if (!destinations.file) mkdirSync2(
|
|
1445
|
+
if (!destinations.file) mkdirSync2(dirname5(path), { recursive: true });
|
|
1396
1446
|
const file = destinations.file ?? new RotatingLog(path);
|
|
1397
1447
|
const state = { format: options.logFormat ?? "human" };
|
|
1398
1448
|
const streams = [];
|
|
@@ -1684,13 +1734,13 @@ var CONTENT_TYPES = {
|
|
|
1684
1734
|
function registerRoutes(app, deps) {
|
|
1685
1735
|
const { supervisor, hub, staticDir } = deps;
|
|
1686
1736
|
app.get("/", async (c) => {
|
|
1687
|
-
const html = await readFile(
|
|
1737
|
+
const html = await readFile(join7(staticDir, "index.html"), "utf8");
|
|
1688
1738
|
return c.html(html);
|
|
1689
1739
|
});
|
|
1690
1740
|
app.get("/static/:file", async (c) => {
|
|
1691
1741
|
const file = c.req.param("file");
|
|
1692
1742
|
const safe2 = normalize(file).replace(/^(\.\.[/\\])+/, "");
|
|
1693
|
-
const full =
|
|
1743
|
+
const full = join7(staticDir, safe2);
|
|
1694
1744
|
if (!full.startsWith(staticDir) || !existsSync3(full)) return c.notFound();
|
|
1695
1745
|
const body = await readFile(full);
|
|
1696
1746
|
const type = CONTENT_TYPES[extname(full).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -1902,14 +1952,14 @@ function isAddrInUse(err) {
|
|
|
1902
1952
|
return Boolean(err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE");
|
|
1903
1953
|
}
|
|
1904
1954
|
function resolveStaticDir() {
|
|
1905
|
-
return
|
|
1955
|
+
return join8(dirname6(fileURLToPath4(import.meta.url)), "static");
|
|
1906
1956
|
}
|
|
1907
1957
|
|
|
1908
1958
|
// src/control-socket.ts
|
|
1909
1959
|
import { createHash } from "crypto";
|
|
1910
1960
|
import { chmodSync as chmodSync2, existsSync as existsSync4, rmSync as rmSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
1911
1961
|
import { createServer, connect } from "net";
|
|
1912
|
-
import { join as
|
|
1962
|
+
import { join as join9 } from "path";
|
|
1913
1963
|
var CONTROL_TIMEOUT_MS = 1e3;
|
|
1914
1964
|
var MAX_UNIX_SOCKET_PATH_BYTES = 103;
|
|
1915
1965
|
function controlSocketPath() {
|
|
@@ -1918,11 +1968,11 @@ function controlSocketPath() {
|
|
|
1918
1968
|
if (process.platform === "win32") {
|
|
1919
1969
|
return `\\\\.\\pipe\\cabane-companion-${key}`;
|
|
1920
1970
|
}
|
|
1921
|
-
const besideRuntime =
|
|
1971
|
+
const besideRuntime = join9(dir2, "companion.sock");
|
|
1922
1972
|
if (Buffer.byteLength(besideRuntime) <= MAX_UNIX_SOCKET_PATH_BYTES) return besideRuntime;
|
|
1923
1973
|
const filename = `cabane-companion-${key}.sock`;
|
|
1924
|
-
const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync4(process.env.XDG_RUNTIME_DIR) ?
|
|
1925
|
-
return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath :
|
|
1974
|
+
const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync4(process.env.XDG_RUNTIME_DIR) ? join9(process.env.XDG_RUNTIME_DIR, filename) : null;
|
|
1975
|
+
return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath : join9("/tmp", filename);
|
|
1926
1976
|
}
|
|
1927
1977
|
async function startControlServer(handlers) {
|
|
1928
1978
|
const path = controlSocketPath();
|
|
@@ -2122,10 +2172,10 @@ import {
|
|
|
2122
2172
|
openSync as openSync3,
|
|
2123
2173
|
closeSync as closeSync2
|
|
2124
2174
|
} from "fs";
|
|
2125
|
-
import { join as
|
|
2175
|
+
import { join as join10 } from "path";
|
|
2126
2176
|
var PROBE_TIMEOUT_MS2 = 1e3;
|
|
2127
2177
|
function runtimePath() {
|
|
2128
|
-
return
|
|
2178
|
+
return join10(cabaneDir(), "runtime.json");
|
|
2129
2179
|
}
|
|
2130
2180
|
function serialize(state) {
|
|
2131
2181
|
return JSON.stringify(state, null, 2) + "\n";
|
|
@@ -2344,7 +2394,7 @@ function turnFailureCopy(reason, runtime) {
|
|
|
2344
2394
|
if (reason?.startsWith("missing_secret:"))
|
|
2345
2395
|
return `A required secret is missing: ${humanText(reason.slice(15))}. Add it to ~/.cabane/secrets.json, then reply in Cabane to try again.`;
|
|
2346
2396
|
if (reason?.startsWith("runtime_incomplete:"))
|
|
2347
|
-
return
|
|
2397
|
+
return `The agent installation is incomplete. ${repairAction()} Then restart the companion.`;
|
|
2348
2398
|
if (/^(runtime|codex|opencode)_unavailable:/.test(reason ?? ""))
|
|
2349
2399
|
return "The agent is unavailable on this machine. Enable it here or choose another connector in Cabane.";
|
|
2350
2400
|
if (reason?.startsWith("model_unavailable:"))
|
|
@@ -2619,7 +2669,7 @@ var CabaneApi = class {
|
|
|
2619
2669
|
}
|
|
2620
2670
|
this.opts.log?.warn(
|
|
2621
2671
|
{ kind: entry.kind, turnId: entry.turnId, seq: entry.seq, ...apiErrorLogFields(err) },
|
|
2622
|
-
|
|
2672
|
+
`Cabane rejected a saved update; it could not be delivered. ${mayNeedUpdate()}`
|
|
2623
2673
|
);
|
|
2624
2674
|
outbox.remove(entry.turnId, entry.seq);
|
|
2625
2675
|
progressed = true;
|
|
@@ -3031,10 +3081,10 @@ import {
|
|
|
3031
3081
|
rmSync as rmSync4,
|
|
3032
3082
|
writeFileSync as writeFileSync3
|
|
3033
3083
|
} from "fs";
|
|
3034
|
-
import { dirname as
|
|
3084
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
3035
3085
|
import { z as z4 } from "zod";
|
|
3036
3086
|
function credentialsPath() {
|
|
3037
|
-
return
|
|
3087
|
+
return join11(cabaneDir(), "credentials.json");
|
|
3038
3088
|
}
|
|
3039
3089
|
var credentialStoreSchema = z4.record(z4.string(), z4.string());
|
|
3040
3090
|
function load() {
|
|
@@ -3056,7 +3106,7 @@ function load() {
|
|
|
3056
3106
|
}
|
|
3057
3107
|
function save(map) {
|
|
3058
3108
|
const path = credentialsPath();
|
|
3059
|
-
mkdirSync5(
|
|
3109
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
3060
3110
|
try {
|
|
3061
3111
|
chmodSync3(cabaneDir(), 448);
|
|
3062
3112
|
} catch {
|
|
@@ -3102,13 +3152,13 @@ function pruneCredentials(keepAgentIds) {
|
|
|
3102
3152
|
|
|
3103
3153
|
// src/dispatch-dedupe.ts
|
|
3104
3154
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, renameSync as renameSync4, existsSync as existsSync7 } from "fs";
|
|
3105
|
-
import { join as
|
|
3155
|
+
import { join as join12 } from "path";
|
|
3106
3156
|
var MAX_IDS = 256;
|
|
3107
3157
|
function dir(log) {
|
|
3108
|
-
return
|
|
3158
|
+
return join12(cabaneDir(), log);
|
|
3109
3159
|
}
|
|
3110
3160
|
function pathFor(log, workspaceId) {
|
|
3111
|
-
return
|
|
3161
|
+
return join12(dir(log), encodeURIComponent(workspaceId));
|
|
3112
3162
|
}
|
|
3113
3163
|
function readIds(log, workspaceId) {
|
|
3114
3164
|
const path = pathFor(log, workspaceId);
|
|
@@ -3145,10 +3195,10 @@ function markCompleted(workspaceId, eventId) {
|
|
|
3145
3195
|
}
|
|
3146
3196
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
3147
3197
|
function resumeDir() {
|
|
3148
|
-
return
|
|
3198
|
+
return join12(cabaneDir(), "resume-attempts");
|
|
3149
3199
|
}
|
|
3150
3200
|
function resumePathFor(workspaceId) {
|
|
3151
|
-
return
|
|
3201
|
+
return join12(resumeDir(), encodeURIComponent(workspaceId));
|
|
3152
3202
|
}
|
|
3153
3203
|
function readResumeCounts(workspaceId) {
|
|
3154
3204
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3184,10 +3234,10 @@ function bumpResumeAttempt(workspaceId, eventId) {
|
|
|
3184
3234
|
return next;
|
|
3185
3235
|
}
|
|
3186
3236
|
function turnDir() {
|
|
3187
|
-
return
|
|
3237
|
+
return join12(cabaneDir(), "turns");
|
|
3188
3238
|
}
|
|
3189
3239
|
function turnPathFor(workspaceId) {
|
|
3190
|
-
return
|
|
3240
|
+
return join12(turnDir(), encodeURIComponent(workspaceId));
|
|
3191
3241
|
}
|
|
3192
3242
|
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;
|
|
3193
3243
|
function readTurnIds(workspaceId) {
|
|
@@ -8182,7 +8232,7 @@ var ConnectorHealthStore = class {
|
|
|
8182
8232
|
// src/dispatcher.ts
|
|
8183
8233
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8184
8234
|
import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
8185
|
-
import { join as
|
|
8235
|
+
import { join as join18 } from "path";
|
|
8186
8236
|
|
|
8187
8237
|
// src/runner/launch.ts
|
|
8188
8238
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -8641,11 +8691,11 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8641
8691
|
|
|
8642
8692
|
// src/secrets.ts
|
|
8643
8693
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
8644
|
-
import { join as
|
|
8694
|
+
import { join as join13 } from "path";
|
|
8645
8695
|
import { z as z14 } from "zod";
|
|
8646
8696
|
var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
8647
8697
|
function secretsPath() {
|
|
8648
|
-
return
|
|
8698
|
+
return join13(cabaneDir(), "secrets.json");
|
|
8649
8699
|
}
|
|
8650
8700
|
var secretStoreSchema = z14.record(z14.string(), z14.string());
|
|
8651
8701
|
function loadSecretStore() {
|
|
@@ -9615,11 +9665,11 @@ function trimSlash2(s) {
|
|
|
9615
9665
|
// src/codex-instructions.ts
|
|
9616
9666
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
9617
9667
|
import { tmpdir } from "os";
|
|
9618
|
-
import { join as
|
|
9668
|
+
import { join as join14 } from "path";
|
|
9619
9669
|
var PREFIX = "cabane-codex-instructions-";
|
|
9620
9670
|
async function writeCodexInstructionsFile(contents) {
|
|
9621
|
-
const dir2 = await mkdtemp(
|
|
9622
|
-
const path =
|
|
9671
|
+
const dir2 = await mkdtemp(join14(tmpdir(), PREFIX));
|
|
9672
|
+
const path = join14(dir2, "instructions.md");
|
|
9623
9673
|
await writeFile(path, contents, { encoding: "utf8", mode: 384 });
|
|
9624
9674
|
return {
|
|
9625
9675
|
path,
|
|
@@ -9632,7 +9682,7 @@ async function writeCodexInstructionsFile(contents) {
|
|
|
9632
9682
|
// src/codex-images.ts
|
|
9633
9683
|
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
9634
9684
|
import { tmpdir as tmpdir2 } from "os";
|
|
9635
|
-
import { join as
|
|
9685
|
+
import { join as join15 } from "path";
|
|
9636
9686
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
9637
9687
|
var EXTENSIONS = {
|
|
9638
9688
|
"image/png": "png",
|
|
@@ -9645,7 +9695,7 @@ function createCodexImagePreparer(opts) {
|
|
|
9645
9695
|
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
9646
9696
|
return async (urls, signal) => {
|
|
9647
9697
|
signal.throwIfAborted();
|
|
9648
|
-
const dir2 = await mkdtemp2(
|
|
9698
|
+
const dir2 = await mkdtemp2(join15(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
|
|
9649
9699
|
const cleanup = async () => {
|
|
9650
9700
|
try {
|
|
9651
9701
|
await rm2(dir2, { recursive: true, force: true });
|
|
@@ -9666,7 +9716,7 @@ function createCodexImagePreparer(opts) {
|
|
|
9666
9716
|
const ext = EXTENSIONS[type];
|
|
9667
9717
|
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
9668
9718
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
9669
|
-
const path =
|
|
9719
|
+
const path = join15(dir2, `image-${index + 1}.${ext}`);
|
|
9670
9720
|
await writeFile2(path, bytes, { mode: 384 });
|
|
9671
9721
|
return path;
|
|
9672
9722
|
} catch (err) {
|
|
@@ -9788,15 +9838,15 @@ function reportFailure(config, body) {
|
|
|
9788
9838
|
|
|
9789
9839
|
// src/prepared.ts
|
|
9790
9840
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "fs";
|
|
9791
|
-
import { join as
|
|
9841
|
+
import { join as join16 } from "path";
|
|
9792
9842
|
function dirFor(workspaceId) {
|
|
9793
|
-
return
|
|
9843
|
+
return join16(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
9794
9844
|
}
|
|
9795
9845
|
function conversationDir(workspaceId, conversationId) {
|
|
9796
|
-
return
|
|
9846
|
+
return join16(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
9797
9847
|
}
|
|
9798
9848
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
9799
|
-
return
|
|
9849
|
+
return join16(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
9800
9850
|
}
|
|
9801
9851
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
9802
9852
|
const path = pathFor2(workspaceId, conversationId, agentId);
|
|
@@ -9828,9 +9878,9 @@ function clearPrepared(workspaceId, conversationId, agentId) {
|
|
|
9828
9878
|
|
|
9829
9879
|
// src/transcript-writer.ts
|
|
9830
9880
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
9831
|
-
import { basename, dirname as
|
|
9881
|
+
import { basename as basename2, dirname as dirname8, join as join17 } from "path";
|
|
9832
9882
|
function transcriptsDir() {
|
|
9833
|
-
return
|
|
9883
|
+
return join17(runnerStateDir(), "transcripts");
|
|
9834
9884
|
}
|
|
9835
9885
|
var RETAIN = 200;
|
|
9836
9886
|
var ANOMALY_RETAIN = 50;
|
|
@@ -9840,7 +9890,7 @@ var TranscriptWriter = class {
|
|
|
9840
9890
|
onWarn;
|
|
9841
9891
|
constructor(dir2, meta, onWarn) {
|
|
9842
9892
|
this.onWarn = onWarn;
|
|
9843
|
-
this.path =
|
|
9893
|
+
this.path = join17(dir2, fileName(meta));
|
|
9844
9894
|
try {
|
|
9845
9895
|
mkdirSync8(dir2, { recursive: true });
|
|
9846
9896
|
try {
|
|
@@ -9869,10 +9919,10 @@ var TranscriptWriter = class {
|
|
|
9869
9919
|
preserveAnomaly() {
|
|
9870
9920
|
if (this.broken) return;
|
|
9871
9921
|
try {
|
|
9872
|
-
const dir2 =
|
|
9922
|
+
const dir2 = join17(dirname8(this.path), "anomalies");
|
|
9873
9923
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
9874
9924
|
chmodSync4(dir2, 448);
|
|
9875
|
-
const target =
|
|
9925
|
+
const target = join17(dir2, basename2(this.path));
|
|
9876
9926
|
copyFileSync(this.path, target);
|
|
9877
9927
|
chmodSync4(target, 384);
|
|
9878
9928
|
pruneOld(dir2, ANOMALY_RETAIN);
|
|
@@ -9918,7 +9968,7 @@ function pruneOld(dir2, retain) {
|
|
|
9918
9968
|
const drop = files.sort().slice(0, files.length - retain);
|
|
9919
9969
|
for (const f of drop) {
|
|
9920
9970
|
try {
|
|
9921
|
-
rmSync6(
|
|
9971
|
+
rmSync6(join17(dir2, f), { force: true });
|
|
9922
9972
|
} catch {
|
|
9923
9973
|
}
|
|
9924
9974
|
}
|
|
@@ -9932,7 +9982,7 @@ var TurnCommitter = class {
|
|
|
9932
9982
|
this.onError = (err, hook) => {
|
|
9933
9983
|
deps.log.warn(
|
|
9934
9984
|
{ ...apiErrorLogFields(err), hook },
|
|
9935
|
-
`Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane.
|
|
9985
|
+
`Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane. ${mayNeedUpdate()}`
|
|
9936
9986
|
);
|
|
9937
9987
|
deps.onCommitFailed?.(err);
|
|
9938
9988
|
};
|
|
@@ -10072,7 +10122,7 @@ async function postIncompleteNotice(ctx2, harness, resolution) {
|
|
|
10072
10122
|
} catch (err) {
|
|
10073
10123
|
ctx2.log.warn(
|
|
10074
10124
|
{ ...apiErrorLogFields(err) },
|
|
10075
|
-
|
|
10125
|
+
`Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
|
|
10076
10126
|
);
|
|
10077
10127
|
return false;
|
|
10078
10128
|
}
|
|
@@ -10374,7 +10424,7 @@ var TurnExecution = class {
|
|
|
10374
10424
|
} else {
|
|
10375
10425
|
turnLog.warn(
|
|
10376
10426
|
{ ...apiErrorLogFields(err), turnId },
|
|
10377
|
-
|
|
10427
|
+
`Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
|
|
10378
10428
|
);
|
|
10379
10429
|
}
|
|
10380
10430
|
}
|
|
@@ -10852,7 +10902,7 @@ var TurnExecution = class {
|
|
|
10852
10902
|
} catch (postErr) {
|
|
10853
10903
|
turnLog.warn(
|
|
10854
10904
|
{ ...apiErrorLogFields(postErr) },
|
|
10855
|
-
|
|
10905
|
+
`Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
|
|
10856
10906
|
);
|
|
10857
10907
|
const reason = `runtime_unavailable:${err.runtime}`;
|
|
10858
10908
|
throw this.concluded(reason, reason);
|
|
@@ -11061,7 +11111,7 @@ var TurnExecution = class {
|
|
|
11061
11111
|
stateLength: event.state.length,
|
|
11062
11112
|
runtime: turnRuntime
|
|
11063
11113
|
},
|
|
11064
|
-
|
|
11114
|
+
`Couldn't save the agent's memory in Cabane. The next turn will start a new session. ${mayNeedUpdate()}`
|
|
11065
11115
|
);
|
|
11066
11116
|
} else {
|
|
11067
11117
|
turnLog.warn(
|
|
@@ -11225,7 +11275,7 @@ var TurnExecution = class {
|
|
|
11225
11275
|
} else {
|
|
11226
11276
|
turnLog.warn(
|
|
11227
11277
|
{ ...apiErrorLogFields(err) },
|
|
11228
|
-
|
|
11278
|
+
`Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
|
|
11229
11279
|
);
|
|
11230
11280
|
}
|
|
11231
11281
|
}
|
|
@@ -11298,7 +11348,7 @@ function checkoutState(cwd) {
|
|
|
11298
11348
|
if (entries.length === 0) {
|
|
11299
11349
|
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
11300
11350
|
}
|
|
11301
|
-
const gitPath =
|
|
11351
|
+
const gitPath = join18(cwd, ".git");
|
|
11302
11352
|
if (!existsSync11(gitPath)) return { ok: true, reason: "usable" };
|
|
11303
11353
|
let stat;
|
|
11304
11354
|
try {
|
|
@@ -11306,7 +11356,7 @@ function checkoutState(cwd) {
|
|
|
11306
11356
|
} catch (error) {
|
|
11307
11357
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
11308
11358
|
}
|
|
11309
|
-
if (stat.isDirectory() && !existsSync11(
|
|
11359
|
+
if (stat.isDirectory() && !existsSync11(join18(gitPath, "HEAD")))
|
|
11310
11360
|
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
11311
11361
|
return { ok: true, reason: "usable" };
|
|
11312
11362
|
}
|
|
@@ -11524,7 +11574,7 @@ import {
|
|
|
11524
11574
|
statSync as statSync4,
|
|
11525
11575
|
writeFileSync as writeFileSync6
|
|
11526
11576
|
} from "fs";
|
|
11527
|
-
import { join as
|
|
11577
|
+
import { join as join21 } from "path";
|
|
11528
11578
|
import { Writable } from "stream";
|
|
11529
11579
|
|
|
11530
11580
|
// src/providers/docker-cli.ts
|
|
@@ -11718,7 +11768,7 @@ import {
|
|
|
11718
11768
|
renameSync as renameSync5,
|
|
11719
11769
|
rmSync as rmSync8
|
|
11720
11770
|
} from "fs";
|
|
11721
|
-
import { join as
|
|
11771
|
+
import { join as join19 } from "path";
|
|
11722
11772
|
function routes(t) {
|
|
11723
11773
|
const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
|
|
11724
11774
|
const agent = `${conv}/participants/agents/${t.agentId}`;
|
|
@@ -11764,7 +11814,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11764
11814
|
const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
|
|
11765
11815
|
let entry = {};
|
|
11766
11816
|
try {
|
|
11767
|
-
entry = JSON.parse(readFileSync8(
|
|
11817
|
+
entry = JSON.parse(readFileSync8(join19(from, name), "utf8"));
|
|
11768
11818
|
} catch {
|
|
11769
11819
|
}
|
|
11770
11820
|
return { name, entry };
|
|
@@ -11777,7 +11827,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11777
11827
|
let published = 0;
|
|
11778
11828
|
for (const { name } of staged) {
|
|
11779
11829
|
try {
|
|
11780
|
-
move(
|
|
11830
|
+
move(join19(from, name), join19(to, name));
|
|
11781
11831
|
} catch {
|
|
11782
11832
|
return { published, complete: false };
|
|
11783
11833
|
}
|
|
@@ -11789,9 +11839,9 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11789
11839
|
|
|
11790
11840
|
// src/providers/runner-base.ts
|
|
11791
11841
|
import { createHash as createHash3 } from "crypto";
|
|
11792
|
-
import { existsSync as existsSync13, realpathSync as
|
|
11793
|
-
import { dirname as
|
|
11794
|
-
import { fileURLToPath as
|
|
11842
|
+
import { existsSync as existsSync13, realpathSync as realpathSync4 } from "fs";
|
|
11843
|
+
import { dirname as dirname9, join as join20, sep as sep3 } from "path";
|
|
11844
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11795
11845
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
11796
11846
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
11797
11847
|
RUN apt-get update \\
|
|
@@ -11835,17 +11885,17 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
11835
11885
|
}
|
|
11836
11886
|
return true;
|
|
11837
11887
|
}
|
|
11838
|
-
function ownInstall(moduleFile =
|
|
11839
|
-
let dir2 =
|
|
11888
|
+
function ownInstall(moduleFile = fileURLToPath5(import.meta.url)) {
|
|
11889
|
+
let dir2 = dirname9(moduleFile);
|
|
11840
11890
|
for (let i = 0; i < 4; i++) {
|
|
11841
|
-
if (existsSync13(
|
|
11842
|
-
dir2 =
|
|
11891
|
+
if (existsSync13(join20(dir2, "dist", "cli.js")) && existsSync13(join20(dir2, "package.json"))) break;
|
|
11892
|
+
dir2 = dirname9(dir2);
|
|
11843
11893
|
}
|
|
11844
|
-
if (!existsSync13(
|
|
11845
|
-
const sdk =
|
|
11894
|
+
if (!existsSync13(join20(dir2, "dist", "cli.js"))) return null;
|
|
11895
|
+
const sdk = join20(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
11846
11896
|
if (!existsSync13(sdk)) return null;
|
|
11847
|
-
const root =
|
|
11848
|
-
return
|
|
11897
|
+
const root = realpathSync4(dir2);
|
|
11898
|
+
return realpathSync4(sdk).startsWith(root + sep3) ? root : null;
|
|
11849
11899
|
}
|
|
11850
11900
|
function companionSource(input) {
|
|
11851
11901
|
if (input.configured) {
|
|
@@ -11981,39 +12031,39 @@ function networkCreateError(stderr) {
|
|
|
11981
12031
|
}
|
|
11982
12032
|
function sessionArchivePath(deviceDir, attemptId) {
|
|
11983
12033
|
if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
|
|
11984
|
-
return
|
|
12034
|
+
return join21(deviceDir, "attempts", attemptId, "session.tar");
|
|
11985
12035
|
}
|
|
11986
12036
|
function pruneOtherAttempts(deviceDir, pair, keep) {
|
|
11987
|
-
const root =
|
|
12037
|
+
const root = join21(deviceDir, "attempts");
|
|
11988
12038
|
if (!existsSync14(root)) return;
|
|
11989
12039
|
for (const id of readdirSync4(root)) {
|
|
11990
12040
|
if (id === keep || !UUID_RE.test(id)) continue;
|
|
11991
12041
|
let pairOf = "";
|
|
11992
12042
|
try {
|
|
11993
|
-
pairOf = readFileSync9(
|
|
12043
|
+
pairOf = readFileSync9(join21(root, id, "pair"), "utf8");
|
|
11994
12044
|
} catch {
|
|
11995
12045
|
continue;
|
|
11996
12046
|
}
|
|
11997
12047
|
if (pairOf === `${pair.conversationId}/${pair.agentId}`) {
|
|
11998
|
-
rmSync9(
|
|
12048
|
+
rmSync9(join21(root, id), { recursive: true, force: true });
|
|
11999
12049
|
}
|
|
12000
12050
|
}
|
|
12001
12051
|
}
|
|
12002
12052
|
var IDLE_SESSION_ARCHIVE_MS = 30 * 24 * 60 * 6e4;
|
|
12003
12053
|
function pruneIdleSessionArchives(deviceDir, now = Date.now(), maxAgeMs = IDLE_SESSION_ARCHIVE_MS) {
|
|
12004
|
-
const root =
|
|
12054
|
+
const root = join21(deviceDir, "attempts");
|
|
12005
12055
|
if (!existsSync14(root)) return [];
|
|
12006
12056
|
const removed = [];
|
|
12007
12057
|
for (const id of readdirSync4(root)) {
|
|
12008
12058
|
if (!UUID_RE.test(id)) continue;
|
|
12009
12059
|
let kept;
|
|
12010
12060
|
try {
|
|
12011
|
-
kept = statSync4(
|
|
12061
|
+
kept = statSync4(join21(root, id)).mtimeMs;
|
|
12012
12062
|
} catch {
|
|
12013
12063
|
continue;
|
|
12014
12064
|
}
|
|
12015
12065
|
if (now - kept < maxAgeMs) continue;
|
|
12016
|
-
rmSync9(
|
|
12066
|
+
rmSync9(join21(root, id), { recursive: true, force: true });
|
|
12017
12067
|
removed.push(id);
|
|
12018
12068
|
}
|
|
12019
12069
|
return removed;
|
|
@@ -12214,11 +12264,11 @@ function createDockerProvider(opts) {
|
|
|
12214
12264
|
await must(["network", "disconnect", "-f", other, name], "moving the container");
|
|
12215
12265
|
if (others.includes(sharedNetwork)) await retireSharedNetwork();
|
|
12216
12266
|
};
|
|
12217
|
-
const socketRoot = opts.socketRoot ??
|
|
12267
|
+
const socketRoot = opts.socketRoot ?? join21(
|
|
12218
12268
|
process.env.XDG_RUNTIME_DIR && existsSync14(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
|
|
12219
12269
|
`cabane-runners-${opts.deviceKey}`
|
|
12220
12270
|
);
|
|
12221
|
-
const socketDir = (name) =>
|
|
12271
|
+
const socketDir = (name) => join21(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
|
|
12222
12272
|
const create = async (name, attempt) => {
|
|
12223
12273
|
mkdirSync10(socketRoot, { recursive: true, mode: 448 });
|
|
12224
12274
|
mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
|
|
@@ -12360,7 +12410,7 @@ function createDockerProvider(opts) {
|
|
|
12360
12410
|
}
|
|
12361
12411
|
};
|
|
12362
12412
|
const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
|
|
12363
|
-
const pendingPath = (name) =>
|
|
12413
|
+
const pendingPath = (name) => join21(opts.deviceDir, "runners-pending", `${name}.json`);
|
|
12364
12414
|
const readPending = (name) => {
|
|
12365
12415
|
try {
|
|
12366
12416
|
const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
|
|
@@ -12374,12 +12424,12 @@ function createDockerProvider(opts) {
|
|
|
12374
12424
|
rmSync9(pendingPath(name), { force: true });
|
|
12375
12425
|
return;
|
|
12376
12426
|
}
|
|
12377
|
-
mkdirSync10(
|
|
12427
|
+
mkdirSync10(join21(opts.deviceDir, "runners-pending"), { recursive: true });
|
|
12378
12428
|
writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
|
|
12379
12429
|
};
|
|
12380
12430
|
const keepSession = async (name, attempt, attemptId) => {
|
|
12381
12431
|
const archive = sessionArchivePath(opts.deviceDir, attemptId);
|
|
12382
|
-
mkdirSync10(
|
|
12432
|
+
mkdirSync10(join21(archive, ".."), { recursive: true });
|
|
12383
12433
|
const partial = `${archive}.${process.pid}.partial`;
|
|
12384
12434
|
const file = new CappedFile(partial, sessionCap);
|
|
12385
12435
|
const r = await execIn(
|
|
@@ -12396,7 +12446,7 @@ function createDockerProvider(opts) {
|
|
|
12396
12446
|
);
|
|
12397
12447
|
if (r.code === 0 && !file.over) {
|
|
12398
12448
|
renameSync6(partial, archive);
|
|
12399
|
-
writeFileSync6(
|
|
12449
|
+
writeFileSync6(join21(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
|
|
12400
12450
|
pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
|
|
12401
12451
|
await writeMarker(name, attemptId);
|
|
12402
12452
|
log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
|
|
@@ -12410,7 +12460,7 @@ function createDockerProvider(opts) {
|
|
|
12410
12460
|
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"
|
|
12411
12461
|
);
|
|
12412
12462
|
};
|
|
12413
|
-
const stagingDir = (name) =>
|
|
12463
|
+
const stagingDir = (name) => join21(opts.deviceDir, "runners-pending", `${name}.outbox`);
|
|
12414
12464
|
const ensureRunning = async (name) => {
|
|
12415
12465
|
const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
|
|
12416
12466
|
if (seen.code !== 0) return false;
|
|
@@ -12418,7 +12468,7 @@ function createDockerProvider(opts) {
|
|
|
12418
12468
|
return (await docker.run(["start", name])).code === 0;
|
|
12419
12469
|
};
|
|
12420
12470
|
const dropArchive = (attemptId) => {
|
|
12421
|
-
rmSync9(
|
|
12471
|
+
rmSync9(join21(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
|
|
12422
12472
|
recursive: true,
|
|
12423
12473
|
force: true
|
|
12424
12474
|
});
|
|
@@ -12487,7 +12537,7 @@ function createDockerProvider(opts) {
|
|
|
12487
12537
|
if (seen.stdout.trim().split(" ")[0] !== "true")
|
|
12488
12538
|
await must(["start", name], "starting the container");
|
|
12489
12539
|
const git = await serveGitCredentials({
|
|
12490
|
-
socketPath:
|
|
12540
|
+
socketPath: join21(socketDir(name), "git.sock"),
|
|
12491
12541
|
hosts: opts.gitHosts,
|
|
12492
12542
|
fill: opts.gitFill,
|
|
12493
12543
|
log
|
|
@@ -12636,15 +12686,15 @@ function createDockerProvider(opts) {
|
|
|
12636
12686
|
const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
|
|
12637
12687
|
if (transcripts) {
|
|
12638
12688
|
const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
|
|
12639
|
-
if (kept.length) mkdirSync10(
|
|
12640
|
-
for (const f of kept) writeFileSync6(
|
|
12689
|
+
if (kept.length) mkdirSync10(join21(opts.hostStateDir, "transcripts"), { recursive: true });
|
|
12690
|
+
for (const f of kept) writeFileSync6(join21(opts.hostStateDir, "transcripts", f.name), f.data);
|
|
12641
12691
|
await removeIn(
|
|
12642
12692
|
name,
|
|
12643
12693
|
`${CONTAINER_STATE}/transcripts`,
|
|
12644
12694
|
kept.map((f) => f.name)
|
|
12645
12695
|
);
|
|
12646
12696
|
}
|
|
12647
|
-
const outboxRel =
|
|
12697
|
+
const outboxRel = join21("outbox", encodeURIComponent(attempt.agentId));
|
|
12648
12698
|
const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
|
|
12649
12699
|
let unpublished = false;
|
|
12650
12700
|
if (outbox) {
|
|
@@ -12670,7 +12720,7 @@ function createDockerProvider(opts) {
|
|
|
12670
12720
|
const staged = stagingDir(name);
|
|
12671
12721
|
mkdirSync10(staged, { recursive: true });
|
|
12672
12722
|
writeFileSync6(
|
|
12673
|
-
|
|
12723
|
+
join21(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
|
|
12674
12724
|
`${JSON.stringify(entry)}
|
|
12675
12725
|
`
|
|
12676
12726
|
);
|
|
@@ -12683,7 +12733,7 @@ function createDockerProvider(opts) {
|
|
|
12683
12733
|
if (outbox.complete) {
|
|
12684
12734
|
const published = publishStaged(
|
|
12685
12735
|
stagingDir(name),
|
|
12686
|
-
|
|
12736
|
+
join21(opts.hostStateDir, outboxRel),
|
|
12687
12737
|
opts.moveFile
|
|
12688
12738
|
);
|
|
12689
12739
|
queued = published.published;
|
|
@@ -12892,15 +12942,15 @@ import {
|
|
|
12892
12942
|
mkdirSync as mkdirSync11,
|
|
12893
12943
|
mkdtempSync,
|
|
12894
12944
|
readFileSync as readFileSync10,
|
|
12895
|
-
realpathSync as
|
|
12945
|
+
realpathSync as realpathSync5,
|
|
12896
12946
|
rmSync as rmSync10,
|
|
12897
12947
|
writeFileSync as writeFileSync7
|
|
12898
12948
|
} from "fs";
|
|
12899
12949
|
import { createRequire as createRequire2 } from "module";
|
|
12900
12950
|
import { isIP } from "net";
|
|
12901
12951
|
import { homedir as homedir6, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
12902
|
-
import { dirname as
|
|
12903
|
-
import { fileURLToPath as
|
|
12952
|
+
import { dirname as dirname10, join as join22, sep as sep4 } from "path";
|
|
12953
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12904
12954
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
12905
12955
|
function accountHome() {
|
|
12906
12956
|
try {
|
|
@@ -12912,9 +12962,9 @@ function accountHome() {
|
|
|
12912
12962
|
var STORE_WALK_TIMEOUT_MS = 6e4;
|
|
12913
12963
|
var STORE_WALK_MAX_AGE_MS = 5 * 6e4;
|
|
12914
12964
|
function storeWalkRoots(homes, cabaneDir2) {
|
|
12915
|
-
const companionHome =
|
|
12916
|
-
const parent =
|
|
12917
|
-
const beside = homes.includes(companionHome) ||
|
|
12965
|
+
const companionHome = dirname10(cabaneDir2);
|
|
12966
|
+
const parent = dirname10(companionHome);
|
|
12967
|
+
const beside = homes.includes(companionHome) || dirname10(parent) === parent ? [] : [parent];
|
|
12918
12968
|
const candidates = [.../* @__PURE__ */ new Set([...homes, ...beside])];
|
|
12919
12969
|
return candidates.filter(
|
|
12920
12970
|
(root) => !candidates.some((other) => other !== root && isAtOrUnder(root, other))
|
|
@@ -13109,7 +13159,7 @@ import {
|
|
|
13109
13159
|
rmSync as rmSync11,
|
|
13110
13160
|
writeFileSync as writeFileSync8
|
|
13111
13161
|
} from "fs";
|
|
13112
|
-
import { join as
|
|
13162
|
+
import { join as join23 } from "path";
|
|
13113
13163
|
var MAX_ENTRIES = 2e3;
|
|
13114
13164
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
13115
13165
|
var Outbox = class {
|
|
@@ -13122,10 +13172,10 @@ var Outbox = class {
|
|
|
13122
13172
|
// Resolved lazily (per call) so tests that swap HOME between
|
|
13123
13173
|
// cases route writes at the right tmpdir.
|
|
13124
13174
|
dir() {
|
|
13125
|
-
return
|
|
13175
|
+
return join23(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
13126
13176
|
}
|
|
13127
13177
|
fileFor(turnId, seq) {
|
|
13128
|
-
return
|
|
13178
|
+
return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
13129
13179
|
}
|
|
13130
13180
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
13131
13181
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -13167,7 +13217,7 @@ var Outbox = class {
|
|
|
13167
13217
|
const entries = [];
|
|
13168
13218
|
for (const name of names) {
|
|
13169
13219
|
if (!name.endsWith(".json")) continue;
|
|
13170
|
-
const full =
|
|
13220
|
+
const full = join23(dir2, name);
|
|
13171
13221
|
try {
|
|
13172
13222
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
13173
13223
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -13281,6 +13331,8 @@ var SseSubscriber = class {
|
|
|
13281
13331
|
opts;
|
|
13282
13332
|
aborted = false;
|
|
13283
13333
|
controller = null;
|
|
13334
|
+
// Ends the reconnect backoff early, so a stop never waits out a sleep of up to 30 s.
|
|
13335
|
+
wake = null;
|
|
13284
13336
|
lastEventId;
|
|
13285
13337
|
// Set after `start()`; resolved when the subscriber loop exits (either
|
|
13286
13338
|
// because `stop()` was called or because an auth failure tore it down).
|
|
@@ -13291,12 +13343,7 @@ var SseSubscriber = class {
|
|
|
13291
13343
|
stop() {
|
|
13292
13344
|
this.aborted = true;
|
|
13293
13345
|
this.controller?.abort();
|
|
13294
|
-
|
|
13295
|
-
// Connect again after a `stop()`, once its loop has exited.
|
|
13296
|
-
async resume() {
|
|
13297
|
-
await this.finished;
|
|
13298
|
-
this.aborted = false;
|
|
13299
|
-
this.start();
|
|
13346
|
+
this.wake?.();
|
|
13300
13347
|
}
|
|
13301
13348
|
setCursor(eventId) {
|
|
13302
13349
|
this.lastEventId = eventId;
|
|
@@ -13309,7 +13356,7 @@ var SseSubscriber = class {
|
|
|
13309
13356
|
backoff = 500;
|
|
13310
13357
|
if (!this.aborted) {
|
|
13311
13358
|
this.opts.log.warn("Lost the connection to Cabane; reconnecting");
|
|
13312
|
-
await
|
|
13359
|
+
await this.backoff(backoff);
|
|
13313
13360
|
}
|
|
13314
13361
|
} catch (err) {
|
|
13315
13362
|
if (this.aborted) return;
|
|
@@ -13329,11 +13376,21 @@ var SseSubscriber = class {
|
|
|
13329
13376
|
},
|
|
13330
13377
|
"Lost the connection to Cabane; reconnecting"
|
|
13331
13378
|
);
|
|
13332
|
-
await
|
|
13379
|
+
await this.backoff(backoff);
|
|
13333
13380
|
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
13334
13381
|
}
|
|
13335
13382
|
}
|
|
13336
13383
|
}
|
|
13384
|
+
backoff(ms) {
|
|
13385
|
+
return new Promise((resolve2) => {
|
|
13386
|
+
const timer = setTimeout(done, ms);
|
|
13387
|
+
this.wake = done;
|
|
13388
|
+
function done() {
|
|
13389
|
+
clearTimeout(timer);
|
|
13390
|
+
resolve2();
|
|
13391
|
+
}
|
|
13392
|
+
});
|
|
13393
|
+
}
|
|
13337
13394
|
async connect() {
|
|
13338
13395
|
const base = this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
|
|
13339
13396
|
const url = `${base}${this.opts.path}`;
|
|
@@ -13380,26 +13437,23 @@ var SseSubscriber = class {
|
|
|
13380
13437
|
}
|
|
13381
13438
|
}
|
|
13382
13439
|
};
|
|
13383
|
-
function sleep3(ms) {
|
|
13384
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
13385
|
-
}
|
|
13386
13440
|
|
|
13387
13441
|
// src/updater.ts
|
|
13388
13442
|
import { spawn as spawn8 } from "child_process";
|
|
13389
13443
|
import { copyFileSync as copyFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
|
|
13390
|
-
import { dirname as
|
|
13444
|
+
import { dirname as dirname14, join as join27 } from "path";
|
|
13391
13445
|
|
|
13392
13446
|
// src/boot-rule.ts
|
|
13393
13447
|
import {
|
|
13394
13448
|
mkdirSync as mkdirSync13,
|
|
13395
13449
|
readFileSync as readFileSync12,
|
|
13396
|
-
realpathSync as
|
|
13450
|
+
realpathSync as realpathSync6,
|
|
13397
13451
|
renameSync as renameSync8,
|
|
13398
13452
|
rmSync as rmSync12,
|
|
13399
13453
|
symlinkSync,
|
|
13400
13454
|
writeFileSync as writeFileSync9
|
|
13401
13455
|
} from "fs";
|
|
13402
|
-
import { dirname as
|
|
13456
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
13403
13457
|
var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
|
|
13404
13458
|
var MAX_STAGE_ATTEMPTS = 3;
|
|
13405
13459
|
function superseded(pending, ...running) {
|
|
@@ -13427,31 +13481,31 @@ import {
|
|
|
13427
13481
|
mkdirSync as mkdirSync14,
|
|
13428
13482
|
readFileSync as readFileSync13,
|
|
13429
13483
|
readdirSync as readdirSync6,
|
|
13430
|
-
realpathSync as
|
|
13484
|
+
realpathSync as realpathSync7,
|
|
13431
13485
|
renameSync as renameSync9,
|
|
13432
13486
|
rmSync as rmSync13,
|
|
13433
13487
|
symlinkSync as symlinkSync2
|
|
13434
13488
|
} from "fs";
|
|
13435
|
-
import { dirname as
|
|
13436
|
-
import { fileURLToPath as
|
|
13489
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
13490
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13437
13491
|
function versionsDir() {
|
|
13438
|
-
return
|
|
13492
|
+
return join25(cabaneDir(), "versions");
|
|
13439
13493
|
}
|
|
13440
13494
|
function currentLink() {
|
|
13441
|
-
return
|
|
13495
|
+
return join25(cabaneDir(), "current");
|
|
13442
13496
|
}
|
|
13443
13497
|
function bootGuardPath() {
|
|
13444
|
-
return
|
|
13498
|
+
return join25(cabaneDir(), "boot-guard.mjs");
|
|
13445
13499
|
}
|
|
13446
13500
|
function versionPackageRoot(version) {
|
|
13447
|
-
return
|
|
13501
|
+
return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
|
|
13448
13502
|
}
|
|
13449
13503
|
function ownPackageRoot(moduleUrl = import.meta.url) {
|
|
13450
|
-
return
|
|
13504
|
+
return dirname12(dirname12(fileURLToPath7(moduleUrl)));
|
|
13451
13505
|
}
|
|
13452
13506
|
function packageVersion(root) {
|
|
13453
13507
|
try {
|
|
13454
|
-
const pkg2 = JSON.parse(readFileSync13(
|
|
13508
|
+
const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
|
|
13455
13509
|
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
13456
13510
|
} catch {
|
|
13457
13511
|
return null;
|
|
@@ -13459,15 +13513,15 @@ function packageVersion(root) {
|
|
|
13459
13513
|
}
|
|
13460
13514
|
function currentRoot() {
|
|
13461
13515
|
try {
|
|
13462
|
-
const root =
|
|
13463
|
-
return existsSync16(
|
|
13516
|
+
const root = realpathSync7(currentLink());
|
|
13517
|
+
return existsSync16(join25(root, "dist", "cli.js")) ? root : null;
|
|
13464
13518
|
} catch {
|
|
13465
13519
|
return null;
|
|
13466
13520
|
}
|
|
13467
13521
|
}
|
|
13468
13522
|
function pointCurrentAt(root) {
|
|
13469
13523
|
const link = currentLink();
|
|
13470
|
-
mkdirSync14(
|
|
13524
|
+
mkdirSync14(dirname12(link), { recursive: true });
|
|
13471
13525
|
const fresh = `${link}.${process.pid}.${Date.now()}`;
|
|
13472
13526
|
symlinkSync2(root, fresh);
|
|
13473
13527
|
try {
|
|
@@ -13483,7 +13537,7 @@ function pruneVersions(keep) {
|
|
|
13483
13537
|
const removed = [];
|
|
13484
13538
|
for (const name of readdirSync6(dir2)) {
|
|
13485
13539
|
if (keep.includes(name)) continue;
|
|
13486
|
-
rmSync13(
|
|
13540
|
+
rmSync13(join25(dir2, name), { recursive: true, force: true });
|
|
13487
13541
|
removed.push(name);
|
|
13488
13542
|
}
|
|
13489
13543
|
return removed;
|
|
@@ -13502,7 +13556,7 @@ function compareVersions(a, b) {
|
|
|
13502
13556
|
|
|
13503
13557
|
// src/update-state.ts
|
|
13504
13558
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
13505
|
-
import { dirname as
|
|
13559
|
+
import { dirname as dirname13, join as join26 } from "path";
|
|
13506
13560
|
import { z as z18 } from "zod";
|
|
13507
13561
|
var resultSchema = z18.object({
|
|
13508
13562
|
kind: z18.enum(["updated", "rolled_back", "failed"]),
|
|
@@ -13526,7 +13580,7 @@ var stateSchema = z18.object({
|
|
|
13526
13580
|
failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
|
|
13527
13581
|
});
|
|
13528
13582
|
function updateStatePath(dir2 = cabaneDir()) {
|
|
13529
|
-
return
|
|
13583
|
+
return join26(dir2, "update.json");
|
|
13530
13584
|
}
|
|
13531
13585
|
function readUpdateState(path = updateStatePath()) {
|
|
13532
13586
|
try {
|
|
@@ -13537,7 +13591,7 @@ function readUpdateState(path = updateStatePath()) {
|
|
|
13537
13591
|
}
|
|
13538
13592
|
}
|
|
13539
13593
|
function writeUpdateState(state, path = updateStatePath()) {
|
|
13540
|
-
mkdirSync15(
|
|
13594
|
+
mkdirSync15(dirname13(path), { recursive: true });
|
|
13541
13595
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
13542
13596
|
writeFileSync10(tmp, `${JSON.stringify(state, null, 2)}
|
|
13543
13597
|
`, { mode: 384 });
|
|
@@ -13554,7 +13608,7 @@ function updateMode(env = process.env) {
|
|
|
13554
13608
|
return env.CABANE_COMPANION_SUPERVISED === "1" ? "supervised" : "attached";
|
|
13555
13609
|
}
|
|
13556
13610
|
var STAGE_RETRY_MS = 60 * 6e4;
|
|
13557
|
-
var
|
|
13611
|
+
var SWAP_WAIT_MS = 10 * 6e4;
|
|
13558
13612
|
var SOURCE_BUILD_VERSION = "0.0.0";
|
|
13559
13613
|
function versionToStage(input) {
|
|
13560
13614
|
const { own, state, now, force } = input;
|
|
@@ -13575,10 +13629,10 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
|
|
|
13575
13629
|
return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
|
|
13576
13630
|
}
|
|
13577
13631
|
function writeBootGuard(root = ownPackageRoot()) {
|
|
13578
|
-
const built =
|
|
13632
|
+
const built = join27(root, "dist", "boot-guard.js");
|
|
13579
13633
|
if (!existsSync17(built)) return false;
|
|
13580
13634
|
const target = bootGuardPath();
|
|
13581
|
-
mkdirSync16(
|
|
13635
|
+
mkdirSync16(dirname14(target), { recursive: true });
|
|
13582
13636
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
13583
13637
|
copyFileSync3(built, tmp);
|
|
13584
13638
|
renameSync11(tmp, target);
|
|
@@ -13615,17 +13669,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve2) => {
|
|
|
13615
13669
|
async function stageVersion(version, harnesses, deps = {}) {
|
|
13616
13670
|
const run = deps.run ?? runProcess;
|
|
13617
13671
|
const node = deps.execPath ?? process.execPath;
|
|
13618
|
-
const target =
|
|
13672
|
+
const target = join27(versionsDir(), version);
|
|
13619
13673
|
const placed = versionPackageRoot(version);
|
|
13620
13674
|
if (existsSync17(placed)) {
|
|
13621
13675
|
if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
|
|
13622
13676
|
return { ok: true, root: placed };
|
|
13623
13677
|
}
|
|
13624
|
-
const npm =
|
|
13678
|
+
const npm = join27(dirname14(node), "npm");
|
|
13625
13679
|
if (!existsSync17(npm))
|
|
13626
|
-
return { ok: false, reason: `there is no npm beside Node at ${
|
|
13680
|
+
return { ok: false, reason: `there is no npm beside Node at ${dirname14(node)}` };
|
|
13627
13681
|
mkdirSync16(versionsDir(), { recursive: true });
|
|
13628
|
-
const staging =
|
|
13682
|
+
const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
|
|
13629
13683
|
rmSync14(staging, { recursive: true, force: true });
|
|
13630
13684
|
const installed = await run(
|
|
13631
13685
|
npm,
|
|
@@ -13642,7 +13696,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
13642
13696
|
],
|
|
13643
13697
|
{
|
|
13644
13698
|
// npm's install scripts run on this Node, not whichever `node` is first on PATH.
|
|
13645
|
-
env: { ...process.env, PATH: `${
|
|
13699
|
+
env: { ...process.env, PATH: `${dirname14(node)}:${process.env.PATH ?? ""}` },
|
|
13646
13700
|
timeoutMs: 15 * 6e4
|
|
13647
13701
|
}
|
|
13648
13702
|
);
|
|
@@ -13654,7 +13708,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
13654
13708
|
return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
|
|
13655
13709
|
return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
|
|
13656
13710
|
}
|
|
13657
|
-
const root =
|
|
13711
|
+
const root = join27(staging, "node_modules", "@cabane", "companion");
|
|
13658
13712
|
const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
|
|
13659
13713
|
if (verdict) {
|
|
13660
13714
|
rmSync14(staging, { recursive: true, force: true });
|
|
@@ -13666,7 +13720,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
13666
13720
|
}
|
|
13667
13721
|
async function verifyInstall(root, version, harnesses, deps) {
|
|
13668
13722
|
if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
|
|
13669
|
-
const answered = await deps.run(deps.execPath, [
|
|
13723
|
+
const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
|
|
13670
13724
|
timeoutMs: 6e4,
|
|
13671
13725
|
env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
|
|
13672
13726
|
});
|
|
@@ -13674,7 +13728,7 @@ async function verifyInstall(root, version, harnesses, deps) {
|
|
|
13674
13728
|
return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
|
|
13675
13729
|
}
|
|
13676
13730
|
if (harnesses.length === 0) return null;
|
|
13677
|
-
const probed = await deps.run(deps.execPath, [
|
|
13731
|
+
const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
|
|
13678
13732
|
timeoutMs: 6e4
|
|
13679
13733
|
});
|
|
13680
13734
|
let resolutions = {};
|
|
@@ -13704,6 +13758,8 @@ var CompanionUpdater = class {
|
|
|
13704
13758
|
deps;
|
|
13705
13759
|
staging = null;
|
|
13706
13760
|
ready = null;
|
|
13761
|
+
// When `ready` verified; `SWAP_WAIT_MS` counts from here.
|
|
13762
|
+
readyAt = 0;
|
|
13707
13763
|
swapping = false;
|
|
13708
13764
|
watchdog = null;
|
|
13709
13765
|
now;
|
|
@@ -13802,7 +13858,7 @@ var CompanionUpdater = class {
|
|
|
13802
13858
|
return { ok: true, message: `Cabane ${this.deps.own} is up to date.` };
|
|
13803
13859
|
if (state.pending?.to === latest) return { ok: true, message: this.waitingMessage(latest) };
|
|
13804
13860
|
if (this.ready === latest) {
|
|
13805
|
-
void this.trySwap();
|
|
13861
|
+
void this.trySwap(true);
|
|
13806
13862
|
return { ok: true, message: this.waitingMessage(latest) };
|
|
13807
13863
|
}
|
|
13808
13864
|
this.maybeStage(true);
|
|
@@ -13818,13 +13874,14 @@ var CompanionUpdater = class {
|
|
|
13818
13874
|
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.`
|
|
13819
13875
|
};
|
|
13820
13876
|
}
|
|
13821
|
-
void this.trySwap();
|
|
13877
|
+
void this.trySwap(true);
|
|
13822
13878
|
return { ok: true, message: this.waitingMessage(latest) };
|
|
13823
13879
|
}
|
|
13880
|
+
// Only the person's `cabane update` asks, and that swaps at once.
|
|
13824
13881
|
waitingMessage(version) {
|
|
13825
13882
|
if (this.deps.mode === "attached")
|
|
13826
13883
|
return `Cabane ${version} is installed. It runs from the next cabane start.`;
|
|
13827
|
-
return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version}
|
|
13884
|
+
return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version} now. The turns running now carry on after the restart.`;
|
|
13828
13885
|
}
|
|
13829
13886
|
maybeStage(force) {
|
|
13830
13887
|
if (this.staging) return;
|
|
@@ -13842,6 +13899,7 @@ var CompanionUpdater = class {
|
|
|
13842
13899
|
const outcome = await stage(version, this.deps.harnesses());
|
|
13843
13900
|
if (outcome.ok) {
|
|
13844
13901
|
this.ready = version;
|
|
13902
|
+
this.readyAt = this.now();
|
|
13845
13903
|
updateUpdateState((s) => {
|
|
13846
13904
|
const { failed: _failed, ...rest } = s;
|
|
13847
13905
|
return rest;
|
|
@@ -13880,8 +13938,10 @@ var CompanionUpdater = class {
|
|
|
13880
13938
|
});
|
|
13881
13939
|
void this.staging.then(() => this.trySwap());
|
|
13882
13940
|
}
|
|
13883
|
-
// Swap into the ready version
|
|
13884
|
-
|
|
13941
|
+
// Swap into the ready version: when nothing is running, when it has waited `SWAP_WAIT_MS` for
|
|
13942
|
+
// that, when this version is below the server's minimum, or `now` (the person asked). Called on
|
|
13943
|
+
// each heartbeat until it does.
|
|
13944
|
+
async trySwap(now = false) {
|
|
13885
13945
|
const to = this.ready;
|
|
13886
13946
|
if (!to || this.swapping) return;
|
|
13887
13947
|
if (readUpdateState().pending) return;
|
|
@@ -13890,18 +13950,9 @@ var CompanionUpdater = class {
|
|
|
13890
13950
|
this.deps.log.info({ version: to }, `Cabane ${to} is installed; it runs from the next start`);
|
|
13891
13951
|
return;
|
|
13892
13952
|
}
|
|
13893
|
-
if (!this.deps.isIdle()) return;
|
|
13953
|
+
if (!now && !this.deps.isIdle() && !this.waitedLongEnough() && !this.belowMinimum()) return;
|
|
13894
13954
|
this.swapping = true;
|
|
13895
13955
|
try {
|
|
13896
|
-
await this.deps.pauseStream();
|
|
13897
|
-
const sleep4 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13898
|
-
const deadline = this.now() + SWAP_DRAIN_MS;
|
|
13899
|
-
await sleep4(250);
|
|
13900
|
-
while (!this.deps.isIdle() && this.now() < deadline) await sleep4(250);
|
|
13901
|
-
if (!this.deps.isIdle()) {
|
|
13902
|
-
this.deps.resumeStream();
|
|
13903
|
-
return;
|
|
13904
|
-
}
|
|
13905
13956
|
this.select(to);
|
|
13906
13957
|
this.deps.log.info({ from: this.deps.own, to }, `Restarting into Cabane ${to}`);
|
|
13907
13958
|
await this.deps.restart();
|
|
@@ -13909,6 +13960,14 @@ var CompanionUpdater = class {
|
|
|
13909
13960
|
this.swapping = false;
|
|
13910
13961
|
}
|
|
13911
13962
|
}
|
|
13963
|
+
waitedLongEnough() {
|
|
13964
|
+
return this.now() - this.readyAt >= SWAP_WAIT_MS;
|
|
13965
|
+
}
|
|
13966
|
+
// Cabane refuses this version's turns: waiting would only turn them away.
|
|
13967
|
+
belowMinimum() {
|
|
13968
|
+
const minimum = readUpdateState().server?.minimum;
|
|
13969
|
+
return !!minimum && compareVersions(this.deps.own, minimum) < 0;
|
|
13970
|
+
}
|
|
13912
13971
|
// Record the swap and point `current` at `to`, leaving the guard that counts its starts.
|
|
13913
13972
|
select(to) {
|
|
13914
13973
|
const fromRoot = currentRoot() ?? ownPackageRoot();
|
|
@@ -13936,6 +13995,7 @@ var COMPANION_VERSION = pkg.version;
|
|
|
13936
13995
|
// src/supervisor.ts
|
|
13937
13996
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
13938
13997
|
var ARCHIVE_PRUNE_MS = 24 * 60 * 6e4;
|
|
13998
|
+
var FORCE_EXIT_MS = 4e3;
|
|
13939
13999
|
var SHUTDOWN_HEARTBEAT_WAIT_MS = 1e3;
|
|
13940
14000
|
function positiveIntEnv(name) {
|
|
13941
14001
|
const raw = process.env[name];
|
|
@@ -14156,14 +14216,7 @@ var CompanionSupervisor = class {
|
|
|
14156
14216
|
autoUpdate: () => this.config.update?.auto === true,
|
|
14157
14217
|
harnesses: () => this.connectedBundledHarnesses(),
|
|
14158
14218
|
isIdle: () => this.idle(),
|
|
14159
|
-
|
|
14160
|
-
const sub = this.deviceSub;
|
|
14161
|
-
if (!sub) return;
|
|
14162
|
-
sub.stop();
|
|
14163
|
-
await sub.finished;
|
|
14164
|
-
},
|
|
14165
|
-
resumeStream: () => void this.deviceSub?.resume(),
|
|
14166
|
-
restart: () => this.restartForUpdate(),
|
|
14219
|
+
restart: () => this.restartBounded(),
|
|
14167
14220
|
log: this.log
|
|
14168
14221
|
});
|
|
14169
14222
|
this.updater.start();
|
|
@@ -14256,7 +14309,7 @@ var CompanionSupervisor = class {
|
|
|
14256
14309
|
this.versionSkewWarned = true;
|
|
14257
14310
|
this.log.warn(
|
|
14258
14311
|
{ companionVersion: COMPANION_VERSION, serverVersion },
|
|
14259
|
-
|
|
14312
|
+
`This companion and Cabane are on different versions. If turns fail, update with ${cabane("update")}`
|
|
14260
14313
|
);
|
|
14261
14314
|
}
|
|
14262
14315
|
// Pull assignments and reconcile the live runner set against them. Re-entrancy
|
|
@@ -14485,7 +14538,8 @@ var CompanionSupervisor = class {
|
|
|
14485
14538
|
bundled: this.bundledStatus("claude-code")
|
|
14486
14539
|
},
|
|
14487
14540
|
codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
|
|
14488
|
-
install: companionInstall()
|
|
14541
|
+
install: companionInstall(),
|
|
14542
|
+
managed: repairRoute() === "managed"
|
|
14489
14543
|
});
|
|
14490
14544
|
}
|
|
14491
14545
|
// A missing binary is reported ONCE, and again only when the answer CHANGES,
|
|
@@ -15285,8 +15339,22 @@ var CompanionSupervisor = class {
|
|
|
15285
15339
|
);
|
|
15286
15340
|
}
|
|
15287
15341
|
}
|
|
15342
|
+
// The update's restart, held to the same `FORCE_EXIT_MS` a signal's stop gets. Exit 0: the
|
|
15343
|
+
// swap is recorded and `current` names the new version, so the service starts it either way.
|
|
15344
|
+
async restartBounded() {
|
|
15345
|
+
const force = setTimeout(() => {
|
|
15346
|
+
this.log.warn({ ms: FORCE_EXIT_MS }, "Restarting for the update: shutdown took too long");
|
|
15347
|
+
this.exitFn(0);
|
|
15348
|
+
}, FORCE_EXIT_MS);
|
|
15349
|
+
force.unref?.();
|
|
15350
|
+
try {
|
|
15351
|
+
await this.restartForUpdate();
|
|
15352
|
+
} finally {
|
|
15353
|
+
clearTimeout(force);
|
|
15354
|
+
}
|
|
15355
|
+
}
|
|
15288
15356
|
// Nothing running and nothing about to: no dispatch claimed, no turn chained, no recovery
|
|
15289
|
-
// in progress. What a self-update swap waits for
|
|
15357
|
+
// in progress. What a self-update swap waits for, up to `SWAP_WAIT_MS`.
|
|
15290
15358
|
idle() {
|
|
15291
15359
|
if (this.claims.size > 0 || this.recovering) return false;
|
|
15292
15360
|
for (const wr of this.workspaces.values()) if (wr.chains.size > 0) return false;
|
|
@@ -15308,7 +15376,7 @@ var CompanionSupervisor = class {
|
|
|
15308
15376
|
if (auto) this.kickHeartbeat();
|
|
15309
15377
|
return {
|
|
15310
15378
|
ok: true,
|
|
15311
|
-
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."
|
|
15379
|
+
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."
|
|
15312
15380
|
};
|
|
15313
15381
|
}
|
|
15314
15382
|
if (!this.updater) return { ok: false, message: "This computer isn't paired yet." };
|
|
@@ -15426,9 +15494,9 @@ function handleUncaught(log, err, origin) {
|
|
|
15426
15494
|
|
|
15427
15495
|
// src/crash-marker.ts
|
|
15428
15496
|
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync15, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "fs";
|
|
15429
|
-
import { join as
|
|
15497
|
+
import { join as join28 } from "path";
|
|
15430
15498
|
function crashMarkerPath() {
|
|
15431
|
-
return
|
|
15499
|
+
return join28(cabaneDir(), "last-error.json");
|
|
15432
15500
|
}
|
|
15433
15501
|
function recordCrash(rec) {
|
|
15434
15502
|
try {
|