@cabane/companion 0.6.182 → 0.6.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,16 +44,26 @@ The script brings its own Node (checksum-checked, into `~/.cabane`) and needs no
44
44
  npm i -g @cabane/companion && cabane setup CODE
45
45
  ```
46
46
 
47
- Either way `cabane setup CODE` pairs the device to your account, joins it to that workspace, detects your harnesses, and starts the Companion: as a service on macOS and Linux, in the background elsewhere. No confirm, no name screen, no browser on the device. The same line on a device that is already paired joins it to the workspace and mints nothing.
47
+ Either way `cabane setup CODE` pairs the device to your account, joins it to that workspace, starts the Companion in the background, waits for its first check-in, and ends with the device's state in Cabane's words. No confirm, no name screen, no browser on the device. The same line on a device that is already paired joins it to the workspace and mints nothing.
48
48
 
49
49
  ```
50
- ✓ Claude Code signed in (2.1.289) — connected
51
- Codex not signed in — run codex login
52
- OpenCode not running
53
- All set here. Back to the browser.
50
+ ✓ Paired as "maya-mbp" with Acme.
51
+ Cabane is running in the background. This device is ready — checked in 2s ago.
52
+
53
+ Harnesses on this device
54
+ ✓ Claude Code connected — signed in (2.1.289)
55
+ Codex needs sign-in — run codex login
56
+ OpenCode off — not running
57
+
58
+ Status cabane status Stop cabane stop Log cabane logs
59
+ Start at login and keep Cabane updating itself: cabane service install
60
+
61
+ In Cabane: https://app.cabane.ai/acme/settings/devices
54
62
  ```
55
63
 
56
- With nothing signed in, the device still pairs and the Companion still runs; the rows say which harness needs sign-in, and the device shows **Needs attention** in Cabane until one is usable. Sign in, and the next check finds it: no restart.
64
+ The device is online the moment the Companion checks in, before any harness is checked; Cabane shows it as **Checking harnesses…** until the first detection lands, then **Ready** or **Needs attention**. With nothing signed in, the device still pairs and the Companion still runs; the rows say which harness needs sign-in. Sign in, and the next check finds it: no restart. A harness that was signed in earlier in this run stays offered through a lapsed login, so a turn reaches the device, fails with the sign-in notice, and the first reply after you sign in again runs with no wait.
65
+
66
+ Setup never installs the service. Run `cabane service install` when you want the Companion to start at login and keep itself updated; the closing block names it.
57
67
 
58
68
  A fresh device runs nothing yet. In a workspace, open an agent's page and assign it to the device; the Companion picks the assignment up on its next poll.
59
69
 
@@ -61,23 +71,23 @@ If `cabane` isn't found afterwards, open a new terminal, or run the `source` lin
61
71
 
62
72
  ## Commands
63
73
 
64
- | Command | What it does |
65
- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
- | `setup [CODE]` | With a code: pair or join, detect harnesses, start. Without: a re-runnable checklist that reports harnesses and starts what isn't running. |
67
- | `start [--foreground]` | Run in the background. Never asks. Unpaired, it names `setup`. `--foreground` stays attached, for a supervisor of your own. |
68
- | `stop` | Stop a running Companion cleanly. Ends any turn in progress. |
69
- | `status` | Version and install, the paired device by its Cabane name, whether it's running, each harness's state, the sandbox posture, declared secret names, and local overrides. |
70
- | `update [--auto on\|off]` | Install the version Cabane names now; with `--auto`, set whether the Companion keeps itself up to date. |
71
- | `service install\|uninstall\|status` | Run as a systemd user service or a LaunchAgent that starts at boot and restarts if it stops; remove it; show its state. |
72
- | `logs [-n N] [-f]` | Recent log lines, or follow. |
73
- | `transcript [file] [--last] [-f]` | Read back a full agent turn, tool calls and results included. |
74
- | `logout [--purge]` | Remove the device token and cached agent credentials. Doesn't remove the device from Cabane. |
74
+ | Command | What it does |
75
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
76
+ | `setup [CODE]` | With a code: pair or join, detect harnesses, start. Without: a re-runnable checklist that reports harnesses and starts what isn't running. |
77
+ | `start [--foreground]` | Run in the background. Never asks. Unpaired, it names `setup`. `--foreground` stays attached, for a supervisor of your own. |
78
+ | `stop` | Stop a running Companion cleanly. Ends any turn in progress. |
79
+ | `status` | Version and install, the paired device by its Cabane name, its state in Cabane's words and when it last checked in, each harness's state, the sandbox posture, declared secret names, and local overrides. |
80
+ | `update [--auto on\|off]` | Install the version Cabane names now; with `--auto`, set whether the Companion keeps itself up to date. |
81
+ | `service install\|uninstall\|status` | Run as a systemd user service or a LaunchAgent that starts at boot and restarts if it stops; remove it; show its state. |
82
+ | `logs [-n N] [-f]` | Recent log lines, or follow. |
83
+ | `transcript [file] [--last] [-f]` | Read back a full agent turn, tool calls and results included. |
84
+ | `logout [--purge]` | Remove the device token and cached agent credentials. Doesn't remove the device from Cabane. |
75
85
 
76
86
  Anything else about a device is done in Cabane, on the device's page.
77
87
 
78
88
  ## Keeping it running
79
89
 
80
- Under the service the Companion restarts if it stops, comes back after a reboot, and follows the version Cabane names: it installs the new version under `~/.cabane/versions`, checks it starts, and swaps between turns. A version that can't start is rolled back by the boot guard. `cabane update --auto off` stops updates; without the service, `cabane update` installs and the next `cabane start` runs it.
90
+ Without the service, a reboot or a crash leaves the Companion stopped until the next `cabane start`, and the device shows **Offline** in Cabane. Under the service it restarts if it stops, comes back after a reboot, and follows the version Cabane names: it installs the new version under `~/.cabane/versions`, checks it starts, and swaps between turns. A version that can't start is rolled back by the boot guard. `cabane update --auto off` stops updates; without the service, `cabane update` installs and the next `cabane start` runs it.
81
91
 
82
92
  Other supervisors: run `cabane start --foreground` with `CABANE_COMPANION_SUPERVISED=1` set, using the absolute paths `~/.cabane/node/bin/node` and `~/.cabane/current/dist/bin.js`, which don't move on an update.
83
93
 
package/dist/cli.js CHANGED
@@ -1128,7 +1128,7 @@ function classifyRuntimeNoticeText(text) {
1128
1128
  }
1129
1129
  return null;
1130
1130
  }
1131
- var turnFailureSchema, USAGE_CAPPED, RATE_LIMITED, SERVER_ERROR, AUTH_EXPIRED, AUTH_PATTERNS, SERVER_PATTERNS, CAP_PATTERNS, NEGATED_CAP, RATE_PATTERNS, BARE_LIMIT;
1131
+ var turnFailureSchema, ERROR_REASON_MAX, USAGE_CAPPED, RATE_LIMITED, SERVER_ERROR, AUTH_EXPIRED, AUTH_PATTERNS, SERVER_PATTERNS, CAP_PATTERNS, NEGATED_CAP, RATE_PATTERNS, BARE_LIMIT;
1132
1132
  var init_failure = __esm({
1133
1133
  "packages/agent-runtime/src/failure.ts"() {
1134
1134
  "use strict";
@@ -1138,6 +1138,7 @@ var init_failure = __esm({
1138
1138
  z4.object({ kind: z4.literal("server_error") }),
1139
1139
  z4.object({ kind: z4.literal("auth_expired") })
1140
1140
  ]);
1141
+ ERROR_REASON_MAX = 2e3;
1141
1142
  USAGE_CAPPED = "usage_capped";
1142
1143
  RATE_LIMITED = "rate_limited";
1143
1144
  SERVER_ERROR = "server_error";
@@ -2966,7 +2967,7 @@ function createFailureEvidence() {
2966
2967
  const failure = classifyResultFailure(msg, signals);
2967
2968
  const resultText = msg.result ?? "";
2968
2969
  const isError = msg.is_error === true;
2969
- return failure ? encodeFailureReason(failure) : isError ? `error:${resultText.slice(0, 200) || "unknown"}` : `result_error:${msg.subtype}`;
2970
+ return failure ? encodeFailureReason(failure) : isError ? `error:${resultText || "unknown"}`.slice(0, ERROR_REASON_MAX) : `result_error:${msg.subtype}`;
2970
2971
  },
2971
2972
  thrownFailure(message2, initReceived) {
2972
2973
  return initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
@@ -4260,7 +4261,7 @@ async function* decodeOpencodeStream(events, ctx2) {
4260
4261
  yield { type: "runtime_notice", body: notice };
4261
4262
  }
4262
4263
  const failure = classifyErrorText(errorText);
4263
- reason = failure ? encodeFailureReason(failure) : `error:${errorText.slice(0, 200)}`;
4264
+ reason = failure ? encodeFailureReason(failure) : `error:${errorText}`.slice(0, ERROR_REASON_MAX);
4264
4265
  settled = true;
4265
4266
  break;
4266
4267
  }
@@ -5500,7 +5501,7 @@ async function* decodeCodexStream(events, ctx2) {
5500
5501
  if (isModelMetadataError(message2)) {
5501
5502
  yield* flushInterim();
5502
5503
  ok = false;
5503
- reason = `model_unavailable:${message2.slice(0, 200)}`;
5504
+ reason = `model_unavailable:${message2}`.slice(0, ERROR_REASON_MAX);
5504
5505
  settled = true;
5505
5506
  break;
5506
5507
  }
@@ -5568,7 +5569,7 @@ async function* decodeCodexStream(events, ctx2) {
5568
5569
  yield { type: "runtime_notice", body: notice };
5569
5570
  }
5570
5571
  const failure = classifyErrorText(text);
5571
- reason = failure ? encodeFailureReason(failure) : `error:${text.slice(0, 200)}`;
5572
+ reason = failure ? encodeFailureReason(failure) : `error:${text}`.slice(0, ERROR_REASON_MAX);
5572
5573
  settled = true;
5573
5574
  break;
5574
5575
  }
@@ -7760,16 +7761,16 @@ function missingSecretReason(missing) {
7760
7761
  }
7761
7762
  return `${prefix}+${missing.length}`;
7762
7763
  }
7763
- var PLACEHOLDER_RE, secretStoreSchema, SECRET_ENV_NAME, ERROR_REASON_MAX;
7764
+ var PLACEHOLDER_RE, secretStoreSchema, SECRET_ENV_NAME;
7764
7765
  var init_secrets = __esm({
7765
7766
  "src/secrets.ts"() {
7766
7767
  "use strict";
7768
+ init_src();
7767
7769
  init_config();
7768
7770
  init_errors();
7769
7771
  PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
7770
7772
  secretStoreSchema = z15.record(z15.string(), z15.string());
7771
7773
  SECRET_ENV_NAME = /^CABANE_MCP_(\d+)$/;
7772
- ERROR_REASON_MAX = 200;
7773
7774
  }
7774
7775
  });
7775
7776
 
@@ -8133,7 +8134,7 @@ async function settleUnstarted(opts, payload, turnId, errorReason, log2) {
8133
8134
  activeRunStartedAt: null,
8134
8135
  turnId,
8135
8136
  outcome: "failed",
8136
- ...errorReason ? { errorReason: errorReason.slice(0, 200) } : {},
8137
+ ...errorReason ? { errorReason: errorReason.slice(0, ERROR_REASON_MAX) } : {},
8137
8138
  readThrough: payload.messageId
8138
8139
  });
8139
8140
  } catch (err) {
@@ -8155,7 +8156,7 @@ function sandboxInputs(launch, log2) {
8155
8156
  }
8156
8157
  if (!policy.ok) {
8157
8158
  log2.warn({ error: policy.error }, "runners: the sandbox policy is invalid; refusing the turn");
8158
- return { ok: false, reason: `setup_failed: ${policy.error}`.slice(0, 200) };
8159
+ return { ok: false, reason: `setup_failed: ${policy.error}`.slice(0, ERROR_REASON_MAX) };
8159
8160
  }
8160
8161
  if (!policyHasCredentials(policy.policy)) return { ok: true, credentials: [] };
8161
8162
  const store = (launch.secretStore ?? defaultSecretStore)(log2);
@@ -8393,7 +8394,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8393
8394
  activeRunStartedAt: null,
8394
8395
  turnId,
8395
8396
  outcome: "failed",
8396
- errorReason: reason.slice(0, 200),
8397
+ errorReason: reason.slice(0, ERROR_REASON_MAX),
8397
8398
  readThrough: payload.messageId
8398
8399
  });
8399
8400
  } catch (err) {
@@ -8419,6 +8420,7 @@ var PINO_LEVELS;
8419
8420
  var init_launch = __esm({
8420
8421
  "src/runner/launch.ts"() {
8421
8422
  "use strict";
8423
+ init_src();
8422
8424
  init_sandbox_policy();
8423
8425
  init_opencode_lock();
8424
8426
  init_api_error_shape();
@@ -9556,7 +9558,7 @@ var init_turn_execution = __esm({
9556
9558
  async concludeBeforeRun(reason, errorReason) {
9557
9559
  const { payload, turnLog, startedAt, turnId } = this;
9558
9560
  const body = { activeRunStartedAt: null, turnId, outcome: "failed" };
9559
- if (errorReason) body.errorReason = errorReason.slice(0, 200);
9561
+ if (errorReason) body.errorReason = errorReason.slice(0, ERROR_REASON_MAX);
9560
9562
  const context = this.turnContext;
9561
9563
  if (context?.readThrough !== void 0) {
9562
9564
  body.readThrough = context.readThrough;
@@ -9595,7 +9597,7 @@ var init_turn_execution = __esm({
9595
9597
  // `errorReason`, so the inference alone would read it as settled.
9596
9598
  outcome: errorReason || this.outcome.runtimeIncomplete ? "failed" : "settled"
9597
9599
  };
9598
- if (errorReason) body.errorReason = errorReason.slice(0, 200);
9600
+ if (errorReason) body.errorReason = errorReason.slice(0, ERROR_REASON_MAX);
9599
9601
  try {
9600
9602
  await this.opts.api.setActiveRun(
9601
9603
  this.opts.workspaceId,
@@ -10007,7 +10009,7 @@ var init_turn_execution = __esm({
10007
10009
  }) : null;
10008
10010
  } catch (err) {
10009
10011
  if (!(err instanceof HarnessSandboxRefused)) throw err;
10010
- const reason = `setup_failed: ${err.message}`.slice(0, 200);
10012
+ const reason = `setup_failed: ${err.message}`.slice(0, ERROR_REASON_MAX);
10011
10013
  throw this.concluded(reason, reason);
10012
10014
  }
10013
10015
  const adapters = [];
@@ -10407,7 +10409,7 @@ var init_turn_execution = __esm({
10407
10409
  body.resolvedConfig = o.turnResolvedConfig;
10408
10410
  }
10409
10411
  if (!o.okResult && o.resultReason && o.resultReason !== "cancelled" && !userCancelled && !o.leaseLost && !o.runtimeIncomplete && !this.skipState.skipped) {
10410
- body.errorReason = o.resultReason.slice(0, 200);
10412
+ body.errorReason = o.resultReason.slice(0, ERROR_REASON_MAX);
10411
10413
  }
10412
10414
  body.outcome = o.okResult ? committer.spoke ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
10413
10415
  if (body.outcome === "settled") body.lastMessageSeq = committer.lastMessageSeq;
package/dist/runtime.js CHANGED
@@ -1758,6 +1758,7 @@ var turnFailureSchema = z4.discriminatedUnion("kind", [
1758
1758
  z4.object({ kind: z4.literal("server_error") }),
1759
1759
  z4.object({ kind: z4.literal("auth_expired") })
1760
1760
  ]);
1761
+ var ERROR_REASON_MAX = 2e3;
1761
1762
  var USAGE_CAPPED = "usage_capped";
1762
1763
  var RATE_LIMITED = "rate_limited";
1763
1764
  var SERVER_ERROR = "server_error";
@@ -3902,7 +3903,7 @@ function createFailureEvidence() {
3902
3903
  const failure = classifyResultFailure(msg, signals);
3903
3904
  const resultText = msg.result ?? "";
3904
3905
  const isError = msg.is_error === true;
3905
- return failure ? encodeFailureReason(failure) : isError ? `error:${resultText.slice(0, 200) || "unknown"}` : `result_error:${msg.subtype}`;
3906
+ return failure ? encodeFailureReason(failure) : isError ? `error:${resultText || "unknown"}`.slice(0, ERROR_REASON_MAX) : `result_error:${msg.subtype}`;
3906
3907
  },
3907
3908
  thrownFailure(message2, initReceived) {
3908
3909
  return initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
@@ -5120,7 +5121,7 @@ async function* decodeOpencodeStream(events, ctx) {
5120
5121
  yield { type: "runtime_notice", body: notice };
5121
5122
  }
5122
5123
  const failure = classifyErrorText(errorText);
5123
- reason = failure ? encodeFailureReason(failure) : `error:${errorText.slice(0, 200)}`;
5124
+ reason = failure ? encodeFailureReason(failure) : `error:${errorText}`.slice(0, ERROR_REASON_MAX);
5124
5125
  settled = true;
5125
5126
  break;
5126
5127
  }
@@ -6276,7 +6277,7 @@ async function* decodeCodexStream(events, ctx) {
6276
6277
  if (isModelMetadataError(message2)) {
6277
6278
  yield* flushInterim();
6278
6279
  ok = false;
6279
- reason = `model_unavailable:${message2.slice(0, 200)}`;
6280
+ reason = `model_unavailable:${message2}`.slice(0, ERROR_REASON_MAX);
6280
6281
  settled = true;
6281
6282
  break;
6282
6283
  }
@@ -6344,7 +6345,7 @@ async function* decodeCodexStream(events, ctx) {
6344
6345
  yield { type: "runtime_notice", body: notice };
6345
6346
  }
6346
6347
  const failure = classifyErrorText(text);
6347
- reason = failure ? encodeFailureReason(failure) : `error:${text.slice(0, 200)}`;
6348
+ reason = failure ? encodeFailureReason(failure) : `error:${text}`.slice(0, ERROR_REASON_MAX);
6348
6349
  settled = true;
6349
6350
  break;
6350
6351
  }
@@ -8411,7 +8412,6 @@ function resolveMcpSecrets(mcpServers, store, serverSecretEnv = {}) {
8411
8412
  }
8412
8413
  return { servers, secretEnv, missing: [...missing].sort() };
8413
8414
  }
8414
- var ERROR_REASON_MAX = 200;
8415
8415
  function missingSecretReason(missing) {
8416
8416
  const prefix = "missing_secret: ";
8417
8417
  for (let shown = missing.length; shown >= 0; shown--) {
@@ -8746,7 +8746,7 @@ async function settleUnstarted(opts, payload, turnId, errorReason, log) {
8746
8746
  activeRunStartedAt: null,
8747
8747
  turnId,
8748
8748
  outcome: "failed",
8749
- ...errorReason ? { errorReason: errorReason.slice(0, 200) } : {},
8749
+ ...errorReason ? { errorReason: errorReason.slice(0, ERROR_REASON_MAX) } : {},
8750
8750
  readThrough: payload.messageId
8751
8751
  });
8752
8752
  } catch (err) {
@@ -8768,7 +8768,7 @@ function sandboxInputs(launch, log) {
8768
8768
  }
8769
8769
  if (!policy.ok) {
8770
8770
  log.warn({ error: policy.error }, "runners: the sandbox policy is invalid; refusing the turn");
8771
- return { ok: false, reason: `setup_failed: ${policy.error}`.slice(0, 200) };
8771
+ return { ok: false, reason: `setup_failed: ${policy.error}`.slice(0, ERROR_REASON_MAX) };
8772
8772
  }
8773
8773
  if (!policyHasCredentials(policy.policy)) return { ok: true, credentials: [] };
8774
8774
  const store = (launch.secretStore ?? defaultSecretStore)(log);
@@ -9006,7 +9006,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
9006
9006
  activeRunStartedAt: null,
9007
9007
  turnId,
9008
9008
  outcome: "failed",
9009
- errorReason: reason.slice(0, 200),
9009
+ errorReason: reason.slice(0, ERROR_REASON_MAX),
9010
9010
  readThrough: payload.messageId
9011
9011
  });
9012
9012
  } catch (err) {
@@ -10035,7 +10035,7 @@ var TurnExecution = class {
10035
10035
  async concludeBeforeRun(reason, errorReason) {
10036
10036
  const { payload, turnLog, startedAt, turnId } = this;
10037
10037
  const body = { activeRunStartedAt: null, turnId, outcome: "failed" };
10038
- if (errorReason) body.errorReason = errorReason.slice(0, 200);
10038
+ if (errorReason) body.errorReason = errorReason.slice(0, ERROR_REASON_MAX);
10039
10039
  const context = this.turnContext;
10040
10040
  if (context?.readThrough !== void 0) {
10041
10041
  body.readThrough = context.readThrough;
@@ -10074,7 +10074,7 @@ var TurnExecution = class {
10074
10074
  // `errorReason`, so the inference alone would read it as settled.
10075
10075
  outcome: errorReason || this.outcome.runtimeIncomplete ? "failed" : "settled"
10076
10076
  };
10077
- if (errorReason) body.errorReason = errorReason.slice(0, 200);
10077
+ if (errorReason) body.errorReason = errorReason.slice(0, ERROR_REASON_MAX);
10078
10078
  try {
10079
10079
  await this.opts.api.setActiveRun(
10080
10080
  this.opts.workspaceId,
@@ -10486,7 +10486,7 @@ var TurnExecution = class {
10486
10486
  }) : null;
10487
10487
  } catch (err) {
10488
10488
  if (!(err instanceof HarnessSandboxRefused)) throw err;
10489
- const reason = `setup_failed: ${err.message}`.slice(0, 200);
10489
+ const reason = `setup_failed: ${err.message}`.slice(0, ERROR_REASON_MAX);
10490
10490
  throw this.concluded(reason, reason);
10491
10491
  }
10492
10492
  const adapters = [];
@@ -10886,7 +10886,7 @@ var TurnExecution = class {
10886
10886
  body.resolvedConfig = o.turnResolvedConfig;
10887
10887
  }
10888
10888
  if (!o.okResult && o.resultReason && o.resultReason !== "cancelled" && !userCancelled && !o.leaseLost && !o.runtimeIncomplete && !this.skipState.skipped) {
10889
- body.errorReason = o.resultReason.slice(0, 200);
10889
+ body.errorReason = o.resultReason.slice(0, ERROR_REASON_MAX);
10890
10890
  }
10891
10891
  body.outcome = o.okResult ? committer.spoke ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
10892
10892
  if (body.outcome === "settled") body.lastMessageSeq = committer.lastMessageSeq;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.182",
3
+ "version": "0.6.184",
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",