@cabane/companion 0.6.165 → 0.6.167
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 +36 -30
- package/dist/cli.js +17 -6
- package/dist/runtime.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# cabane
|
|
2
2
|
|
|
3
|
-
Connect
|
|
3
|
+
Connect **coding tools on your device** to your [Cabane](https://cabane.ai) workspaces as a **responder** — it replies to messages inside Cabane while running as a full local AI client, so you can drive work against your own codebase, files, and MCP servers without putting any of it in Cabane. The model and tools run on your box; only the reply crosses back.
|
|
4
4
|
|
|
5
|
-
The Companion runs your agents through a **harness** on your
|
|
5
|
+
The Companion runs your agents through a **harness** on your device — **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)**, **[Codex](https://developers.openai.com/codex/cli)**, or **[opencode](https://opencode.ai)** — and you expose the ones you have. Install and sign in to a harness yourself; the Companion drives it. See [Harnesses](#harnesses).
|
|
6
6
|
|
|
7
|
-
This is **v0**. It's a small CLI — install it from npm with one command, pair the
|
|
7
|
+
This is **v0**. It's a small CLI — install it from npm with one command, pair the device once, and run. It talks to Cabane only over Cabane's public API.
|
|
8
8
|
|
|
9
|
-
**The step-by-step guides** — **Pair a device**, **Create an agent**, **Give an agent host access** — are in Cabane's docs, with screenshots: in Cabane, open the **Account menu** and choose **Docs**. This README is the reference for _running_ the Companion on the
|
|
9
|
+
**The step-by-step guides** — **Pair a device**, **Create an agent**, **Give an agent host access** — are in Cabane's docs, with screenshots: in Cabane, open the **Account menu** and choose **Docs**. This README is the reference for _running_ the Companion on the device.
|
|
10
10
|
|
|
11
11
|
## The model in one paragraph
|
|
12
12
|
|
|
13
|
-
The Companion runs on a **device you pair**, running **agents you assign to it**. `cabane start` pairs the device with a short code the first time, then runs. From then on it **pulls its assignments from Cabane at runtime** — every agent you've pointed at this device, across all your workspaces — and runs each one locally. Which agents to run, and how they're configured (host access, MCP servers, model), is owned by Cabane and delivered per turn. The
|
|
13
|
+
The Companion runs on a **device you pair**, running **agents you assign to it**. `cabane start` pairs the device with a short code the first time, then runs. From then on it **pulls its assignments from Cabane at runtime** — every agent you've pointed at this device, across all your workspaces — and runs each one locally. Which agents to run, and how they're configured (host access, MCP servers, model), is owned by Cabane and delivered per turn. The device only holds the bits that _must_ be local: the device token, your secrets, and any overrides you set for this device.
|
|
14
14
|
|
|
15
|
-
> **No account password or full-account token ever touches your
|
|
15
|
+
> **No account password or full-account token ever touches your device.** The short-code pairing flow delivers a **device token** (`cabdev_…`) directly to the waiting CLI — good only for pulling this device's assignments and reporting liveness. Each agent the Companion runs gets its own **workspace-bound agent token**, delivered once when the agent is assigned and scoped to that one agent in that one workspace. Renaming, deactivating and removing a device, rotating its token, and assigning agents to it all live in Cabane; the CLI only pairs the device and runs the agents locally.
|
|
16
16
|
|
|
17
17
|
## Prerequisites
|
|
18
18
|
|
|
19
|
-
**Sign in to
|
|
19
|
+
**Sign in to a coding tool first.** The Companion runs turns with its own bundled copies of Claude Code and Codex, so it never needs them on your PATH, only signed in. `cabane start` checks with those bundled copies and refuses to pair until one is signed in, or has an API key, printing the lines to run. The Companion never runs a sign-in itself. One coding tool is enough; a device can offer several. [Harnesses](#harnesses) has the full wiring.
|
|
20
20
|
|
|
21
21
|
**Claude Code** (needs a Claude Pro or Max plan):
|
|
22
22
|
|
|
@@ -80,7 +80,7 @@ It names each agent that's signed in and asks whether to use it in Cabane. Press
|
|
|
80
80
|
|
|
81
81
|
With nothing signed in, it pairs nothing, starts nothing, prints the sign-in lines above and exits non-zero.
|
|
82
82
|
|
|
83
|
-
In Cabane, open the **Account menu** at the bottom left and choose **Settings**, then **Devices**, then **Pair a device** and **Enter pairing code**. Type the eight characters and choose **Pair device**. Or open the link from the terminal, which fills the code in. Any browser signed in to the account will do, so a headless
|
|
83
|
+
In Cabane, open the **Account menu** at the bottom left and choose **Settings**, then **Devices**, then **Pair a device** and **Enter pairing code**. Type the eight characters and choose **Pair device**. Or open the link from the terminal, which fills the code in. Any browser signed in to the account will do, so a headless device needs no browser of its own. The device token is delivered straight to the waiting CLI. Nothing is copied or pasted.
|
|
84
84
|
|
|
85
85
|
The terminal finishes by itself:
|
|
86
86
|
|
|
@@ -95,7 +95,7 @@ The terminal finishes by itself:
|
|
|
95
95
|
All set on this side. Finish up in Cabane.
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
Back in Cabane, **Name this device** asks for a **Device name**, which starts as the
|
|
98
|
+
Back in Cabane, **Name this device** asks for a **Device name**, which starts as the device's hostname and can change later. Choose **Save and continue**. **What Runs on This Device** lists each coding tool on the device as a connector; choose **Continue**. The device is then listed under **Settings → Devices** as **Online**.
|
|
99
99
|
|
|
100
100
|
A freshly paired device runs nothing yet: pairing doesn't put any agent on it. In a workspace, open **Settings**, then **Agents**, choose an agent, and under **Assignment** choose **Assign a device**, pick this device, and choose **Assign device**. Then, under **Execution**, choose its **Connector** and model and choose **Save execution settings**. Until those are saved, the agent's row in the list still says "Not configured". Only the device's own owner can assign an agent to it. The Create an agent guide in Cabane's docs walks these screens. Assign agents in as many workspaces as the account is in: one running Companion serves them all.
|
|
101
101
|
|
|
@@ -115,7 +115,7 @@ A freshly paired device runs nothing yet: pairing doesn't put any agent on it. I
|
|
|
115
115
|
| `logout [-y] [--purge]` | Remove the device token and cached agent credentials. `--purge` removes the whole local config. Neither removes the device from Cabane. |
|
|
116
116
|
| `--version` / `-V` | Print the Companion version. |
|
|
117
117
|
|
|
118
|
-
Everything else about a device is done in Cabane, not the CLI: **rename**, **deactivate**, **rotate token** and **remove** are on the device's page under the account's **Settings → Devices**, and agents are assigned
|
|
118
|
+
Everything else about a device is done in Cabane, not the CLI: **rename**, **deactivate**, **rotate token** and **remove** are on the device's page under the account's **Settings → Devices**, and agents are assigned from each agent's page in Cabane, under **Where it runs**.
|
|
119
119
|
|
|
120
120
|
## Managing the device
|
|
121
121
|
|
|
@@ -124,7 +124,7 @@ Each of these starts in Cabane, on the device's page, and three of them need a c
|
|
|
124
124
|
- **After a reboot, or whenever the Companion has stopped**, run `cabane start`. Running it while the Companion is already running changes nothing. In Cabane, a device whose Companion has stopped turns to **Offline** about two minutes later, and back to **Online** once it is up.
|
|
125
125
|
- **Deactivate** turns the device off without removing it. The Companion on the device exits. To bring it back, choose **Reactivate** in Cabane, then run `cabane start` on the device. The Companion's log says to log out and pair again; that isn't needed.
|
|
126
126
|
- **Rotate token** shows a new device token (`cabdev_…`) once, and the old one stops working at once. On the device, run `cabane stop`, replace the value of `deviceToken` in `~/.cabane/config.json` with the new token, then run `cabane start`. Stop first: a Companion that is still running keeps the old token, and `start` does nothing while one is running.
|
|
127
|
-
- **Remove device** can't be undone. The Companion on the device exits. To pair the same
|
|
127
|
+
- **Remove device** can't be undone. The Companion on the device exits. To pair the same device again, run `cabane logout`, then `cabane start`.
|
|
128
128
|
|
|
129
129
|
If `cabane start` says the Companion is running but the device stays **Offline**, the Companion may have exited straight after starting, which happens when Cabane no longer accepts the device. `cabane status` and `cabane logs` show which.
|
|
130
130
|
|
|
@@ -140,37 +140,39 @@ Updates need the service: it's what restarts the Companion into a new version. T
|
|
|
140
140
|
|
|
141
141
|
Run `cabane start --foreground` with `CABANE_COMPANION_SUPERVISED=1` in its environment. Configure the supervisor itself for restart and login/boot behavior, and use absolute executable paths when it does not load your interactive shell: `~/.cabane/node/bin/node` and `~/.cabane/current/dist/bin.js` don't move on an update. Without the boot guard a supervisor of your own doesn't roll back a release that fails before it starts.
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
<a id="configuring-an-agent-whats-in-cabane-vs-on-this-machine"></a>
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
## Configuring an agent: what's in Cabane vs. on this device
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
There's a deliberate split. **What the agent is** — its host access, working directory, MCP servers, connector, model and charter — is configured in **Cabane** (each agent's page) and delivered to the Companion per turn, so an edit in Cabane lands on the agent's next reply with **no restart**. One exception: a conversation given Custom execution settings keeps its own connector, model and tuning when the agent's defaults change. The Companion holds only the bits that can't travel from a server: your **secrets**, and device-local overrides such as a prepare-hook.
|
|
148
|
+
|
|
149
|
+
An agent can reach MCP servers two ways, and they don't overlap. **The agent's own MCP servers** are part of what the agent is: set on its page, they go with it into every room. **Connections** belong to a person instead: you add one under a workspace's **Settings → Connections** (an endpoint and a pasted key, stored encrypted in Cabane), then add it to a room from the room's menu and pick which agents there may use it. On each turn in that room, Cabane attaches the room's connections for that agent and sends the key with the turn, into the harness's environment and never onto its command line. Nothing is configured on this device. Replacing, disconnecting or deleting a key takes effect on the agent's next turn, and nothing is revoked at the service itself.
|
|
148
150
|
|
|
149
151
|
### Host access (set in Cabane)
|
|
150
152
|
|
|
151
|
-
**Host access** is a setting
|
|
153
|
+
**Host access** is a setting under **What it can reach**, on the agent's page in Cabane. Only the agent's owner can change it.
|
|
152
154
|
|
|
153
155
|
| Host access | What the agent can do |
|
|
154
156
|
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
155
157
|
| No | Work in the workspace: read and write the workspace's files and take part in conversations. No shell or file tools on the device. The default. |
|
|
156
158
|
| Yes | All of that, plus run shell commands and read and write files on the device, starting in its working directory. ⚠️ |
|
|
157
159
|
|
|
158
|
-
> ⚠️ **An agent with host access acts on the device as the user account the Companion runs under, with no approval prompts.** With Claude Code as the connector, it runs every command and writes every file it is asked to, and it isn't restricted to its working directory. Turn it on for an agent whose work needs the device, on a device where that reach is acceptable. The [sandbox](#sandbox-set-on-this-
|
|
160
|
+
> ⚠️ **An agent with host access acts on the device as the user account the Companion runs under, with no approval prompts.** With Claude Code as the connector, it runs every command and writes every file it is asked to, and it isn't restricted to its working directory. Turn it on for an agent whose work needs the device, on a device where that reach is acceptable. The [sandbox](#sandbox-set-on-this-device) below limits what those tools can touch.
|
|
159
161
|
|
|
160
|
-
|
|
162
|
+
The agent's MCP servers don't depend on host access: a stdio server listed there runs on the device and gives the agent its tools either way.
|
|
161
163
|
|
|
162
164
|
### Working directory + prepare-hook
|
|
163
165
|
|
|
164
|
-
An agent with host access starts in
|
|
166
|
+
An agent with host access starts in the folder set under **Starts in**, in Cabane's **What it can reach** dialog alongside host access. Left empty, or naming a folder that isn't on the device, the agent starts in the folder the Companion was started from; a missing folder is reported only in the Companion's log.
|
|
165
167
|
|
|
166
|
-
The
|
|
168
|
+
The device can override it. A `cwd` in `~/.cabane/config.json` wins over the directory set in Cabane, and a prepare-hook wins over both. Add an `agents` map keyed by the agent's id, its `@username`, or `workspace-slug/username` (all three show up in `cabane status`, copy-pasteable):
|
|
167
169
|
|
|
168
170
|
```jsonc
|
|
169
171
|
{
|
|
170
172
|
// …device identity, written when the device is paired. The one field to edit by
|
|
171
173
|
// hand is `deviceToken`, after rotating the token in Cabane…
|
|
172
174
|
|
|
173
|
-
// Per-agent
|
|
175
|
+
// Per-agent device-local overrides. Only `cwd`, `prepareHook`, `claudeCode` and
|
|
174
176
|
// `sandbox` live here — everything else about the agent (host access, MCP
|
|
175
177
|
// servers, model) is set in Cabane.
|
|
176
178
|
"agents": {
|
|
@@ -212,9 +214,11 @@ Claude Code's **auto-memory** — where the agent writes notes to a local `~/.cl
|
|
|
212
214
|
}
|
|
213
215
|
```
|
|
214
216
|
|
|
215
|
-
|
|
217
|
+
<a id="secrets-set-on-this-machine"></a>
|
|
218
|
+
|
|
219
|
+
### Secrets (set on this device)
|
|
216
220
|
|
|
217
|
-
When an agent's MCP servers in Cabane need a credential, that credential is written in Cabane as a `${PLACEHOLDER}` reference — the real value never leaves your
|
|
221
|
+
When an agent's MCP servers in Cabane need a credential, that credential is written in Cabane as a `${PLACEHOLDER}` reference — the real value never leaves your device. The Companion resolves placeholders at dispatch time from an explicit, operator-declared store at `~/.cabane/secrets.json`, a flat `{ NAME: "value" }` map (mode 600):
|
|
218
222
|
|
|
219
223
|
```jsonc
|
|
220
224
|
// ~/.cabane/secrets.json
|
|
@@ -226,9 +230,11 @@ When an agent's MCP servers in Cabane need a credential, that credential is writ
|
|
|
226
230
|
|
|
227
231
|
A `${VAR}` the store doesn't declare fails the turn loudly — it is **never** read from your shell environment, so a config can't smuggle out an ambient credential. The Companion reports the declared **names** (never values) to Cabane on its heartbeat, so the settings UI can warn "this agent needs `${GITHUB_TOKEN}`, this device doesn't expose it" before a turn ever runs.
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
<a id="sandbox-set-on-this-machine"></a>
|
|
234
|
+
|
|
235
|
+
### Sandbox (set on this device)
|
|
230
236
|
|
|
231
|
-
With `"runners": { "enabled": true, "sandbox": "os" }`, each turn's harness — the model's process and every shell it starts — runs inside an OS sandbox (bubblewrap on Linux, Seatbelt on macOS) whose network goes through a local proxy. It's a guardrail on your own
|
|
237
|
+
With `"runners": { "enabled": true, "sandbox": "os" }`, each turn's harness — the model's process and every shell it starts — runs inside an OS sandbox (bubblewrap on Linux, Seatbelt on macOS) whose network goes through a local proxy. It's a guardrail on your own device: it stops a turn from reading the secrets named below or reaching hosts you didn't allow. It runs as your user on your kernel, so it isn't isolation. _Host access_ on the agent in Cabane decides whether the agent gets shell and file tools at all; the sandbox decides what those tools can touch. The two are independent.
|
|
232
238
|
|
|
233
239
|
What the sandbox allows is `runners.policy` in `~/.cabane/config.json`. Every key is optional, and with no `policy` block you get the defaults:
|
|
234
240
|
|
|
@@ -256,7 +262,7 @@ What the sandbox allows is `runners.policy` in `~/.cabane/config.json`. Every ke
|
|
|
256
262
|
| **Reads**, `blocklist` mode (the default) | Everything is readable except every companion's store (`~/.cabane`, and any `.cabane` under your home or beside this companion's home; see below), the `.claude` and `.codex` of the other companion homes those stores sit in, `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gh`, `~/.git-credentials`, `~/.netrc`, `~/.npmrc`, `~/.pypirc`, `~/.docker/config.json`, `~/.kube`, `~/.config/gcloud`, `~/.azure`, `~/.config/op`, `~/.password-store`, and your `denyRead` paths. `allowRead` reopens a path inside a denied one. |
|
|
257
263
|
| **Writes** | The working directory, the temp dir, `~/.claude`, `~/.claude.json`, `~/.codex`, plus `allowWrite`. |
|
|
258
264
|
|
|
259
|
-
**One agent can be excepted.** The sandbox is set per device, and one agent's row in `agents` can say otherwise — for example an ops agent whose job is the
|
|
265
|
+
**One agent can be excepted.** The sandbox is set per device, and one agent's row in `agents` can say otherwise — for example an ops agent whose job is the device itself, on a device whose other agents stay sandboxed:
|
|
260
266
|
|
|
261
267
|
```jsonc
|
|
262
268
|
{
|
|
@@ -268,7 +274,7 @@ What the sandbox allows is `runners.policy` in `~/.cabane/config.json`. Every ke
|
|
|
268
274
|
},
|
|
269
275
|
},
|
|
270
276
|
"agents": {
|
|
271
|
-
"014d18a7-6395-48e3-a2f2-313d5baa2a5e": { "sandbox": "none" }, // the ops agent, by its id: it runs with the
|
|
277
|
+
"014d18a7-6395-48e3-a2f2-313d5baa2a5e": { "sandbox": "none" }, // the ops agent, by its id: it runs with the device; everyone else stays sandboxed
|
|
272
278
|
},
|
|
273
279
|
}
|
|
274
280
|
```
|
|
@@ -277,7 +283,7 @@ It works the other way too: `"agents": { "<agent id>": { "sandbox": "os" } }` sa
|
|
|
277
283
|
|
|
278
284
|
Paths may start with `~/` and may be globs. An invalid `policy` doesn't stop the Companion from starting. Instead, every sandboxed turn is refused and the conversation names the field that's wrong, for example `runners.policy.filesystem.mode`.
|
|
279
285
|
|
|
280
|
-
**Other companions on the same
|
|
286
|
+
**Other companions on the same device.** A second companion (another account, a test device) keeps its own secrets in its own `.cabane`, so a turn can't read any companion's store, not only this device's. Every turn denies this companion's store and `~/.cabane` (both, when the Companion runs from a home set with `CABANE_COMPANION_HOME`), plus any `.cabane` up to two levels under your home or beside this companion's home, where companion homes conventionally sit. Stores anywhere deeper are found by a search of your home with `find`, along with the `.claude` and `.codex` beside each one, which hold that companion's harness logins. That search takes seconds over a large home, so the Companion runs it in the background: when it starts, and again whenever a turn starts more than 5 minutes after the last search finished. Only the first turn after a start waits for it. **A store created deeper than two levels is denied within about 5 minutes plus one search, not instantly.** If a search can't finish (`find` is missing, runs past 60 seconds, or hits a directory it can't list), the stores it did find are still denied, and the log says so. The search runs in strict mode too, because strict mode reopens the working directory, and a store inside it must stay hidden.
|
|
281
287
|
|
|
282
288
|
**`.env` files are readable.** Beyond the credential locations above, nothing guesses which of your files hold secrets, so `.env` files, in the working directory or any other project, are readable unless you deny them. To hide them, list the roots that hold them, for example `"denyRead": ["~/work/**/.env", "~/work/**/.env.*"]`. Each glob is expanded when the turn starts, which costs a walk of that tree on every turn and misses files created during the turn, so keep the roots narrow. When other projects' secrets really matter, strict mode is the better answer, because it hides them all without a walk.
|
|
283
289
|
|
|
@@ -290,9 +296,9 @@ Paths may start with `~/` and may be globs. An invalid `policy` doesn't stop the
|
|
|
290
296
|
1. Create a fine-grained personal access token scoped to your repositories, with Contents and Pull requests set to read and write.
|
|
291
297
|
2. Put it in `~/.cabane/secrets.json` as `"GITHUB_TOKEN": "github_pat_…"`.
|
|
292
298
|
3. Add both credential entries from the example above: `env.GH_TOKEN` with `"hosts": ["api.github.com"]` for `gh`, and a `git` entry for `github.com`.
|
|
293
|
-
4. Use HTTPS remotes, because `~/.ssh` is unreadable in the sandbox. Rewrite existing SSH remotes once on the
|
|
299
|
+
4. Use HTTPS remotes, because `~/.ssh` is unreadable in the sandbox. Rewrite existing SSH remotes once on the device: `git config --global url."https://github.com/".insteadOf git@github.com:`.
|
|
294
300
|
|
|
295
|
-
A `gh auth login` session on the
|
|
301
|
+
A `gh auth login` session on the device isn't used: `~/.config/gh` is unreadable in the sandbox. Only the token in `secrets.json` is.
|
|
296
302
|
|
|
297
303
|
`cabane status` prints the effective posture: the filesystem mode, extra hosts, denied hosts and reads, the **names** of masked credentials, never their values, and each agent excepted on its row. The Companion also logs it once at start.
|
|
298
304
|
|
|
@@ -300,7 +306,7 @@ A `gh auth login` session on the machine isn't used: `~/.config/gh` is unreadabl
|
|
|
300
306
|
|
|
301
307
|
A **harness** is the local tool that runs a model — **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)**, **[Codex](https://developers.openai.com/codex/cli)**, or **[opencode](https://opencode.ai)** — together with the credential behind it. A harness connected to Cabane on this device is a **Connector**, and one device runs as many Connectors as it has harnesses. None is privileged: a device advertises exactly the harnesses it actually has.
|
|
302
308
|
|
|
303
|
-
Which harness a turn uses is the agent's **Connector**, chosen in Cabane under **
|
|
309
|
+
Which harness a turn uses is the agent's **Connector**, chosen in Cabane under **How it thinks** on the agent's page, together with a model from that connector. The list holds the connectors on the agent's device, so set the harness up first, then choose it for the agent.
|
|
304
310
|
|
|
305
311
|
What "exposed" means differs per harness, because each is discoverable in a different way. Bring your own install and your own login in all three cases — the Companion never installs a binary and never drives a login:
|
|
306
312
|
|
package/dist/cli.js
CHANGED
|
@@ -11212,7 +11212,7 @@ var init_outbox = __esm({
|
|
|
11212
11212
|
}
|
|
11213
11213
|
this.log?.warn(
|
|
11214
11214
|
{ workspaceId: this.workspaceId, err: err instanceof Error ? err.message : String(err) },
|
|
11215
|
-
"Couldn't save an update on this
|
|
11215
|
+
"Couldn't save an update on this device. Check free disk space and access to ~/.cabane."
|
|
11216
11216
|
);
|
|
11217
11217
|
return;
|
|
11218
11218
|
}
|
|
@@ -12083,7 +12083,7 @@ function nothingUsableLines(detection) {
|
|
|
12083
12083
|
if (reason) lines2.push(reason);
|
|
12084
12084
|
}
|
|
12085
12085
|
}
|
|
12086
|
-
lines2.push(`Nothing has been started. Run ${cabane("setup")} to set up
|
|
12086
|
+
lines2.push(`Nothing has been started. Run ${cabane("setup")} to set up a coding tool.`);
|
|
12087
12087
|
return lines2;
|
|
12088
12088
|
}
|
|
12089
12089
|
function indent(line) {
|
|
@@ -14402,7 +14402,10 @@ async function setupScript(opts, deps) {
|
|
|
14402
14402
|
const detection = await detect(cfg, running, deps);
|
|
14403
14403
|
ui.intro("Cabane setup");
|
|
14404
14404
|
const usable = anyUsable(detection);
|
|
14405
|
-
ui.step([
|
|
14405
|
+
ui.step([
|
|
14406
|
+
"Coding tools on this device",
|
|
14407
|
+
...agentLines(detection, { signInHint: paired && !usable })
|
|
14408
|
+
]);
|
|
14406
14409
|
if (paired) ui.step([PAIRED_LINE]);
|
|
14407
14410
|
if (paired && running) {
|
|
14408
14411
|
ui.step([runningLine(serviceInstalled())]);
|
|
@@ -17320,6 +17323,10 @@ var CompanionSupervisor = class {
|
|
|
17320
17323
|
heartbeated = false;
|
|
17321
17324
|
recoveryCursor;
|
|
17322
17325
|
recoveredTurns = /* @__PURE__ */ new Set();
|
|
17326
|
+
// Agents assigned here, as `workspaceId|agentId`, whose sign-in to Cabane is missing and
|
|
17327
|
+
// couldn't be had back on the last pull. A dispatch for one is dropped as `missing_credential`,
|
|
17328
|
+
// so the room names that cause rather than a roster miss. Rebuilt on every pull.
|
|
17329
|
+
uncredentialed = /* @__PURE__ */ new Set();
|
|
17323
17330
|
heartbeatTimer = null;
|
|
17324
17331
|
inFlightHeartbeat = null;
|
|
17325
17332
|
pollTimer = null;
|
|
@@ -17615,6 +17622,9 @@ var CompanionSupervisor = class {
|
|
|
17615
17622
|
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
17616
17623
|
}
|
|
17617
17624
|
items = await this.restoreMissingCredentials(device.id, items);
|
|
17625
|
+
this.uncredentialed = new Set(
|
|
17626
|
+
items.filter((it) => !it.credential && !getCredential(device.id, it.agentId)).map((it) => `${it.workspaceId}|${it.agentId}`)
|
|
17627
|
+
);
|
|
17618
17628
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
17619
17629
|
for (const it of items) {
|
|
17620
17630
|
const list = byWorkspace.get(it.workspaceId) ?? [];
|
|
@@ -17723,7 +17733,7 @@ var CompanionSupervisor = class {
|
|
|
17723
17733
|
if (!credential) {
|
|
17724
17734
|
this.log.error(
|
|
17725
17735
|
{ workspaceId, agentId: it.agentId, username: it.agentUsername },
|
|
17726
|
-
`Cannot run ${it.agentDisplayName}: its sign-in to Cabane is missing.
|
|
17736
|
+
`Cannot run ${it.agentDisplayName}: its sign-in to Cabane is missing and Cabane didn't send a new one. Asking again on the next check.`
|
|
17727
17737
|
);
|
|
17728
17738
|
this.removeAgent(wr, it.agentId);
|
|
17729
17739
|
this.hub.setAgent(workspaceId, {
|
|
@@ -18209,7 +18219,8 @@ var CompanionSupervisor = class {
|
|
|
18209
18219
|
conversationId: payload.conversationId,
|
|
18210
18220
|
agentId: payload.agentId,
|
|
18211
18221
|
messageId: payload.messageId,
|
|
18212
|
-
reason: "agent_not_on_device"
|
|
18222
|
+
reason: "agent_not_on_device",
|
|
18223
|
+
...this.uncredentialed.has(`${workspaceId}|${payload.agentId}`) ? { detail: "missing_credential" } : {}
|
|
18213
18224
|
});
|
|
18214
18225
|
this.log.debug(
|
|
18215
18226
|
{
|
|
@@ -18961,7 +18972,7 @@ async function startScript(opts, interactive) {
|
|
|
18961
18972
|
}
|
|
18962
18973
|
if (!isDevicePaired()) {
|
|
18963
18974
|
refuse([
|
|
18964
|
-
`This device isn't set up yet. Run ${cabane("setup")} to
|
|
18975
|
+
`This device isn't set up yet. Run ${cabane("setup")} to set up a coding tool and pair it.`
|
|
18965
18976
|
]);
|
|
18966
18977
|
return;
|
|
18967
18978
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -13014,7 +13014,7 @@ var Outbox = class {
|
|
|
13014
13014
|
}
|
|
13015
13015
|
this.log?.warn(
|
|
13016
13016
|
{ workspaceId: this.workspaceId, err: err instanceof Error ? err.message : String(err) },
|
|
13017
|
-
"Couldn't save an update on this
|
|
13017
|
+
"Couldn't save an update on this device. Check free disk space and access to ~/.cabane."
|
|
13018
13018
|
);
|
|
13019
13019
|
return;
|
|
13020
13020
|
}
|
|
@@ -13886,6 +13886,10 @@ var CompanionSupervisor = class {
|
|
|
13886
13886
|
heartbeated = false;
|
|
13887
13887
|
recoveryCursor;
|
|
13888
13888
|
recoveredTurns = /* @__PURE__ */ new Set();
|
|
13889
|
+
// Agents assigned here, as `workspaceId|agentId`, whose sign-in to Cabane is missing and
|
|
13890
|
+
// couldn't be had back on the last pull. A dispatch for one is dropped as `missing_credential`,
|
|
13891
|
+
// so the room names that cause rather than a roster miss. Rebuilt on every pull.
|
|
13892
|
+
uncredentialed = /* @__PURE__ */ new Set();
|
|
13889
13893
|
heartbeatTimer = null;
|
|
13890
13894
|
inFlightHeartbeat = null;
|
|
13891
13895
|
pollTimer = null;
|
|
@@ -14181,6 +14185,9 @@ var CompanionSupervisor = class {
|
|
|
14181
14185
|
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
14182
14186
|
}
|
|
14183
14187
|
items = await this.restoreMissingCredentials(device.id, items);
|
|
14188
|
+
this.uncredentialed = new Set(
|
|
14189
|
+
items.filter((it) => !it.credential && !getCredential(device.id, it.agentId)).map((it) => `${it.workspaceId}|${it.agentId}`)
|
|
14190
|
+
);
|
|
14184
14191
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
14185
14192
|
for (const it of items) {
|
|
14186
14193
|
const list = byWorkspace.get(it.workspaceId) ?? [];
|
|
@@ -14289,7 +14296,7 @@ var CompanionSupervisor = class {
|
|
|
14289
14296
|
if (!credential) {
|
|
14290
14297
|
this.log.error(
|
|
14291
14298
|
{ workspaceId, agentId: it.agentId, username: it.agentUsername },
|
|
14292
|
-
`Cannot run ${it.agentDisplayName}: its sign-in to Cabane is missing.
|
|
14299
|
+
`Cannot run ${it.agentDisplayName}: its sign-in to Cabane is missing and Cabane didn't send a new one. Asking again on the next check.`
|
|
14293
14300
|
);
|
|
14294
14301
|
this.removeAgent(wr, it.agentId);
|
|
14295
14302
|
this.hub.setAgent(workspaceId, {
|
|
@@ -14775,7 +14782,8 @@ var CompanionSupervisor = class {
|
|
|
14775
14782
|
conversationId: payload.conversationId,
|
|
14776
14783
|
agentId: payload.agentId,
|
|
14777
14784
|
messageId: payload.messageId,
|
|
14778
|
-
reason: "agent_not_on_device"
|
|
14785
|
+
reason: "agent_not_on_device",
|
|
14786
|
+
...this.uncredentialed.has(`${workspaceId}|${payload.agentId}`) ? { detail: "missing_credential" } : {}
|
|
14779
14787
|
});
|
|
14780
14788
|
this.log.debug(
|
|
14781
14789
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.167",
|
|
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",
|