@cabane/companion 0.6.174 → 0.6.175

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
@@ -12117,11 +12117,60 @@ init_config();
12117
12117
  init_bundled_binaries();
12118
12118
  init_cabane_command();
12119
12119
  init_config();
12120
+ import { homedir as homedir6 } from "os";
12121
+ import { join as join7 } from "path";
12122
+
12123
+ // src/device-state.ts
12124
+ var HARNESS_STATE_LABEL = {
12125
+ checking: "checking",
12126
+ connected: "connected",
12127
+ needs_sign_in: "needs sign-in",
12128
+ limited: "limited",
12129
+ problem: "problem",
12130
+ off: "off",
12131
+ gone: "gone"
12132
+ };
12133
+
12134
+ // src/harness-state.ts
12135
+ init_config();
12136
+ function harnessEntry(runtime, state) {
12137
+ switch (state.state) {
12138
+ case "usable":
12139
+ return {
12140
+ runtime,
12141
+ state: "connected",
12142
+ reason: state.via,
12143
+ version: state.version,
12144
+ ...state.url ? { url: state.url } : {}
12145
+ };
12146
+ case "signed_out":
12147
+ return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
12148
+ case "not_running":
12149
+ return { runtime, state: "problem", reason: "not_running", url: state.url };
12150
+ case "off":
12151
+ return { runtime, state: "off" };
12152
+ case "problem":
12153
+ return { runtime, state: "problem", reason: checkReason(state.check) };
12154
+ }
12155
+ }
12156
+ function harnessEntriesFromDetection(detection) {
12157
+ return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
12158
+ }
12159
+ function checkReason(check) {
12160
+ switch (check.status) {
12161
+ case "bundle_missing":
12162
+ return check.bundled;
12163
+ case "check_failed":
12164
+ return `check_failed:${check.reason}`;
12165
+ default:
12166
+ return check.status;
12167
+ }
12168
+ }
12169
+
12170
+ // src/harness-copy.ts
12120
12171
  init_install_kind();
12121
12172
  init_install_record();
12122
12173
  init_term();
12123
- import { homedir as homedir6 } from "os";
12124
- import { join as join7 } from "path";
12125
12174
  var HARNESS_LABELS = {
12126
12175
  "claude-code": "Claude Code",
12127
12176
  codex: "Codex",
@@ -12176,23 +12225,23 @@ function agentRows(detection, opts = {}) {
12176
12225
  });
12177
12226
  }
12178
12227
  function agentRowText(runtime, state, opts = {}) {
12179
- const connected = opts.connected ?? true ? " \u2014 connected" : "";
12228
+ const word = HARNESS_STATE_LABEL[harnessEntry(runtime, state).state];
12180
12229
  switch (state.state) {
12181
12230
  case "usable": {
12182
12231
  const version = state.version ? ` (${state.version})` : "";
12183
- if (state.via === "api_key") return `API key configured${version}${connected}`;
12184
- if (state.via === "container") return `connected \u2014 turns run in Docker`;
12185
- if (state.via === "serve") return `running at ${state.url ?? ""}${connected}`;
12186
- return `signed in${version}${connected}`;
12232
+ if (state.via === "api_key") return `${word} \u2014 API key configured${version}`;
12233
+ if (state.via === "container") return `${word} \u2014 turns run in Docker`;
12234
+ if (state.via === "serve") return `${word} \u2014 running at ${state.url ?? ""}`;
12235
+ return `${word} \u2014 signed in${version}`;
12187
12236
  }
12188
12237
  case "signed_out":
12189
- return opts.signInHint && runtime !== "opencode" ? `not signed in \u2014 run ${loginCommand(runtime)}` : "not signed in";
12238
+ return opts.signInHint && runtime !== "opencode" ? `${word} \u2014 run ${loginCommand(runtime)}` : word;
12190
12239
  case "not_running":
12191
- return "not running";
12240
+ return `${word} \u2014 not running`;
12192
12241
  case "off":
12193
- return "turned off in config";
12242
+ return `${word} \u2014 turned off in config`;
12194
12243
  case "problem":
12195
- return runtime === "opencode" ? "not running" : unusableReason(runtime, state.check) ?? "not signed in";
12244
+ return runtime === "opencode" ? `${word} \u2014 not running` : `${word} \u2014 ${unusableReason(runtime, state.check) ?? "could not be checked"}`;
12196
12245
  }
12197
12246
  }
