@cabane/companion 0.6.163 → 0.6.165

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
@@ -12437,7 +12437,9 @@ function clearRuntimeStateIfOurs(instanceId) {
12437
12437
  }
12438
12438
  rmSync3(path, { force: true });
12439
12439
  }
12440
- function readLiveRuntimeState() {
12440
+ function readLiveRuntimeState({
12441
+ sweep = true
12442
+ } = {}) {
12441
12443
  const path = runtimePath();
12442
12444
  if (!existsSync5(path)) return null;
12443
12445
  let parsed;
@@ -12450,7 +12452,7 @@ function readLiveRuntimeState() {
12450
12452
  try {
12451
12453
  process.kill(parsed.pid, 0);
12452
12454
  } catch {
12453
- clearRuntimeState();
12455
+ if (sweep) clearRuntimeState();
12454
12456
  return null;
12455
12457
  }
12456
12458
  return parsed;
@@ -15401,6 +15403,12 @@ var DeviceApi = class {
15401
15403
  getAssignments() {
15402
15404
  return this.request("GET", "/api/companion/assignments");
15403
15405
  }
15406
+ // Ask again for the credential of an agent assigned to this device, when the saved copy is
15407
+ // gone. The previous credential stops working; any agent not assigned here is a 404, and a
15408
+ // second ask inside a minute a 429.
15409
+ reissueAgentCredential(agentId) {
15410
+ return this.request("POST", `/api/companion/agents/${encodeURIComponent(agentId)}/credential`);
15411
+ }
15404
15412
  getRuns(cursor) {
15405
15413
  const query = new URLSearchParams({ limit: "100", order: "asc" });
15406
15414
  if (cursor) query.set("cursor", cursor);
@@ -17606,6 +17614,7 @@ var CompanionSupervisor = class {
17606
17614
  for (const it of items) {
17607
17615
  if (it.credential) setCredential(device.id, it.agentId, it.credential);
17608
17616
  }
17617
+ items = await this.restoreMissingCredentials(device.id, items);
17609
17618
  const byWorkspace = /* @__PURE__ */ new Map();
17610
17619
  for (const it of items) {
17611
17620
  const list = byWorkspace.get(it.workspaceId) ?? [];
@@ -17638,6 +17647,42 @@ var CompanionSupervisor = class {
17638
17647
  this.inFlightRefresh = null;
17639
17648
  }
17640
17649
  }
17650
+ // An assigned agent whose saved credential is gone (the file wiped or edited, a crash between
17651
+ // delivery and write) gets it back here, before reconciling. A runner still holding it in
17652
+ // memory writes it back; otherwise Cabane is asked for a new one, which revokes the old. The
17653
+ // result rides the item as a fresh delivery, so reconcile builds the runner on it. A refused
17654
+ // ask leaves the item as it was, and reconcile reports the agent can't run.
17655
+ async restoreMissingCredentials(deviceId, items) {
17656
+ const restored = [];
17657
+ for (const it of items) {
17658
+ if (it.credential || getCredential(deviceId, it.agentId)) {
17659
+ restored.push(it);
17660
+ continue;
17661
+ }
17662
+ const held = this.workspaces.get(it.workspaceId)?.agents.get(it.agentId)?.credential;
17663
+ if (held) {
17664
+ setCredential(deviceId, it.agentId, held);
17665
+ restored.push(it);
17666
+ continue;
17667
+ }
17668
+ try {
17669
+ const { credential } = await this.deviceApi.reissueAgentCredential(it.agentId);
17670
+ setCredential(deviceId, it.agentId, credential);
17671
+ this.log.info(
17672
+ { agentId: it.agentId, agentName: it.agentDisplayName },
17673
+ `Restored ${it.agentDisplayName}'s sign-in to Cabane: it was missing on this device.`
17674
+ );
17675
+ restored.push({ ...it, credential });
17676
+ } catch (err) {
17677
+ this.log.warn(
17678
+ { agentId: it.agentId, agentName: it.agentDisplayName, ...apiErrorLogFields(err) },
17679
+ `Couldn't restore ${it.agentDisplayName}'s sign-in to Cabane.`
17680
+ );
17681
+ restored.push(it);
17682
+ }
17683
+ }
17684
+ return restored;
17685
+ }
17641
17686
  // ---- reconciliation ----
17642
17687
  reconcileWorkspace(deviceId, workspaceId, list) {
17643
17688
  const first = list[0];
@@ -19060,11 +19105,13 @@ var LABEL_WIDTH2 = 13;
19060
19105
  function row2(label, value) {
19061
19106
  return ` ${label.padEnd(LABEL_WIDTH2)}${value}`;
19062
19107
  }
19063
- async function status() {
19108
+ async function status(deps = {}) {
19064
19109
  const out = (line) => void process.stdout.write(`${line}
19065
19110
  `);
19111
+ const running = await liveState();
19112
+ const strays = strayStatusLines(running, deps.strays);
19066
19113
  out(` ${versionLine()}`);
19067
- for (const line of otherInstallLines(await liveState())) out(line);
19114
+ for (const line of otherInstallLines(running)) out(line);
19068
19115
  let cfg = null;
19069
19116
  try {
19070
19117
  cfg = loadConfig();
@@ -19073,11 +19120,11 @@ async function status() {
19073
19120
  }
19074
19121
  if (!cfg || !cfg.deviceToken) {
19075
19122
  out(` Not paired yet. Run ${cabane("setup")} to set up this device.`);
19123
+ for (const line of strays) out(line);
19076
19124
  process.exitCode = 1;
19077
19125
  return;
19078
19126
  }
19079
19127
  out(row2("Paired with", `${cfg.baseUrl}${cfg.deviceLabel ? ` as ${cfg.deviceLabel}` : ""}`));
19080
- const running = await liveState();
19081
19128
  let detection = null;
19082
19129
  if (running) {
19083
19130
  const mode = running.daemon ? "in the background" : "in a terminal";
@@ -19087,6 +19134,7 @@ async function status() {
19087
19134
  } else {
19088
19135
  out(row2("Running", `no \u2014 start it with ${cabane("start")}`));
19089
19136
  }
19137
+ for (const line of strays) out(line);
19090
19138
  const service2 = await serviceState();
19091
19139
  for (const line of serviceRows(service2)) out(line);
19092
19140
  for (const line of updateRows(readUpdateState(), cfg.update?.auto, {
@@ -19158,11 +19206,8 @@ function otherInstall(install, own) {
19158
19206
  return real(found) === ownAlias ? null : dirname15(found);
19159
19207
  }
19160
19208
  async function liveState() {
19161
- const running = readLiveRuntimeState();
19162
- if (running && await verifyRuntime(running) === "stale") {
19163
- clearRuntimeState();
19164
- return null;
19165
- }
19209
+ const running = readLiveRuntimeState({ sweep: false });
19210
+ if (running && await verifyRuntime(running) === "stale") return null;
19166
19211
  return running;
19167
19212
  }
19168
19213
  async function runningDetection(running) {
@@ -19178,6 +19223,11 @@ async function runningDetection(running) {
19178
19223
  return null;
19179
19224
  }
19180
19225
  }
19226
+ function strayStatusLines(running, find = findStrayCompanions) {
19227
+ const strays = find(running ? [running.pid] : []);
19228
+ if (strays.length === 0) return [];
19229
+ return strayCompanionLines(strays, cabane("status")).map((line) => ` ${line}`);
19230
+ }
19181
19231
  async function agentLines2(cfg, running) {
19182
19232
  const detection = running ?? await detectHarnesses(cfg);
19183
19233
  return agentRows(detection, { connected: false }).map((agent) => row2(agent.label, agent.text));
package/dist/runtime.js CHANGED
@@ -1678,7 +1678,9 @@ function clearRuntimeStateIfOurs(instanceId) {
1678
1678
  }
1679
1679
  rmSync3(path, { force: true });
1680
1680
  }
1681
- function readLiveRuntimeState() {
1681
+ function readLiveRuntimeState({
1682
+ sweep = true
1683
+ } = {}) {
1682
1684
  const path = runtimePath();
1683
1685
  if (!existsSync6(path)) return null;
1684
1686
  let parsed;
@@ -1691,7 +1693,7 @@ function readLiveRuntimeState() {
1691
1693
  try {
1692
1694
  process.kill(parsed.pid, 0);
1693
1695
  } catch {
1694
- clearRuntimeState();
1696
+ if (sweep) clearRuntimeState();
1695
1697
  return null;
1696
1698
  }
1697
1699
  return parsed;
@@ -2494,6 +2496,12 @@ var DeviceApi = class {
2494
2496
  getAssignments() {
2495
2497
  return this.request("GET", "/api/companion/assignments");
2496
2498
  }
2499
+ // Ask again for the credential of an agent assigned to this device, when the saved copy is
2500
+ // gone. The previous credential stops working; any agent not assigned here is a 404, and a
2501
+ // second ask inside a minute a 429.
2502
+ reissueAgentCredential(agentId) {
2503
+ return this.request("POST", `/api/companion/agents/${encodeURIComponent(agentId)}/credential`);
2504
+ }
2497
2505
  getRuns(cursor) {
2498
2506
  const query = new URLSearchParams({ limit: "100", order: "asc" });
2499
2507
  if (cursor) query.set("cursor", cursor);
@@ -14172,6 +14180,7 @@ var CompanionSupervisor = class {
14172
14180
  for (const it of items) {
14173
14181
  if (it.credential) setCredential(device.id, it.agentId, it.credential);
14174
14182
  }
14183
+ items = await this.restoreMissingCredentials(device.id, items);
14175
14184
  const byWorkspace = /* @__PURE__ */ new Map();
14176
14185
  for (const it of items) {
14177
14186
  const list = byWorkspace.get(it.workspaceId) ?? [];
@@ -14204,6 +14213,42 @@ var CompanionSupervisor = class {
14204
14213
  this.inFlightRefresh = null;
14205
14214
  }
14206
14215
  }
14216
+ // An assigned agent whose saved credential is gone (the file wiped or edited, a crash between
14217
+ // delivery and write) gets it back here, before reconciling. A runner still holding it in
14218
+ // memory writes it back; otherwise Cabane is asked for a new one, which revokes the old. The
14219
+ // result rides the item as a fresh delivery, so reconcile builds the runner on it. A refused
14220
+ // ask leaves the item as it was, and reconcile reports the agent can't run.
14221
+ async restoreMissingCredentials(deviceId, items) {
14222
+ const restored = [];
14223
+ for (const it of items) {
14224
+ if (it.credential || getCredential(deviceId, it.agentId)) {
14225
+ restored.push(it);
14226
+ continue;
14227
+ }
14228
+ const held = this.workspaces.get(it.workspaceId)?.agents.get(it.agentId)?.credential;
14229
+ if (held) {
14230
+ setCredential(deviceId, it.agentId, held);
14231
+ restored.push(it);
14232
+ continue;
14233
+ }
14234
+ try {
14235
+ const { credential } = await this.deviceApi.reissueAgentCredential(it.agentId);
14236
+ setCredential(deviceId, it.agentId, credential);
14237
+ this.log.info(
14238
+ { agentId: it.agentId, agentName: it.agentDisplayName },
14239
+ `Restored ${it.agentDisplayName}'s sign-in to Cabane: it was missing on this device.`
14240
+ );
14241
+ restored.push({ ...it, credential });
14242
+ } catch (err) {
14243
+ this.log.warn(
14244
+ { agentId: it.agentId, agentName: it.agentDisplayName, ...apiErrorLogFields(err) },
14245
+ `Couldn't restore ${it.agentDisplayName}'s sign-in to Cabane.`
14246
+ );
14247
+ restored.push(it);
14248
+ }
14249
+ }
14250
+ return restored;
14251
+ }
14207
14252
  // ---- reconciliation ----
14208
14253
  reconcileWorkspace(deviceId, workspaceId, list) {
14209
14254
  const first = list[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.163",
3
+ "version": "0.6.165",
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",