@cabane/companion 0.6.159 → 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/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/bundled-binaries.ts
10
- import { realpathSync, statSync } from "fs";
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 repairCommands(install = companionInstall()) {
201
- return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
202
- }
203
- function repairAction(install = companionInstall()) {
204
- return `Reinstall with \`${repairCommands(install)[0]}\`.`;
205
- }
206
- function startupWarning(r) {
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 turnIncompleteCopy(runtime) {
216
- return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
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 = entry.indexOf(marker);
81
+ const at = path.indexOf(marker);
251
82
  if (at < 0) return null;
252
- return join2(entry.slice(0, at), "bin", "cabane");
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 dirname2, join as join3 } from "path";
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(dirname2(path), { recursive: true });
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 dirname4, join as join7 } from "path";
799
- import { fileURLToPath as fileURLToPath3 } from "url";
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 join6, normalize } from "path";
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 join4 } from "path";
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({ name, reason: gate.bundled, install: gates.install });
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 ${repairCommands()[0]} , then try again.`;
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" ? ` (${join4(homedir5(), ".codex", "config.toml").replace(homedir5(), "~")})` : ""}. Fix it, then try again.`;
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 ${repairCommands()[0]} , then try again.`;
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 dirname3, join as join5 } from "path";
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 join5(cabaneDir(), "companion.log");
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(dirname3(path), { recursive: true });
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(join6(staticDir, "index.html"), "utf8");
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 = join6(staticDir, safe2);
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 join7(dirname4(fileURLToPath3(import.meta.url)), "static");
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 join8 } from "path";
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 = join8(dir2, "companion.sock");
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) ? join8(process.env.XDG_RUNTIME_DIR, filename) : null;
1925
- return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath : join8("/tmp", filename);
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 join9 } from "path";
2175
+ import { join as join10 } from "path";
2126
2176
  var PROBE_TIMEOUT_MS2 = 1e3;
2127
2177
  function runtimePath() {
2128
- return join9(cabaneDir(), "runtime.json");
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 "The agent installation is incomplete. Reinstall with npm i -g @cabane/companion@latest, then restart the companion.";
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
- "Cabane rejected a saved update; it could not be delivered. This companion may need an update: npm i -g @cabane/companion@latest"
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 dirname5, join as join10 } from "path";
3084
+ import { dirname as dirname7, join as join11 } from "path";
3035
3085
  import { z as z4 } from "zod";
3036
3086
  function credentialsPath() {
3037
- return join10(cabaneDir(), "credentials.json");
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(dirname5(path), { recursive: true });
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 join11 } from "path";
3155
+ import { join as join12 } from "path";
3106
3156
  var MAX_IDS = 256;
3107
3157
  function dir(log) {
3108
- return join11(cabaneDir(), log);
3158
+ return join12(cabaneDir(), log);
3109
3159
  }
3110
3160
  function pathFor(log, workspaceId) {
3111
- return join11(dir(log), encodeURIComponent(workspaceId));
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 join11(cabaneDir(), "resume-attempts");
3198
+ return join12(cabaneDir(), "resume-attempts");
3149
3199
  }
3150
3200
  function resumePathFor(workspaceId) {
3151
- return join11(resumeDir(), encodeURIComponent(workspaceId));
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 join11(cabaneDir(), "turns");
3237
+ return join12(cabaneDir(), "turns");
3188
3238
  }
3189
3239
  function turnPathFor(workspaceId) {
3190
- return join11(turnDir(), encodeURIComponent(workspaceId));
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 join17 } from "path";
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 join12 } from "path";
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 join12(cabaneDir(), "secrets.json");
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 join13 } from "path";
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(join13(tmpdir(), PREFIX));
9622
- const path = join13(dir2, "instructions.md");
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 join14 } from "path";
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(join14(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
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 = join14(dir2, `image-${index + 1}.${ext}`);
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 join15 } from "path";
9841
+ import { join as join16 } from "path";
9792
9842
  function dirFor(workspaceId) {
9793
- return join15(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9843
+ return join16(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
9794
9844
  }
9795
9845
  function conversationDir(workspaceId, conversationId) {
9796
- return join15(dirFor(workspaceId), encodeURIComponent(conversationId));
9846
+ return join16(dirFor(workspaceId), encodeURIComponent(conversationId));
9797
9847
  }
9798
9848
  function pathFor2(workspaceId, conversationId, agentId) {
9799
- return join15(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
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 dirname6, join as join16 } from "path";
9881
+ import { basename as basename2, dirname as dirname8, join as join17 } from "path";
9832
9882
  function transcriptsDir() {
9833
- return join16(runnerStateDir(), "transcripts");
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 = join16(dir2, fileName(meta));
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 = join16(dirname6(this.path), "anomalies");
9922
+ const dir2 = join17(dirname8(this.path), "anomalies");
9873
9923
  mkdirSync8(dir2, { recursive: true, mode: 448 });
9874
9924
  chmodSync4(dir2, 448);
9875
- const target = join16(dir2, basename(this.path));
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(join16(dir2, f), { force: true });
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. This companion may need an update: npm i -g @cabane/companion@latest`
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
- "Couldn't save the explanation to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
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
- "Couldn't save the turn's result to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
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
- "Couldn't save the explanation to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
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
- "Couldn't save the agent's memory in Cabane. The next turn will start a new session. This companion may need an update: npm i -g @cabane/companion@latest"
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
- "Couldn't save the turn's result to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
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 = join17(cwd, ".git");
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(join17(gitPath, "HEAD")))
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 join20 } from "path";
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 join18 } from "path";
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(join18(from, name), "utf8"));
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(join18(from, name), join18(to, name));
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 realpathSync3 } from "fs";
11793
- import { dirname as dirname7, join as join19, sep as sep3 } from "path";
11794
- import { fileURLToPath as fileURLToPath4 } from "url";
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 = fileURLToPath4(import.meta.url)) {
11839
- let dir2 = dirname7(moduleFile);
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(join19(dir2, "dist", "cli.js")) && existsSync13(join19(dir2, "package.json"))) break;
11842
- dir2 = dirname7(dir2);
11891
+ if (existsSync13(join20(dir2, "dist", "cli.js")) && existsSync13(join20(dir2, "package.json"))) break;
11892
+ dir2 = dirname9(dir2);
11843
11893
  }