12198
12247
  var AGENT_CHOICES = [
@@ -12281,7 +12330,7 @@ function nothingUsableLines(detection) {
12281
12330
  if (reason) lines2.push(reason);
12282
12331
  }
12283
12332
  }
12284
- lines2.push(`Cabane can run while you sign in to a coding tool.`);
12333
+ lines2.push(`Cabane can run while you sign in to a harness.`);
12285
12334
  return lines2;
12286
12335
  }
12287
12336
  function indent(line) {
@@ -13460,7 +13509,7 @@ function turnFailureCopy(reason, runtime) {
13460
13509
  if (reason?.startsWith("runtime_incomplete:"))
13461
13510
  return `The agent installation is incomplete. ${repairAction()} Then restart the companion.`;
13462
13511
  if (/^(runtime|codex|opencode)_unavailable:/.test(reason ?? ""))
13463
- return "The agent is unavailable on this device. Enable it here or choose another connector in Cabane.";
13512
+ return "The harness is off on this device. Enable it here, or choose another harness or device in Cabane.";
13464
13513
  if (reason?.startsWith("model_unavailable:"))
13465
13514
  return "The selected model is unavailable. Choose a different model in the agent settings in Cabane.";
13466
13515
  if (reason?.startsWith("prepare_failed:"))
@@ -13839,7 +13888,6 @@ init_install_kind();
13839
13888
  init_src();
13840
13889
  var ConnectorHealthStore = class {
13841
13890
  byRuntime = /* @__PURE__ */ new Map();
13842
- sources = /* @__PURE__ */ new Map();
13843
13891
  // Record a settled turn's outcome for `runtime`. A success heals to `ok`; a
13844
13892
  // classified failure sets `limited`(+reset) / `auth_expired` with the reason as
13845
13893
  // `last_error`; anything else (a user Stop, a timeout, an unclassified error)
@@ -13848,7 +13896,6 @@ var ConnectorHealthStore = class {
13848
13896
  recordSettle(runtime, outcome) {
13849
13897
  if (outcome.ok) {
13850
13898
  this.byRuntime.set(runtime, { quotaState: "ok", limitedUntil: null, lastError: null });
13851
- this.sources.set(runtime, "turn");
13852
13899
  return;
13853
13900
  }
13854
13901
  const failure = decodeFailureReason(outcome.errorReason);
@@ -13860,26 +13907,6 @@ var ConnectorHealthStore = class {
13860
13907
  limitedUntil: quota.limitedUntil,
13861
13908
  lastError: outcome.errorReason ?? null
13862
13909
  });
13863
- this.sources.set(runtime, "turn");
13864
- }
13865
- // Record what the companion's own sign-in check found for a connected
13866
- // runtime at `start` or `connect`, so the heartbeat carries "sign-in needed"
13867
- // before any turn fails. Signed out seeds `auth_expired` unless a turn has
13868
- // already spoken; usable clears only a seed of our own.
13869
- recordLocalCheck(runtime, usable) {
13870
- const source = this.sources.get(runtime);
13871
- if (!usable) {
13872
- if (source === "turn" && this.byRuntime.get(runtime)?.quotaState !== "ok") return;
13873
- this.byRuntime.set(runtime, {
13874
- quotaState: "auth_expired",
13875
- limitedUntil: null,
13876
- lastError: encodeFailureReason({ kind: "auth_expired" })
13877
- });
13878
- this.sources.set(runtime, "local");
13879
- return;
13880
- }
13881
- if (source !== "local") return;
13882
- this.byRuntime.set(runtime, { quotaState: "ok", limitedUntil: null, lastError: null });
13883
13910
  }
13884
13911
  // The store's current opinion for one runtime, or undefined when it has
13885
13912
  // no observation. Read by the dispatcher at idle-timeout fire: an idle reap
@@ -16364,6 +16391,9 @@ var CompanionSupervisor = class {
16364
16391
  // Omitted until the first observed failure or heal, so the server's
16365
16392
  // status-less manifest synthesis still runs for a fresh companion.
16366
16393
  ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
16394
+ // The complete harness inventory in the machine's words, once detection
16395
+ // has run; omitted before it, which the server reads as "nothing yet".
16396
+ ...this.detection ? { harnesses: harnessEntriesFromDetection(this.detection) } : {},
16367
16397
  ...opencodeModels !== null ? { models: opencodeModels } : {},
16368
16398
  ...this.updater ? { update: this.updater.report() } : {}
16369
16399
  });
@@ -17316,7 +17346,6 @@ var CompanionSupervisor = class {
17316
17346
  ...this.probeOpencode ? { probeOpencode: this.probeOpencode } : {}
17317
17347
  });
17318
17348
  this.detection = detection;
17319
- this.seedSignInHealth(previous, detection);
17320
17349
  this.logBundledTransitions();
17321
17350
  this.harnessVersions = {
17322
17351
  claudeCode: detectedVersion(detection["claude-code"]),
@@ -17374,23 +17403,6 @@ var CompanionSupervisor = class {
17374
17403
  const bare = advertisedRuntimes(this.detection).filter((runtime) => !named.has(runtime)).map((runtime) => ({ runtime }));
17375
17404
  return [...health, ...bare];
17376
17405
  }
17377
- // Seed connector health from what detection saw of a sign-in, so Cabane says
17378
- // "sign-in needed" before a turn fails. Only a LAPSE seeds it — an agent that
17379
- // was usable at an earlier look and is signed out now — because a device
17380
- // that never signed in to Codex has nothing to report about Codex. Usable
17381
- // again clears a seed of our own.
17382
- seedSignInHealth(previous, detection) {
17383
- for (const runtime of ["claude-code", "codex"]) {
17384
- const now = detection[runtime];
17385
- if (now.state === "usable") {
17386
- if (now.via !== "container") this.connectorHealth.recordLocalCheck(runtime, true);
17387
- continue;
17388
- }
17389
- if (now.state === "signed_out" && previous?.[runtime].state === "usable") {
17390
- this.connectorHealth.recordLocalCheck(runtime, false);
17391
- }
17392
- }
17393
- }
17394
17406
  // Beat NOW and wait for it to land. `start` calls this before asking the
17395
17407
  // person anything, so the browser's connect step already shows what this
17396
17408
  // machine has rather than sitting blank while the terminal waits on an answer.
@@ -18363,7 +18375,7 @@ async function startScript(opts, interactive) {
18363
18375
  }
18364
18376
  if (!isDevicePaired()) {
18365
18377
  refuse([
18366
- `This device isn't set up yet. Run ${cabane("setup")} to set up a coding tool and pair it.`
18378
+ `This device isn't set up yet. Run ${cabane("setup")} to sign in to a harness and pair it.`
18367
18379
  ]);
18368
18380
  return;
18369
18381
  }
@@ -18814,7 +18826,7 @@ async function setupScript(opts, deps) {
18814
18826
  const detection = await detect(cfg, running, deps);
18815
18827
  ui.intro("Cabane setup");
18816
18828
  const usable = anyUsable(detection);
18817
- ui.step(["Coding tools on this device", ...agentLines(detection, { signInHint: !usable })]);
18829
+ ui.step(["Harnesses on this device", ...agentLines(detection, { signInHint: !usable })]);
18818
18830
  if (!usable && hasProblem(detection)) {
18819
18831
  ui.cancel(`Fix the above, then run ${cabane("setup")} again.`);
18820
18832
  process.exitCode = 1;
@@ -19214,7 +19226,7 @@ function strayStatusLines(running, find = findStrayCompanions) {
19214
19226
  }
19215
19227
  async function agentLines2(cfg, running) {
19216
19228
  const detection = running ?? await detectHarnesses(cfg);
19217
- return agentRows(detection, { connected: false }).map((agent) => row2(agent.label, agent.text));
19229
+ return agentRows(detection).map((agent) => row2(agent.label, agent.text));
19218
19230
  }
19219
19231
  function sandboxStatusLines(cfg) {
19220
19232
  const rows = agentSandboxRows(cfg);
package/dist/runtime.js CHANGED
@@ -1860,7 +1860,7 @@ function turnFailureCopy(reason, runtime) {
1860
1860
  if (reason?.startsWith("runtime_incomplete:"))
1861
1861
  return `The agent installation is incomplete. ${repairAction()} Then restart the companion.`;
1862
1862
  if (/^(runtime|codex|opencode)_unavailable:/.test(reason ?? ""))
1863
- return "The agent is unavailable on this device. Enable it here or choose another connector in Cabane.";
1863
+ return "The harness is off on this device. Enable it here, or choose another harness or device in Cabane.";
1864
1864
  if (reason?.startsWith("model_unavailable:"))
1865
1865
  return "The selected model is unavailable. Choose a different model in the agent settings in Cabane.";
1866
1866
  if (reason?.startsWith("prepare_failed:"))
@@ -7761,7 +7761,6 @@ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
7761
7761
  // src/connector-health.ts
7762
7762
  var ConnectorHealthStore = class {
7763
7763
  byRuntime = /* @__PURE__ */ new Map();
7764
- sources = /* @__PURE__ */ new Map();
7765
7764
  // Record a settled turn's outcome for `runtime`. A success heals to `ok`; a
7766
7765
  // classified failure sets `limited`(+reset) / `auth_expired` with the reason as
7767
7766
  // `last_error`; anything else (a user Stop, a timeout, an unclassified error)
@@ -7770,7 +7769,6 @@ var ConnectorHealthStore = class {
7770
7769
  recordSettle(runtime, outcome) {
7771
7770
  if (outcome.ok) {
7772
7771
  this.byRuntime.set(runtime, { quotaState: "ok", limitedUntil: null, lastError: null });
7773
- this.sources.set(runtime, "turn");
7774
7772
  return;
7775
7773
  }
7776
7774
  const failure = decodeFailureReason(outcome.errorReason);
@@ -7782,26 +7780,6 @@ var ConnectorHealthStore = class {
7782
7780
  limitedUntil: quota.limitedUntil,
7783
7781
  lastError: outcome.errorReason ?? null
7784
7782
  });
7785
- this.sources.set(runtime, "turn");
7786
- }
7787
- // Record what the companion's own sign-in check found for a connected
7788
- // runtime at `start` or `connect`, so the heartbeat carries "sign-in needed"
7789
- // before any turn fails. Signed out seeds `auth_expired` unless a turn has
7790
- // already spoken; usable clears only a seed of our own.
7791
- recordLocalCheck(runtime, usable) {
7792
- const source = this.sources.get(runtime);
7793
- if (!usable) {
7794
- if (source === "turn" && this.byRuntime.get(runtime)?.quotaState !== "ok") return;
7795
- this.byRuntime.set(runtime, {
7796
- quotaState: "auth_expired",
7797
- limitedUntil: null,
7798
- lastError: encodeFailureReason({ kind: "auth_expired" })
7799
- });
7800
- this.sources.set(runtime, "local");
7801
- return;
7802
- }
7803
- if (source !== "local") return;
7804
- this.byRuntime.set(runtime, { quotaState: "ok", limitedUntil: null, lastError: null });
7805
7783
  }
7806
7784
  // The store's current opinion for one runtime, or undefined when it has
7807
7785
  // no observation. Read by the dispatcher at idle-timeout fire: an idle reap
@@ -12767,6 +12745,41 @@ function runtimeIssuesFor(gates) {
12767
12745
  return issues;
12768
12746
  }
12769
12747
 
12748
+ // src/harness-state.ts
12749
+ function harnessEntry(runtime, state) {
12750
+ switch (state.state) {
12751
+ case "usable":
12752
+ return {
12753
+ runtime,
12754
+ state: "connected",
12755
+ reason: state.via,
12756
+ version: state.version,
12757
+ ...state.url ? { url: state.url } : {}
12758
+ };
12759
+ case "signed_out":
12760
+ return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
12761
+ case "not_running":
12762
+ return { runtime, state: "problem", reason: "not_running", url: state.url };
12763
+ case "off":
12764
+ return { runtime, state: "off" };
12765
+ case "problem":
12766
+ return { runtime, state: "problem", reason: checkReason(state.check) };
12767
+ }
12768
+ }
12769
+ function harnessEntriesFromDetection(detection) {
12770
+ return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
12771
+ }
12772
+ function checkReason(check) {
12773
+ switch (check.status) {
12774
+ case "bundle_missing":
12775
+ return check.bundled;
12776
+ case "check_failed":
12777
+ return `check_failed:${check.reason}`;
12778
+ default:
12779
+ return check.status;
12780
+ }
12781
+ }
12782
+
12770
12783
  // src/harness-versions.ts
12771
12784
  var EMPTY = { claudeCode: null, opencode: null, codex: null };
12772
12785
  function parseVersionToken(raw) {
@@ -14301,6 +14314,9 @@ var CompanionSupervisor = class {
14301
14314
  // Omitted until the first observed failure or heal, so the server's
14302
14315
  // status-less manifest synthesis still runs for a fresh companion.
14303
14316
  ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
14317
+ // The complete harness inventory in the machine's words, once detection
14318
+ // has run; omitted before it, which the server reads as "nothing yet".
14319
+ ...this.detection ? { harnesses: harnessEntriesFromDetection(this.detection) } : {},
14304
14320
  ...opencodeModels !== null ? { models: opencodeModels } : {},
14305
14321
  ...this.updater ? { update: this.updater.report() } : {}
14306
14322
  });
@@ -15253,7 +15269,6 @@ var CompanionSupervisor = class {
15253
15269
  ...this.probeOpencode ? { probeOpencode: this.probeOpencode } : {}
15254
15270
  });
15255
15271
  this.detection = detection;
15256
- this.seedSignInHealth(previous, detection);
15257
15272
  this.logBundledTransitions();
15258
15273
  this.harnessVersions = {
15259
15274
  claudeCode: detectedVersion(detection["claude-code"]),
@@ -15311,23 +15326,6 @@ var CompanionSupervisor = class {
15311
15326
  const bare = advertisedRuntimes(this.detection).filter((runtime) => !named.has(runtime)).map((runtime) => ({ runtime }));
15312
15327
  return [...health, ...bare];
15313
15328
  }
15314
- // Seed connector health from what detection saw of a sign-in, so Cabane says
15315
- // "sign-in needed" before a turn fails. Only a LAPSE seeds it — an agent that
15316
- // was usable at an earlier look and is signed out now — because a device
15317
- // that never signed in to Codex has nothing to report about Codex. Usable
15318
- // again clears a seed of our own.
15319
- seedSignInHealth(previous, detection) {
15320
- for (const runtime of ["claude-code", "codex"]) {
15321
- const now = detection[runtime];
15322
- if (now.state === "usable") {
15323
- if (now.via !== "container") this.connectorHealth.recordLocalCheck(runtime, true);
15324
- continue;
15325
- }
15326
- if (now.state === "signed_out" && previous?.[runtime].state === "usable") {
15327
- this.connectorHealth.recordLocalCheck(runtime, false);
15328
- }
15329
- }
15330
- }
15331
15329
  // Beat NOW and wait for it to land. `start` calls this before asking the
15332
15330
  // person anything, so the browser's connect step already shows what this
15333
15331
  // machine has rather than sitting blank while the terminal waits on an answer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.174",
3
+ "version": "0.6.175",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect an 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",