@cabane/companion 0.6.153 → 0.6.155

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -585,7 +585,8 @@ var init_config = __esm({
585
585
  // src/bundled-binaries.ts
586
586
  import { realpathSync, statSync } from "fs";
587
587
  import { createRequire } from "module";
588
- import { dirname as dirname2, join as join3 } from "path";
588
+ import { homedir as homedir2 } from "os";
589
+ import { dirname as dirname2, join as join3, sep } from "path";
589
590
  import { fileURLToPath } from "url";
590
591
  function nodeResolveSelf(specifier) {
591
592
  const here = fileURLToPath(import.meta.url);
@@ -741,25 +742,36 @@ function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
741
742
  function missingNoun(status2) {
742
743
  return status2 === "executable_missing" ? "executable" : "platform package";
743
744
  }
744
- function repairCommands() {
745
- return ["npm i -g @cabane/companion --include=optional"];
745
+ function companionInstall(execPath = process.execPath, home = homedir2()) {
746
+ const real = (path) => {
747
+ try {
748
+ return realpathSync(path);
749
+ } catch {
750
+ return path;
751
+ }
752
+ };
753
+ const scriptNode = join3(real(home), ".cabane", "node") + sep;
754
+ return real(execPath).startsWith(scriptNode) ? "script" : "npm";
755
+ }
756
+ function repairCommands(install = companionInstall()) {
757
+ return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
746
758
  }
747
- function repairAction() {
748
- return `Reinstall with \`${repairCommands()[0]}\`.`;
759
+ function repairAction(install = companionInstall()) {
760
+ return `Reinstall with \`${repairCommands(install)[0]}\`.`;
749
761
  }
750
762
  function startupWarning(r) {
751
763
  const label = HARNESS_LABEL[r.runtime];
752
764
  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.`;
753
765
  }
754
- function harnessIssueNote(runtime, status2) {
766
+ function harnessIssueNote(runtime, status2, install = companionInstall()) {
755
767
  const intent = HARNESS_INTENT_WORD[runtime];
756
768
  const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
757
- return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction()} Then restart the companion.`;
769
+ return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction(install)} Then restart the companion.`;
758
770
  }
759
771
  function turnIncompleteCopy(runtime) {
760
772
  return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
761
773
  }
762
- var CLAUDE_SDK, CODEX_SDK, CODEX_CLI, CODEX_PACKAGE_BY_TRIPLE, HARNESS_LABEL, HARNESS_INTENT_WORD;
774
+ var CLAUDE_SDK, CODEX_SDK, CODEX_CLI, CODEX_PACKAGE_BY_TRIPLE, HARNESS_LABEL, HARNESS_INTENT_WORD, INSTALL_SCRIPT_COMMAND;
763
775
  var init_bundled_binaries = __esm({
764
776
  "src/bundled-binaries.ts"() {
765
777
  "use strict";
@@ -782,12 +794,13 @@ var init_bundled_binaries = __esm({
782
794
  "claude-code": "connected",
783
795
  codex: "enabled"
784
796
  };
797
+ INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
785
798
  }
786
799
  });
787
800
 
788
801
  // src/term.ts
789
802
  import { createInterface } from "readline";
790
- import { homedir as homedir2 } from "os";
803
+ import { homedir as homedir3 } from "os";
791
804
  import pc from "picocolors";
792
805
  function isInteractive() {
793
806
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -800,7 +813,7 @@ function blank() {
800
813
  process.stdout.write("\n");
801
814
  }
802
815
  function tildePath(path) {
803
- const home = homedir2();
816
+ const home = homedir3();
804
817
  if (home && path.startsWith(home + "/")) return `~${path.slice(home.length)}`;
805
818
  return path;
806
819
  }
@@ -2543,8 +2556,8 @@ var init_policy = __esm({
2543
2556
  // packages/agent-runtime/src/claude-code/model.ts
2544
2557
  function parseClaudeCodeModel(model) {
2545
2558
  if (!model) return null;
2546
- const sep3 = model.indexOf("/");
2547
- const bare = sep3 === -1 || model.slice(0, sep3) !== "anthropic" ? model : model.slice(sep3 + 1);
2559
+ const sep4 = model.indexOf("/");
2560
+ const bare = sep4 === -1 || model.slice(0, sep4) !== "anthropic" ? model : model.slice(sep4 + 1);
2548
2561
  return bare === CLAUDE_CODE_DEFAULT_MODEL_BARE ? null : model;
2549
2562
  }
2550
2563
  var CLAUDE_CODE_DEFAULT_MODEL_BARE;
@@ -4097,9 +4110,9 @@ var init_policy2 = __esm({
4097
4110
 
4098
4111
  // packages/agent-runtime/src/opencode/model.ts
4099
4112
  function parseOpencodeModel(model) {
4100
- const sep3 = model.indexOf("/");
4101
- if (sep3 === -1) return { providerID: "", modelID: model };
4102
- return { providerID: model.slice(0, sep3), modelID: model.slice(sep3 + 1) };
4113
+ const sep4 = model.indexOf("/");
4114
+ if (sep4 === -1) return { providerID: "", modelID: model };
4115
+ return { providerID: model.slice(0, sep4), modelID: model.slice(sep4 + 1) };
4103
4116
  }
4104
4117
  var init_model2 = __esm({
4105
4118
  "packages/agent-runtime/src/opencode/model.ts"() {
@@ -4400,10 +4413,10 @@ async function* parseSseStream(body, sessionId, signal) {
4400
4413
  const { value, done } = await reader.read();
4401
4414
  if (done) return;
4402
4415
  buf += decoder.decode(value, { stream: true });
4403
- let sep3;
4404
- while ((sep3 = buf.indexOf("\n\n")) !== -1) {
4405
- const frame = buf.slice(0, sep3);
4406
- buf = buf.slice(sep3 + 2);
4416
+ let sep4;
4417
+ while ((sep4 = buf.indexOf("\n\n")) !== -1) {
4418
+ const frame = buf.slice(0, sep4);
4419
+ buf = buf.slice(sep4 + 2);
4407
4420
  const data = frame.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("");
4408
4421
  if (!data) continue;
4409
4422
  let ev;
@@ -5414,8 +5427,8 @@ var init_policy3 = __esm({
5414
5427
 
5415
5428
  // packages/agent-runtime/src/codex/model.ts
5416
5429
  function parseCodexModel(model) {
5417
- const sep3 = model.indexOf("/");
5418
- const bare = sep3 === -1 || model.slice(0, sep3) !== "openai" ? model : model.slice(sep3 + 1);
5430
+ const sep4 = model.indexOf("/");
5431
+ const bare = sep4 === -1 || model.slice(0, sep4) !== "openai" ? model : model.slice(sep4 + 1);
5419
5432
  return CODEX_DEFAULT_MODEL_BARES.has(bare) ? null : bare;
5420
5433
  }
5421
5434
  var CODEX_DEFAULT_MODEL_BARES;
@@ -10232,6 +10245,23 @@ var init_dispatcher = __esm({
10232
10245
  }
10233
10246
  return true;
10234
10247
  }
10248
+ // Abort every run in flight in THIS process, whatever its conversation. For
10249
+ // an agent taken off this device: nothing it is still running here is
10250
+ // legitimate, and a cancel frame that arrives after it is dropped from the
10251
+ // roster finds no dispatcher to reach. True if any was aborted.
10252
+ cancelAll() {
10253
+ let aborted = false;
10254
+ for (const pair2 of this.aborts.values()) {
10255
+ for (const ac of [...pair2.values()]) {
10256
+ try {
10257
+ ac.abort();
10258
+ aborted = true;
10259
+ } catch {
10260
+ }
10261
+ }
10262
+ }
10263
+ return aborted;
10264
+ }
10235
10265
  };
10236
10266
  }
10237
10267
  });
@@ -10493,8 +10523,8 @@ import {
10493
10523
  } from "fs";
10494
10524
  import { createRequire as createRequire2 } from "module";
10495
10525
  import { isIP } from "net";
10496
- import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
10497
- import { dirname as dirname8, join as join19, sep as sep2 } from "path";
10526
+ import { homedir as homedir4, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
10527
+ import { dirname as dirname8, join as join19, sep as sep3 } from "path";
10498
10528
  import { fileURLToPath as fileURLToPath4 } from "url";
10499
10529
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
10500
10530
  function accountHome() {
@@ -10620,13 +10650,13 @@ function companionStoreWatch(log) {
10620
10650
  return new CompanionStoreWatch({
10621
10651
  log,
10622
10652
  roots: () => {
10623
- const home = homedir3();
10653
+ const home = homedir4();
10624
10654
  return storeWalkRoots([.../* @__PURE__ */ new Set([home, accountHome() ?? home])], cabaneDir());
10625
10655
  }
10626
10656
  });
10627
10657
  }
10628
10658
  function isAtOrUnder(path, dir2) {
10629
- return path === dir2 || path.startsWith(dir2 + sep2);
10659
+ return path === dir2 || path.startsWith(dir2 + sep3);
10630
10660
  }
10631
10661
  function expandHome(path, home) {
10632
10662
  if (path === "~") return home;
@@ -10765,7 +10795,7 @@ async function prepareHarnessSandbox(input) {
10765
10795
  baseUrl: input.baseUrl,
10766
10796
  resolved: await resolvedAddresses(input.baseUrl),
10767
10797
  ownAddresses: ownAddresses(),
10768
- home: homedir3(),
10798
+ home: homedir4(),
10769
10799
  accountHome: accountHome(),
10770
10800
  tmp: tmpdir4(),
10771
10801
  cabaneDir: cabaneDir(),
@@ -11797,7 +11827,7 @@ function runtimeIssuesFor(gates) {
11797
11827
  for (const name of ["claude-code", "codex"]) {
11798
11828
  const gate = name === "codex" ? gates.codex : gates.claudeCode;
11799
11829
  if (!gate.intended || gate.bundled === "present") continue;
11800
- issues.push({ name, reason: gate.bundled });
11830
+ issues.push({ name, reason: gate.bundled, install: gates.install });
11801
11831
  }
11802
11832
  return issues;
11803
11833
  }
@@ -13994,7 +14024,7 @@ function publishStaged(from, to, move = atomicMove) {
13994
14024
  // src/providers/runner-base.ts
13995
14025
  import { createHash as createHash3 } from "crypto";
13996
14026
  import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
13997
- import { dirname as dirname7, join as join17, sep } from "path";
14027
+ import { dirname as dirname7, join as join17, sep as sep2 } from "path";
13998
14028
  import { fileURLToPath as fileURLToPath3 } from "url";
13999
14029
  var COMPANION_MOUNT = "/opt/cabane/companion";
14000
14030
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -14049,7 +14079,7 @@ function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
14049
14079
  const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
14050
14080
  if (!existsSync12(sdk)) return null;
14051
14081
  const root = realpathSync2(dir2);
14052
- return realpathSync2(sdk).startsWith(root + sep) ? root : null;
14082
+ return realpathSync2(sdk).startsWith(root + sep2) ? root : null;
14053
14083
  }
14054
14084
  function companionSource(input) {
14055
14085
  if (input.configured) {
@@ -15738,6 +15768,12 @@ var CompanionSupervisor = class {
15738
15768
  removeAgent(wr, agentId) {
15739
15769
  const runner = wr.agents.get(agentId);
15740
15770
  if (!runner) return;
15771
+ if (runner.dispatcher.cancelAll()) {
15772
+ this.log.info(
15773
+ { workspaceId: wr.workspaceId, agentId },
15774
+ `Stopped ${runner.displayName}'s running turns (no longer on this device)`
15775
+ );
15776
+ }
15741
15777
  runner.cancelDrain();
15742
15778
  wr.agents.delete(agentId);
15743
15779
  this.hub.removeAgent(wr.workspaceId, agentId);
@@ -15778,7 +15814,8 @@ var CompanionSupervisor = class {
15778
15814
  intended: isClaudeCodeConnected(this.config),
15779
15815
  bundled: this.bundledStatus("claude-code")
15780
15816
  },
15781
- codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") }
15817
+ codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
15818
+ install: companionInstall()
15782
15819
  });
15783
15820
  }