11844
- if (!existsSync13(join19(dir2, "dist", "cli.js"))) return null;
11845
- const sdk = join19(dir2, "node_modules", "@anthropic-ai", "claude-agent-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 = realpathSync3(dir2);
11848
- return realpathSync3(sdk).startsWith(root + sep3) ? root : null;
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 join20(deviceDir, "attempts", attemptId, "session.tar");
12034
+ return join21(deviceDir, "attempts", attemptId, "session.tar");
11985
12035
  }
11986
12036
  function pruneOtherAttempts(deviceDir, pair, keep) {
11987
- const root = join20(deviceDir, "attempts");
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(join20(root, id, "pair"), "utf8");
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(join20(root, id), { recursive: true, force: true });
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 = join20(deviceDir, "attempts");
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(join20(root, id)).mtimeMs;
12061
+ kept = statSync4(join21(root, id)).mtimeMs;
12012
12062
  } catch {
12013
12063
  continue;
12014
12064
  }
12015
12065
  if (now - kept < maxAgeMs) continue;
12016
- rmSync9(join20(root, id), { recursive: true, force: true });
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 ?? join20(
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) => join20(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
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) => join20(opts.deviceDir, "runners-pending", `${name}.json`);
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(join20(opts.deviceDir, "runners-pending"), { recursive: true });
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(join20(archive, ".."), { recursive: true });
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(join20(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
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) => join20(opts.deviceDir, "runners-pending", `${name}.outbox`);
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(join20(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
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: join20(socketDir(name), "git.sock"),
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(join20(opts.hostStateDir, "transcripts"), { recursive: true });
12640
- for (const f of kept) writeFileSync6(join20(opts.hostStateDir, "transcripts", f.name), f.data);
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 = join20("outbox", encodeURIComponent(attempt.agentId));
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
- join20(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
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
- join20(opts.hostStateDir, outboxRel),
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 realpathSync4,
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 dirname8, join as join21, sep as sep4 } from "path";
12903
- import { fileURLToPath as fileURLToPath5 } from "url";
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 = dirname8(cabaneDir2);
12916
- const parent = dirname8(companionHome);
12917
- const beside = homes.includes(companionHome) || dirname8(parent) === parent ? [] : [parent];
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 join22 } from "path";
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 join22(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
13175
+ return join23(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
13126
13176
  }
13127
13177
  fileFor(turnId, seq) {
13128
- return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
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 = join22(dir2, name);
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") {
@@ -13391,19 +13441,19 @@ var SseSubscriber = class {
13391
13441
  // src/updater.ts
13392
13442
  import { spawn as spawn8 } from "child_process";
13393
13443
  import { copyFileSync as copyFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
13394
- import { dirname as dirname12, join as join26 } from "path";
13444
+ import { dirname as dirname14, join as join27 } from "path";
13395
13445
 
13396
13446
  // src/boot-rule.ts
13397
13447
  import {
13398
13448
  mkdirSync as mkdirSync13,
13399
13449
  readFileSync as readFileSync12,
13400
- realpathSync as realpathSync5,
13450
+ realpathSync as realpathSync6,
13401
13451
  renameSync as renameSync8,
13402
13452
  rmSync as rmSync12,
13403
13453
  symlinkSync,
13404
13454
  writeFileSync as writeFileSync9
13405
13455
  } from "fs";
13406
- import { dirname as dirname9, join as join23 } from "path";
13456
+ import { dirname as dirname11, join as join24 } from "path";
13407
13457
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
13408
13458
  var MAX_STAGE_ATTEMPTS = 3;
13409
13459
  function superseded(pending, ...running) {
@@ -13431,31 +13481,31 @@ import {
13431
13481
  mkdirSync as mkdirSync14,
13432
13482
  readFileSync as readFileSync13,
13433
13483
  readdirSync as readdirSync6,
13434
- realpathSync as realpathSync6,
13484
+ realpathSync as realpathSync7,
13435
13485
  renameSync as renameSync9,
13436
13486
  rmSync as rmSync13,
13437
13487
  symlinkSync as symlinkSync2
13438
13488
  } from "fs";
13439
- import { dirname as dirname10, join as join24 } from "path";
13440
- import { fileURLToPath as fileURLToPath6 } from "url";
13489
+ import { dirname as dirname12, join as join25 } from "path";
13490
+ import { fileURLToPath as fileURLToPath7 } from "url";
13441
13491
  function versionsDir() {
13442
- return join24(cabaneDir(), "versions");
13492
+ return join25(cabaneDir(), "versions");
13443
13493
  }
13444
13494
  function currentLink() {
13445
- return join24(cabaneDir(), "current");
13495
+ return join25(cabaneDir(), "current");
13446
13496
  }
13447
13497
  function bootGuardPath() {
13448
- return join24(cabaneDir(), "boot-guard.mjs");
13498
+ return join25(cabaneDir(), "boot-guard.mjs");
13449
13499
  }
13450
13500
  function versionPackageRoot(version) {
13451
- return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
13501
+ return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
13452
13502
  }
13453
13503
  function ownPackageRoot(moduleUrl = import.meta.url) {
13454
- return dirname10(dirname10(fileURLToPath6(moduleUrl)));
13504
+ return dirname12(dirname12(fileURLToPath7(moduleUrl)));
13455
13505
  }
13456
13506
  function packageVersion(root) {
13457
13507
  try {
13458
- const pkg2 = JSON.parse(readFileSync13(join24(root, "package.json"), "utf8"));
13508
+ const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
13459
13509
  return typeof pkg2.version === "string" ? pkg2.version : null;
13460
13510
  } catch {
13461
13511
  return null;
@@ -13463,15 +13513,15 @@ function packageVersion(root) {
13463
13513
  }
13464
13514
  function currentRoot() {
13465
13515
  try {
13466
- const root = realpathSync6(currentLink());
13467
- return existsSync16(join24(root, "dist", "cli.js")) ? root : null;
13516
+ const root = realpathSync7(currentLink());
13517
+ return existsSync16(join25(root, "dist", "cli.js")) ? root : null;
13468
13518
  } catch {
13469
13519
  return null;
13470
13520
  }
13471
13521
  }
13472
13522
  function pointCurrentAt(root) {
13473
13523
  const link = currentLink();
13474
- mkdirSync14(dirname10(link), { recursive: true });
13524
+ mkdirSync14(dirname12(link), { recursive: true });
13475
13525
  const fresh = `${link}.${process.pid}.${Date.now()}`;
13476
13526
  symlinkSync2(root, fresh);
13477
13527
  try {
@@ -13487,7 +13537,7 @@ function pruneVersions(keep) {
13487
13537
  const removed = [];
13488
13538
  for (const name of readdirSync6(dir2)) {
13489
13539
  if (keep.includes(name)) continue;
13490
- rmSync13(join24(dir2, name), { recursive: true, force: true });
13540
+ rmSync13(join25(dir2, name), { recursive: true, force: true });
13491
13541
  removed.push(name);
13492
13542
  }
13493
13543
  return removed;
@@ -13506,7 +13556,7 @@ function compareVersions(a, b) {
13506
13556
 
13507
13557
  // src/update-state.ts
13508
13558
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
13509
- import { dirname as dirname11, join as join25 } from "path";
13559
+ import { dirname as dirname13, join as join26 } from "path";
13510
13560
  import { z as z18 } from "zod";
13511
13561
  var resultSchema = z18.object({
13512
13562
  kind: z18.enum(["updated", "rolled_back", "failed"]),
@@ -13530,7 +13580,7 @@ var stateSchema = z18.object({
13530
13580
  failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
13531
13581
  });
13532
13582
  function updateStatePath(dir2 = cabaneDir()) {
13533
- return join25(dir2, "update.json");
13583
+ return join26(dir2, "update.json");
13534
13584
  }
13535
13585
  function readUpdateState(path = updateStatePath()) {
13536
13586
  try {
@@ -13541,7 +13591,7 @@ function readUpdateState(path = updateStatePath()) {
13541
13591
  }
13542
13592
  }
13543
13593
  function writeUpdateState(state, path = updateStatePath()) {
13544
- mkdirSync15(dirname11(path), { recursive: true });
13594
+ mkdirSync15(dirname13(path), { recursive: true });
13545
13595
  const tmp = `${path}.${process.pid}.tmp`;
13546
13596
  writeFileSync10(tmp, `${JSON.stringify(state, null, 2)}
13547
13597
  `, { mode: 384 });
@@ -13579,10 +13629,10 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
13579
13629
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
13580
13630
  }
13581
13631
  function writeBootGuard(root = ownPackageRoot()) {
13582
- const built = join26(root, "dist", "boot-guard.js");
13632
+ const built = join27(root, "dist", "boot-guard.js");
13583
13633
  if (!existsSync17(built)) return false;
13584
13634
  const target = bootGuardPath();
13585
- mkdirSync16(dirname12(target), { recursive: true });
13635
+ mkdirSync16(dirname14(target), { recursive: true });
13586
13636
  const tmp = `${target}.${process.pid}.tmp`;
13587
13637
  copyFileSync3(built, tmp);
13588
13638
  renameSync11(tmp, target);
@@ -13619,17 +13669,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve2) => {
13619
13669
  async function stageVersion(version, harnesses, deps = {}) {
13620
13670
  const run = deps.run ?? runProcess;
13621
13671
  const node = deps.execPath ?? process.execPath;
13622
- const target = join26(versionsDir(), version);
13672
+ const target = join27(versionsDir(), version);
13623
13673
  const placed = versionPackageRoot(version);
13624
13674
  if (existsSync17(placed)) {
13625
13675
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
13626
13676
  return { ok: true, root: placed };
13627
13677
  }
13628
- const npm = join26(dirname12(node), "npm");
13678
+ const npm = join27(dirname14(node), "npm");
13629
13679
  if (!existsSync17(npm))
13630
- return { ok: false, reason: `there is no npm beside Node at ${dirname12(node)}` };
13680
+ return { ok: false, reason: `there is no npm beside Node at ${dirname14(node)}` };
13631
13681
  mkdirSync16(versionsDir(), { recursive: true });
13632
- const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
13682
+ const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
13633
13683
  rmSync14(staging, { recursive: true, force: true });
13634
13684
  const installed = await run(
13635
13685
  npm,
@@ -13646,7 +13696,7 @@ async function stageVersion(version, harnesses, deps = {}) {
13646
13696
  ],
13647
13697
  {
13648
13698
  // npm's install scripts run on this Node, not whichever `node` is first on PATH.
13649
- env: { ...process.env, PATH: `${dirname12(node)}:${process.env.PATH ?? ""}` },
13699
+ env: { ...process.env, PATH: `${dirname14(node)}:${process.env.PATH ?? ""}` },
13650
13700
  timeoutMs: 15 * 6e4
13651
13701
  }
13652
13702
  );
@@ -13658,7 +13708,7 @@ async function stageVersion(version, harnesses, deps = {}) {
13658
13708
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
13659
13709
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
13660
13710
  }
13661
- const root = join26(staging, "node_modules", "@cabane", "companion");
13711
+ const root = join27(staging, "node_modules", "@cabane", "companion");
13662
13712
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
13663
13713
  if (verdict) {
13664
13714
  rmSync14(staging, { recursive: true, force: true });
@@ -13670,7 +13720,7 @@ async function stageVersion(version, harnesses, deps = {}) {
13670
13720
  }
13671
13721
  async function verifyInstall(root, version, harnesses, deps) {
13672
13722
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
13673
- const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
13723
+ const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
13674
13724
  timeoutMs: 6e4,
13675
13725
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
13676
13726
  });
@@ -13678,7 +13728,7 @@ async function verifyInstall(root, version, harnesses, deps) {
13678
13728
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
13679
13729
  }
13680
13730
  if (harnesses.length === 0) return null;
13681
- const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
13731
+ const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
13682
13732
  timeoutMs: 6e4
13683
13733
  });
13684
13734
  let resolutions = {};
@@ -14259,7 +14309,7 @@ var CompanionSupervisor = class {
14259
14309
  this.versionSkewWarned = true;
14260
14310
  this.log.warn(
14261
14311
  { companionVersion: COMPANION_VERSION, serverVersion },
14262
- "This companion and Cabane are on different versions. If turns fail, update with npm i -g @cabane/companion@latest"
14312
+ `This companion and Cabane are on different versions. If turns fail, update with ${cabane("update")}`
14263
14313
  );
14264
14314
  }
14265
14315
  // Pull assignments and reconcile the live runner set against them. Re-entrancy
@@ -14488,7 +14538,8 @@ var CompanionSupervisor = class {
14488
14538
  bundled: this.bundledStatus("claude-code")
14489
14539
  },
14490
14540
  codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
14491
- install: companionInstall()
14541
+ install: companionInstall(),
14542
+ managed: repairRoute() === "managed"
14492
14543
  });
14493
14544
  }
14494
14545
  // A missing binary is reported ONCE, and again only when the answer CHANGES,
@@ -15443,9 +15494,9 @@ function handleUncaught(log, err, origin) {
15443
15494
 
15444
15495
  // src/crash-marker.ts
15445
15496
  import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync15, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "fs";
15446
- import { join as join27 } from "path";
15497
+ import { join as join28 } from "path";
15447
15498
  function crashMarkerPath() {
15448
- return join27(cabaneDir(), "last-error.json");
15499
+ return join28(cabaneDir(), "last-error.json");
15449
15500
  }
15450
15501
  function recordCrash(rec) {
15451
15502
  try {