@cabane/companion 0.6.196 → 0.6.198

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
@@ -4,7 +4,7 @@ The Cabane Companion runs your Cabane agents on your own device. Install it, pas
4
4
 
5
5
  It drives a **harness** you already have signed in: [Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code), [Codex](https://developers.openai.com/codex/cli) or [OpenCode](https://opencode.ai). The Companion never installs a harness and never runs a sign-in; it finds the ones that are usable and reports them.
6
6
 
7
- Step-by-step guides with screenshots are in Cabane: open the **Account menu** and choose **Docs**. This README is the reference for the device side.
7
+ Step-by-step guides with screenshots are at [cabane.ai/docs](https://cabane.ai/docs): [Connect your computer](https://cabane.ai/docs/connect-your-computer) to install and pair, [Create an agent](https://cabane.ai/docs/create-an-agent) to give an agent a device, and [When an agent doesn't answer](https://cabane.ai/docs/when-an-agent-doesnt-answer) when one stays silent. This README is the reference for the device side.
8
8
 
9
9
  ## The model
10
10
 
@@ -131,7 +131,7 @@ Cabane has no sandbox setting of its own. A harness's own sandbox settings restr
131
131
 
132
132
  So where agents run is your choice, and there are two ways:
133
133
 
134
- - **The Companion in a container or VM**, recommended for almost everyone: the Companion and the harness together in Docker, OrbStack or a VM, sharing only the folders and credentials they need. Agents keep a full shell to work and code in, still with no approvals, while the container or VM limits what they reach to what you put in, MCP servers, hooks and the harness process included. If you haven't set one up before, ask Claude to help; **Common device patterns** in Cabane's Docs walks through one with Docker.
134
+ - **The Companion in a container or VM**, recommended for almost everyone: the Companion and the harness together in Docker, OrbStack or a VM, sharing only the folders and credentials they need. Agents keep a full shell to work and code in, still with no approvals, while the container or VM limits what they reach to what you put in, MCP servers, hooks and the harness process included. If you haven't set one up before, ask Claude to help; [Common device patterns](https://cabane.ai/docs/common-device-patterns) walks through one with Docker.
135
135
  - **Directly on your device**, with the harness's own sandbox set to taste. It restricts particular tools, and each harness draws a different line.
136
136
 
137
137
  Running directly, confining agents is set in each harness's own file, and governs your own sessions there as well as every agent on the device:
package/dist/cli.js CHANGED
@@ -1682,7 +1682,7 @@ var init_api = __esm({
1682
1682
  // Single-shot, NEVER retried: a failed renewal has no answer worth replaying
1683
1683
  // and the next tick asks again; retrying stretches one tick over the next.
1684
1684
  //
1685
- // ALL FAILURE IS `unknown`. Only the server SAYING the turn is over may stop a
1685
+ // Only the server SAYING the turn is over may stop a
1686
1686
  // running loop; a 500, a dropped connection, an expired token or an
1687
1687
  // unparseable body says nothing about the turn, and reading it as death would
1688
1688
  // kill healthy turns on every API hiccup. Fixed here, at the boundary, so no
@@ -1706,8 +1706,8 @@ var init_api = __esm({
1706
1706
  }
1707
1707
  );
1708
1708
  return res?.state === "running" || res?.state === "ended" ? res.state : "unknown";
1709
- } catch {
1710
- return "unknown";
1709
+ } catch (err) {
1710
+ return isTurnEndedRefusal(err) ? "ended" : "unknown";
1711
1711
  }
1712
1712
  }
1713
1713
  // Flips `active_run_started_at` and optionally captures the SDK session
@@ -10557,8 +10557,8 @@ var init_dispatcher = __esm({
10557
10557
  this.aborts = opts.aborts ?? /* @__PURE__ */ new Map();
10558
10558
  }
10559
10559
  opts;
10560
- // (conversation|agent) → per-TURN controllers. Stop is pair-scoped and must
10561
- // reach every live loop for the pair.
10560
+ // (conversation|agent) → per-turn controllers. A delayed cancel for an old
10561
+ // turn must never reach its successor.
10562
10562
  aborts;
10563
10563
  // Pairs already told their session couldn't be saved: the notice is once per
10564
10564
  // pair, while the rejection repeats every turn.
@@ -10609,19 +10609,22 @@ var init_dispatcher = __esm({
10609
10609
  { turnId: opts.turnId ?? randomUUID4(), resumed: opts.resumed === true }
10610
10610
  );
10611
10611
  }
10612
- // Cancel a (conversation, agent) run in flight in THIS process. True if one
10613
- // was aborted.
10614
- cancel(conversationId, agentId) {
10612
+ // Cancel the named turn. An older server omits turnId and cancels the pair.
10613
+ cancel(conversationId, agentId, turnId) {
10615
10614
  const key = runKey(conversationId, agentId);
10616
10615
  const pair = this.aborts.get(key);
10617
10616
  if (!pair || pair.size === 0) return false;
10618
- for (const ac of [...pair.values()]) {
10617
+ const controllers = turnId === void 0 ? [...pair.values()] : [pair.get(turnId)];
10618
+ let aborted = false;
10619
+ for (const ac of controllers) {
10620
+ if (!ac) continue;
10621
+ aborted = true;
10619
10622
  try {
10620
10623
  ac.abort();
10621
10624
  } catch {
10622
10625
  }
10623
10626
  }
10624
- return true;
10627
+ return aborted;
10625
10628
  }
10626
10629
  // Abort every run in flight in THIS process, whatever its conversation. For
10627
10630
  // an agent taken off this device: nothing it is still running here is
@@ -14918,7 +14921,11 @@ var CompanionSupervisor = class {
14918
14921
  };
14919
14922
  const agent2 = wr.agents.get(payload2.agentId);
14920
14923
  if (agent2) {
14921
- const aborted = agent2.dispatcher.cancel(payload2.conversationId, payload2.agentId);
14924
+ const aborted = agent2.dispatcher.cancel(
14925
+ payload2.conversationId,
14926
+ payload2.agentId,
14927
+ payload2.turnId
14928
+ );
14922
14929
  if (aborted) {
14923
14930
  this.log.debug(
14924
14931
  {
package/dist/runtime.js CHANGED
@@ -2110,7 +2110,7 @@ var CabaneApi = class {
2110
2110
  // Single-shot, NEVER retried: a failed renewal has no answer worth replaying
2111
2111
  // and the next tick asks again; retrying stretches one tick over the next.
2112
2112
  //
2113
- // ALL FAILURE IS `unknown`. Only the server SAYING the turn is over may stop a
2113
+ // Only the server SAYING the turn is over may stop a
2114
2114
  // running loop; a 500, a dropped connection, an expired token or an
2115
2115
  // unparseable body says nothing about the turn, and reading it as death would
2116
2116
  // kill healthy turns on every API hiccup. Fixed here, at the boundary, so no
@@ -2134,8 +2134,8 @@ var CabaneApi = class {
2134
2134
  }
2135
2135
  );
2136
2136
  return res?.state === "running" || res?.state === "ended" ? res.state : "unknown";
2137
- } catch {
2138
- return "unknown";
2137
+ } catch (err) {
2138
+ return isTurnEndedRefusal(err) ? "ended" : "unknown";
2139
2139
  }
2140
2140
  }
2141
2141
  // Flips `active_run_started_at` and optionally captures the SDK session
@@ -8257,8 +8257,8 @@ var Dispatcher = class {
8257
8257
  this.aborts = opts.aborts ?? /* @__PURE__ */ new Map();
8258
8258
  }
8259
8259
  opts;
8260
- // (conversation|agent) → per-TURN controllers. Stop is pair-scoped and must
8261
- // reach every live loop for the pair.
8260
+ // (conversation|agent) → per-turn controllers. A delayed cancel for an old
8261
+ // turn must never reach its successor.
8262
8262
  aborts;
8263
8263
  // Pairs already told their session couldn't be saved: the notice is once per
8264
8264
  // pair, while the rejection repeats every turn.
@@ -8309,19 +8309,22 @@ var Dispatcher = class {
8309
8309
  { turnId: opts.turnId ?? randomUUID4(), resumed: opts.resumed === true }
8310
8310
  );
8311
8311
  }
8312
- // Cancel a (conversation, agent) run in flight in THIS process. True if one
8313
- // was aborted.
8314
- cancel(conversationId, agentId) {
8312
+ // Cancel the named turn. An older server omits turnId and cancels the pair.
8313
+ cancel(conversationId, agentId, turnId) {
8315
8314
  const key = runKey(conversationId, agentId);
8316
8315
  const pair = this.aborts.get(key);
8317
8316
  if (!pair || pair.size === 0) return false;
8318
- for (const ac of [...pair.values()]) {
8317
+ const controllers = turnId === void 0 ? [...pair.values()] : [pair.get(turnId)];
8318
+ let aborted = false;
8319
+ for (const ac of controllers) {
8320
+ if (!ac) continue;
8321
+ aborted = true;
8319
8322
  try {
8320
8323
  ac.abort();
8321
8324
  } catch {
8322
8325
  }
8323
8326
  }
8324
- return true;
8327
+ return aborted;
8325
8328
  }
8326
8329
  // Abort every run in flight in THIS process, whatever its conversation. For
8327
8330
  // an agent taken off this device: nothing it is still running here is
@@ -10528,7 +10531,11 @@ var CompanionSupervisor = class {
10528
10531
  };
10529
10532
  const agent2 = wr.agents.get(payload2.agentId);
10530
10533
  if (agent2) {
10531
- const aborted = agent2.dispatcher.cancel(payload2.conversationId, payload2.agentId);
10534
+ const aborted = agent2.dispatcher.cancel(
10535
+ payload2.conversationId,
10536
+ payload2.agentId,
10537
+ payload2.turnId
10538
+ );
10532
10539
  if (aborted) {
10533
10540
  this.log.debug(
10534
10541
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.196",
3
+ "version": "0.6.198",
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",