15784
15821
  // A missing binary is reported ONCE, and again only when the answer CHANGES,
@@ -1,7 +1,8 @@
1
1
  // src/bundled-binaries.ts
2
2
  import { realpathSync, statSync } from "fs";
3
3
  import { createRequire } from "module";
4
- import { dirname, join } from "path";
4
+ import { homedir } from "os";
5
+ import { dirname, join, sep } from "path";
5
6
  import { fileURLToPath } from "url";
6
7
  function nodeResolveSelf(specifier) {
7
8
  const here = fileURLToPath(import.meta.url);
@@ -178,11 +179,23 @@ var HARNESS_LABEL = {
178
179
  function missingNoun(status) {
179
180
  return status === "executable_missing" ? "executable" : "platform package";
180
181
  }
181
- function repairCommands() {
182
- return ["npm i -g @cabane/companion --include=optional"];
182
+ var INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
183
+ function companionInstall(execPath = process.execPath, home = homedir()) {
184
+ const real = (path) => {
185
+ try {
186
+ return realpathSync(path);
187
+ } catch {
188
+ return path;
189
+ }
190
+ };
191
+ const scriptNode = join(real(home), ".cabane", "node") + sep;
192
+ return real(execPath).startsWith(scriptNode) ? "script" : "npm";
193
+ }
194
+ function repairCommands(install = companionInstall()) {
195
+ return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
183
196
  }
184
- function repairAction() {
185
- return `Reinstall with \`${repairCommands()[0]}\`.`;
197
+ function repairAction(install = companionInstall()) {
198
+ return `Reinstall with \`${repairCommands(install)[0]}\`.`;
186
199
  }
187
200
  function installWarning(r) {
188
201
  return `warning: @cabane/companion is incomplete: its bundled ${HARNESS_LABEL[r.runtime]} ${missingNoun(r.status)} for ${r.platform}-${r.arch} is missing. ${repairAction()}`;
package/dist/runtime.js CHANGED
@@ -602,7 +602,8 @@ async function safe(fn) {
602
602
  // src/bundled-binaries.ts
603
603
  import { realpathSync, statSync } from "fs";
604
604
  import { createRequire } from "module";
605
- import { dirname as dirname2, join as join2 } from "path";
605
+ import { homedir as homedir2 } from "os";
606
+ import { dirname as dirname2, join as join2, sep } from "path";
606
607
  import { fileURLToPath } from "url";
607
608
  function nodeResolveSelf(specifier) {
608
609
  const here = fileURLToPath(import.meta.url);
@@ -777,20 +778,32 @@ var HARNESS_INTENT_WORD = {
777
778
  function missingNoun(status) {
778
779
  return status === "executable_missing" ? "executable" : "platform package";
779
780
  }
780
- function repairCommands() {
781
- return ["npm i -g @cabane/companion --include=optional"];
781
+ var INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
782
+ function companionInstall(execPath = process.execPath, home = homedir2()) {
783
+ const real = (path) => {
784
+ try {
785
+ return realpathSync(path);
786
+ } catch {
787
+ return path;
788
+ }
789
+ };
790
+ const scriptNode = join2(real(home), ".cabane", "node") + sep;
791
+ return real(execPath).startsWith(scriptNode) ? "script" : "npm";
792
+ }
793
+ function repairCommands(install = companionInstall()) {
794
+ return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
782
795
  }
783
- function repairAction() {
784
- return `Reinstall with \`${repairCommands()[0]}\`.`;
796
+ function repairAction(install = companionInstall()) {
797
+ return `Reinstall with \`${repairCommands(install)[0]}\`.`;
785
798
  }
786
799
  function startupWarning(r) {
787
800
  const label = HARNESS_LABEL[r.runtime];
788
801
  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.`;
789
802
  }
790
- function harnessIssueNote(runtime, status) {
803
+ function harnessIssueNote(runtime, status, install = companionInstall()) {
791
804
  const intent = HARNESS_INTENT_WORD[runtime];
792
805
  const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
793
- return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status)} is missing. ${repairAction()} Then restart the companion.`;
806
+ return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status)} is missing. ${repairAction(install)} Then restart the companion.`;
794
807
  }
795
808
  function turnIncompleteCopy(runtime) {
796
809
  return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
@@ -819,7 +832,7 @@ function runtimeIssuesFor(gates) {
819
832
  for (const name of ["claude-code", "codex"]) {
820
833
  const gate = name === "codex" ? gates.codex : gates.claudeCode;
821
834
  if (!gate.intended || gate.bundled === "present") continue;
822
- issues.push({ name, reason: gate.bundled });
835
+ issues.push({ name, reason: gate.bundled, install: gates.install });
823
836
  }
824
837
  return issues;
825
838
  }
@@ -3951,8 +3964,8 @@ function readThinking(runtimeOptions) {
3951
3964
  var CLAUDE_CODE_DEFAULT_MODEL_BARE = "default";
3952
3965
  function parseClaudeCodeModel(model) {
3953
3966
  if (!model) return null;
3954
- const sep3 = model.indexOf("/");
3955
- const bare = sep3 === -1 || model.slice(0, sep3) !== "anthropic" ? model : model.slice(sep3 + 1);
3967
+ const sep4 = model.indexOf("/");
3968
+ const bare = sep4 === -1 || model.slice(0, sep4) !== "anthropic" ? model : model.slice(sep4 + 1);
3956
3969
  return bare === CLAUDE_CODE_DEFAULT_MODEL_BARE ? null : model;
3957
3970
  }
3958
3971
 
@@ -5407,9 +5420,9 @@ function readOpencodeDialect(runtimeOptions) {
5407
5420
 
5408
5421
  // packages/agent-runtime/src/opencode/model.ts
5409
5422
  function parseOpencodeModel(model) {
5410
- const sep3 = model.indexOf("/");
5411
- if (sep3 === -1) return { providerID: "", modelID: model };
5412
- return { providerID: model.slice(0, sep3), modelID: model.slice(sep3 + 1) };
5423
+ const sep4 = model.indexOf("/");
5424
+ if (sep4 === -1) return { providerID: "", modelID: model };
5425
+ return { providerID: model.slice(0, sep4), modelID: model.slice(sep4 + 1) };
5413
5426
  }
5414
5427
 
5415
5428
  // packages/agent-runtime/src/opencode/run-spec.ts
@@ -5697,10 +5710,10 @@ async function* parseSseStream(body, sessionId, signal) {
5697
5710
  const { value, done } = await reader.read();
5698
5711
  if (done) return;
5699
5712
  buf += decoder.decode(value, { stream: true });
5700
- let sep3;
5701
- while ((sep3 = buf.indexOf("\n\n")) !== -1) {
5702
- const frame = buf.slice(0, sep3);
5703
- buf = buf.slice(sep3 + 2);
5713
+ let sep4;
5714
+ while ((sep4 = buf.indexOf("\n\n")) !== -1) {
5715
+ const frame = buf.slice(0, sep4);
5716
+ buf = buf.slice(sep4 + 2);
5704
5717
  const data = frame.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("");
5705
5718
  if (!data) continue;
5706
5719
  let ev;
@@ -6640,8 +6653,8 @@ function readCodexDialect(runtimeOptions) {
6640
6653
  // packages/agent-runtime/src/codex/model.ts
6641
6654
  var CODEX_DEFAULT_MODEL_BARES = /* @__PURE__ */ new Set(["default"]);
6642
6655
  function parseCodexModel(model) {
6643
- const sep3 = model.indexOf("/");
6644
- const bare = sep3 === -1 || model.slice(0, sep3) !== "openai" ? model : model.slice(sep3 + 1);
6656
+ const sep4 = model.indexOf("/");
6657
+ const bare = sep4 === -1 || model.slice(0, sep4) !== "openai" ? model : model.slice(sep4 + 1);
6645
6658
  return CODEX_DEFAULT_MODEL_BARES.has(bare) ? null : bare;
6646
6659
  }
6647
6660
 
@@ -9158,10 +9171,10 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
9158
9171
 
9159
9172
  // src/term.ts
9160
9173
  import { createInterface as createInterface2 } from "readline";
9161
- import { homedir as homedir2 } from "os";
9174
+ import { homedir as homedir3 } from "os";
9162
9175
  import pc from "picocolors";
9163
9176
  function tildePath(path) {
9164
- const home = homedir2();
9177
+ const home = homedir3();
9165
9178
  if (home && path.startsWith(home + "/")) return `~${path.slice(home.length)}`;
9166
9179
  return path;
9167
9180
  }
@@ -11196,6 +11209,23 @@ var Dispatcher = class {
11196
11209
  }
11197
11210
  return true;
11198
11211
  }
11212
+ // Abort every run in flight in THIS process, whatever its conversation. For
11213
+ // an agent taken off this device: nothing it is still running here is
11214
+ // legitimate, and a cancel frame that arrives after it is dropped from the
11215
+ // roster finds no dispatcher to reach. True if any was aborted.
11216
+ cancelAll() {
11217
+ let aborted = false;
11218
+ for (const pair of this.aborts.values()) {
11219
+ for (const ac of [...pair.values()]) {
11220
+ try {
11221
+ ac.abort();
11222
+ aborted = true;
11223
+ } catch {
11224
+ }
11225
+ }
11226
+ }
11227
+ return aborted;
11228
+ }
11199
11229
  };
11200
11230
 
11201
11231
  // src/providers/local-process.ts
@@ -11570,7 +11600,7 @@ function publishStaged(from, to, move = atomicMove) {
11570
11600
  // src/providers/runner-base.ts
11571
11601
  import { createHash as createHash3 } from "crypto";
11572
11602
  import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
11573
- import { dirname as dirname7, join as join17, sep } from "path";
11603
+ import { dirname as dirname7, join as join17, sep as sep2 } from "path";
11574
11604
  import { fileURLToPath as fileURLToPath3 } from "url";
11575
11605
  var COMPANION_MOUNT = "/opt/cabane/companion";
11576
11606
  var BASE_DOCKERFILE = `FROM node:22-slim
@@ -11625,7 +11655,7 @@ function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
11625
11655
  const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
11626
11656
  if (!existsSync12(sdk)) return null;
11627
11657
  const root = realpathSync2(dir2);
11628
- return realpathSync2(sdk).startsWith(root + sep) ? root : null;
11658
+ return realpathSync2(sdk).startsWith(root + sep2) ? root : null;
11629
11659
  }
11630
11660
  function companionSource(input) {
11631
11661
  if (input.configured) {
@@ -12659,8 +12689,8 @@ import {
12659
12689
  } from "fs";
12660
12690
  import { createRequire as createRequire2 } from "module";
12661
12691
  import { isIP } from "net";
12662
- import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12663
- import { dirname as dirname8, join as join19, sep as sep2 } from "path";
12692
+ import { homedir as homedir4, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
12693
+ import { dirname as dirname8, join as join19, sep as sep3 } from "path";
12664
12694
  import { fileURLToPath as fileURLToPath4 } from "url";
12665
12695
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
12666
12696
  function accountHome() {
@@ -12757,13 +12787,13 @@ function companionStoreWatch(log) {
12757
12787
  return new CompanionStoreWatch({
12758
12788
  log,
12759
12789
  roots: () => {
12760
- const home = homedir3();
12790
+ const home = homedir4();
12761
12791
  return storeWalkRoots([.../* @__PURE__ */ new Set([home, accountHome() ?? home])], cabaneDir());
12762
12792
  }
12763
12793
  });
12764
12794
  }
12765
12795
  function isAtOrUnder(path, dir2) {
12766
- return path === dir2 || path.startsWith(dir2 + sep2);
12796
+ return path === dir2 || path.startsWith(dir2 + sep3);
12767
12797
  }
12768
12798
  function osSandboxUsable(log) {
12769
12799
  if (!SandboxManager.isSupportedPlatform()) {
@@ -13612,6 +13642,12 @@ var CompanionSupervisor = class {
13612
13642
  removeAgent(wr, agentId) {
13613
13643
  const runner = wr.agents.get(agentId);
13614
13644
  if (!runner) return;
13645
+ if (runner.dispatcher.cancelAll()) {
13646
+ this.log.info(
13647
+ { workspaceId: wr.workspaceId, agentId },
13648
+ `Stopped ${runner.displayName}'s running turns (no longer on this device)`
13649
+ );
13650
+ }
13615
13651
  runner.cancelDrain();
13616
13652
  wr.agents.delete(agentId);
13617
13653
  this.hub.removeAgent(wr.workspaceId, agentId);
@@ -13652,7 +13688,8 @@ var CompanionSupervisor = class {
13652
13688
  intended: isClaudeCodeConnected(this.config),
13653
13689
  bundled: this.bundledStatus("claude-code")
13654
13690
  },
13655
- codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") }
13691
+ codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
13692
+ install: companionInstall()
13656
13693
  });
13657
13694
  }
13658
13695
  // A missing binary is reported ONCE, and again only when the answer CHANGES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.153",
3
+ "version": "0.6.155",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",