borgmcp 2.16.1 → 3.0.2
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 +5 -3
- package/dist/assimilate-cmd.js +43 -43
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.js +2 -2
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/assimilate-guard.js +4 -4
- package/dist/assimilate-guard.js.map +1 -1
- package/dist/assimilate-welcome.js +2 -2
- package/dist/assimilate-welcome.js.map +1 -1
- package/dist/bare-launch-menu.d.ts +5 -5
- package/dist/bare-launch-menu.d.ts.map +1 -1
- package/dist/bare-launch-menu.js +2 -2
- package/dist/bare-launch-menu.js.map +1 -1
- package/dist/claude.d.ts +0 -2
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +5 -18
- package/dist/claude.js.map +1 -1
- package/dist/cleanup-cmd.d.ts +1 -1
- package/dist/cleanup-cmd.d.ts.map +1 -1
- package/dist/cleanup-cmd.js +3 -3
- package/dist/cleanup-cmd.js.map +1 -1
- package/dist/cli-help.d.ts +1 -2
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +15 -24
- package/dist/cli-help.js.map +1 -1
- package/dist/cli-platform.d.ts +4 -0
- package/dist/cli-platform.d.ts.map +1 -1
- package/dist/cli-platform.js +29 -10
- package/dist/cli-platform.js.map +1 -1
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +30 -12
- package/dist/cubes.js.map +1 -1
- package/dist/docs-sections.js +1 -1
- package/dist/docs-sections.js.map +1 -1
- package/dist/drone-lifecycle.d.ts.map +1 -1
- package/dist/drone-lifecycle.js +3 -3
- package/dist/drone-lifecycle.js.map +1 -1
- package/dist/drone-management.js +1 -1
- package/dist/drone-management.js.map +1 -1
- package/dist/evict-drone.js +2 -2
- package/dist/evict-drone.js.map +1 -1
- package/dist/first-run-server.d.ts.map +1 -1
- package/dist/first-run-server.js +4 -1
- package/dist/first-run-server.js.map +1 -1
- package/dist/foreign-path-reminder.js +1 -1
- package/dist/foreign-path-reminder.js.map +1 -1
- package/dist/inbox-monitor.js +2 -2
- package/dist/inbox-monitor.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/launch-all-cmd.js +12 -12
- package/dist/launch-all-cmd.js.map +1 -1
- package/dist/launch-all-discovery.js +1 -1
- package/dist/launch-all-discovery.js.map +1 -1
- package/dist/lifecycle-log-guard.d.ts.map +1 -1
- package/dist/lifecycle-log-guard.js +12 -3
- package/dist/lifecycle-log-guard.js.map +1 -1
- package/dist/opencode-seat-identity.js +5 -5
- package/dist/opencode-seat-identity.js.map +1 -1
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +2 -1
- package/dist/regen-format.js.map +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +10 -5
- package/dist/remote-client.js.map +1 -1
- package/dist/repository-cube-init.js +1 -1
- package/dist/repository-cube-init.js.map +1 -1
- package/dist/reset-local-seat-cmd.d.ts +2 -2
- package/dist/reset-local-seat-cmd.d.ts.map +1 -1
- package/dist/reset-local-seat-cmd.js +17 -17
- package/dist/reset-local-seat-cmd.js.map +1 -1
- package/dist/seat-reattach-guard.js +1 -1
- package/dist/seat-reattach-guard.js.map +1 -1
- package/dist/seat-store.d.ts +1 -1
- package/dist/seat-store.js +6 -6
- package/dist/seat-store.js.map +1 -1
- package/dist/seats.js +1 -1
- package/dist/seats.js.map +1 -1
- package/dist/server-handshake.js +1 -1
- package/dist/server-handshake.js.map +1 -1
- package/dist/stream-status.js +3 -3
- package/dist/stream-status.js.map +1 -1
- package/dist/tool-manifest.js +1 -1
- package/dist/tool-manifest.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -2
- package/dist/unknown-subcommand.js.map +1 -1
- package/dist/worktree-lifecycle.d.ts +0 -16
- package/dist/worktree-lifecycle.d.ts.map +1 -1
- package/dist/worktree-lifecycle.js +0 -38
- package/dist/worktree-lifecycle.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +5 -3
- package/docs/LOCAL_SERVER.md +2 -2
- package/docs/SEAT_LIFECYCLE.md +19 -19
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +43 -43
- package/src/assimilate-deps.ts +2 -2
- package/src/assimilate-guard.ts +4 -4
- package/src/assimilate-welcome.ts +2 -2
- package/src/bare-launch-menu.ts +6 -6
- package/src/claude.ts +8 -21
- package/src/cleanup-cmd.ts +4 -4
- package/src/cli-help.ts +15 -27
- package/src/cli-platform.ts +42 -10
- package/src/cubes.ts +29 -14
- package/src/docs-sections.ts +1 -1
- package/src/drone-lifecycle.ts +3 -3
- package/src/drone-management.ts +1 -1
- package/src/evict-drone.ts +2 -2
- package/src/first-run-server.ts +4 -1
- package/src/foreign-path-reminder.ts +1 -1
- package/src/inbox-monitor.ts +2 -2
- package/src/index.ts +1 -1
- package/src/launch-all-cmd.ts +12 -12
- package/src/launch-all-discovery.ts +1 -1
- package/src/lifecycle-log-guard.ts +15 -3
- package/src/opencode-seat-identity.ts +5 -5
- package/src/regen-format.ts +2 -1
- package/src/remote-client.ts +12 -6
- package/src/repository-cube-init.ts +1 -1
- package/src/reset-local-seat-cmd.ts +17 -17
- package/src/seat-reattach-guard.ts +1 -1
- package/src/seat-store.ts +6 -6
- package/src/seats.ts +1 -1
- package/src/server-handshake.ts +1 -1
- package/src/stream-status.ts +3 -3
- package/src/tool-manifest.ts +1 -1
- package/src/unknown-subcommand.ts +1 -2
- package/src/worktree-lifecycle.ts +0 -45
- package/dist/sync.d.ts +0 -129
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -286
- package/dist/sync.js.map +0 -1
- package/src/sync.ts +0 -363
package/docs/SEAT_LIFECYCLE.md
CHANGED
|
@@ -17,7 +17,7 @@ Only two seat states are persisted:
|
|
|
17
17
|
`revoked`, `superseded`, and `evicted` are server verdicts, not values written
|
|
18
18
|
to `seats.json`. Revocation and supersession do not silently delete local
|
|
19
19
|
state. An authoritative eviction is the sole verdict that permits the terminal
|
|
20
|
-
assimilation flow to replace a rejected seat. `borg reset-local-
|
|
20
|
+
assimilation flow to replace a rejected seat. `borg reset-local-connection` is the
|
|
21
21
|
explicit offline deletion path.
|
|
22
22
|
|
|
23
23
|
Seat sessions do not expire. A legacy `expiresAt` field is ignored, and current
|
|
@@ -26,11 +26,11 @@ invitation can expire; recovery is to request a new invitation. An expired
|
|
|
26
26
|
stream cursor is also separate from seat identity and is recovered by resetting
|
|
27
27
|
the cursor and reconnecting.
|
|
28
28
|
|
|
29
|
-
The whole
|
|
29
|
+
The whole private store fails closed if any persisted record is malformed. The
|
|
30
30
|
store reader raises:
|
|
31
31
|
|
|
32
32
|
```text
|
|
33
|
-
Borg
|
|
33
|
+
Borg private store is malformed or has an unsupported version; refusing to read it
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
The file is not rewritten and no bearer material is included in the error.
|
|
@@ -74,7 +74,7 @@ probes it. A live seat is reattached with the identical bearer; its saved role
|
|
|
74
74
|
is authoritative, and no new drone is minted. The success line is:
|
|
75
75
|
|
|
76
76
|
```text
|
|
77
|
-
re-attached
|
|
77
|
+
re-attached as <drone-label> (same session, no new drone minted)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Before relaunch, Borg checks the seat's inbox-monitor PID. A live holder refuses
|
|
@@ -82,7 +82,7 @@ the relaunch, names its PID, and gives both the safe fresh-worktree path and the
|
|
|
82
82
|
explicit one-time override:
|
|
83
83
|
|
|
84
84
|
```text
|
|
85
|
-
This worktree's Borg
|
|
85
|
+
This worktree's Borg drone already has a live session (inbox monitor pid <pid>).
|
|
86
86
|
No agent was launched. Stop the existing session or use a fresh worktree with `borg assimilate --worktree <name>`. If the live monitor is wedged, override once with `borg assimilate --here --force`.
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -100,7 +100,7 @@ If a second monitor reaches the singleton after launch, it exits successfully
|
|
|
100
100
|
without tailing and prints:
|
|
101
101
|
|
|
102
102
|
```text
|
|
103
|
-
borg-inbox-monitor:
|
|
103
|
+
borg-inbox-monitor: inbox "<path>" is already monitored by a live instance (pid <pid>); yielding — another session likely holds this worktree's connection.
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
The existing monitor remains the sole inbox reader.
|
|
@@ -114,7 +114,7 @@ worktree to another cube.
|
|
|
114
114
|
With no saved identity, it returns:
|
|
115
115
|
|
|
116
116
|
```text
|
|
117
|
-
◼ This session has no drone
|
|
117
|
+
◼ This session has no drone for this worktree, and in-session borg_assimilate is re-attach-only (it never creates drones — gh#780). To create a drone for cube "<cube>", run `borg assimilate` in a terminal — it spawns the worktree, persists the identity, and launches the agent in one step.
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
For another cube, it returns:
|
|
@@ -129,15 +129,15 @@ A successful in-session re-attach begins:
|
|
|
129
129
|
# Re-attached to cube: <cube>
|
|
130
130
|
|
|
131
131
|
**Drone label:** <drone-label>
|
|
132
|
-
**
|
|
132
|
+
**Drone:** existing identity reused — no new drone minted (gh#780)
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
If the server no longer accepts the saved seat, the tool fails rather than
|
|
136
136
|
reminting:
|
|
137
137
|
|
|
138
138
|
```text
|
|
139
|
-
◼ Re-attach failed — this worktree's saved
|
|
140
|
-
Recover by running `borg assimilate` in a terminal to create a fresh
|
|
139
|
+
◼ Re-attach failed — this worktree's saved connection is unreachable (likely evicted or its session was revoked). Server said: <server-error>
|
|
140
|
+
Recover by running `borg assimilate` in a terminal to create a fresh drone; in-session borg_assimilate never re-mints (gh#780).
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
## Terminal verdicts and recovery
|
|
@@ -149,12 +149,12 @@ does not mutate the saved seat. The exact output is one of:
|
|
|
149
149
|
|
|
150
150
|
```text
|
|
151
151
|
Local session was revoked.
|
|
152
|
-
Next: run borg reset-local-
|
|
152
|
+
Next: run borg reset-local-connection, then borg assimilate --host <server> --enroll.
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
```text
|
|
156
156
|
Local session was superseded by a newer enrollment.
|
|
157
|
-
Next: run borg reset-local-
|
|
157
|
+
Next: run borg reset-local-connection, then borg assimilate --host <server> --enroll.
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
Run the named offline reset, obtain a new invitation, and enroll again.
|
|
@@ -171,7 +171,7 @@ If the attach itself returns the eviction after re-attach began, Borg does not
|
|
|
171
171
|
claim recovery. It prints:
|
|
172
172
|
|
|
173
173
|
```text
|
|
174
|
-
This worktree's
|
|
174
|
+
This worktree's drone on <server> was evicted. Remove this worktree, or from a fresh worktree run `borg assimilate --host <server>`.
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
The stream path also treats typed `DRONE_EVICTED` as terminal: it marks that
|
|
@@ -188,20 +188,20 @@ The enrollment invitation for <server> was rejected or expired. Ask the server o
|
|
|
188
188
|
|
|
189
189
|
## Offline reset
|
|
190
190
|
|
|
191
|
-
`borg reset-local-
|
|
191
|
+
`borg reset-local-connection` clears only the current worktree's saved credential and
|
|
192
192
|
cube binding. It makes no network call, revokes nothing server-side, and leaves
|
|
193
193
|
the server, trust anchor, cube, and sibling worktrees unchanged.
|
|
194
194
|
|
|
195
195
|
Its shipped help describes the command as:
|
|
196
196
|
|
|
197
197
|
```text
|
|
198
|
-
borg reset-local-
|
|
198
|
+
borg reset-local-connection (borgmcp <version>) — clear ONLY this worktree's saved connection to its cube
|
|
199
199
|
|
|
200
200
|
Usage:
|
|
201
|
-
borg reset-local-
|
|
202
|
-
borg reset-local-
|
|
203
|
-
borg reset-local-
|
|
204
|
-
borg reset-local-
|
|
201
|
+
borg reset-local-connection Reset this worktree's saved connection (TTY confirms [y/N])
|
|
202
|
+
borg reset-local-connection --host <host> No-op unless this worktree connects to <host>
|
|
203
|
+
borg reset-local-connection --yes Reset without a prompt (required when non-interactive)
|
|
204
|
+
borg reset-local-connection --help Show this help
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
The command snapshots the exact binding and token-safe bearer observation,
|
package/package.json
CHANGED
package/src/assimilate-cmd.ts
CHANGED
|
@@ -615,7 +615,7 @@ function reportServerFailure(
|
|
|
615
615
|
);
|
|
616
616
|
return 1;
|
|
617
617
|
}
|
|
618
|
-
if (/seat store lock file .* is stale/i.test(message)) {
|
|
618
|
+
if (/(?:private|seat) store lock file .* is stale/i.test(message)) {
|
|
619
619
|
// RULED option (b): a lock whose recorded holder is DEAD (or whose payload is
|
|
620
620
|
// corrupt) is NEVER auto-removed. Surface the fail-closed guidance verbatim —
|
|
621
621
|
// it already names the exact lockfile path and the delete-only-if-no-borg
|
|
@@ -626,16 +626,16 @@ function reportServerFailure(
|
|
|
626
626
|
);
|
|
627
627
|
return 1;
|
|
628
628
|
}
|
|
629
|
-
if (/(?:seat|credential) store is busy/i.test(message)) {
|
|
629
|
+
if (/(?:private|seat|credential) store is busy/i.test(message)) {
|
|
630
630
|
deps.stderr(
|
|
631
|
-
`Borg's
|
|
632
|
-
`creating or resuming saved
|
|
631
|
+
`Borg's private store is busy for ${apiUrl} because another Borg process is ` +
|
|
632
|
+
`creating or resuming saved connection state. Wait for it to finish, then rerun ${retryCommand}.\n`,
|
|
633
633
|
);
|
|
634
634
|
return 1;
|
|
635
635
|
}
|
|
636
|
-
if (/(?:local )?seat store|(?:secure )?credential (?:store|storage)/i.test(message)) {
|
|
636
|
+
if (/(?:(?:local )?(?:private|seat) store|(?:secure )?credential (?:store|storage))/i.test(message)) {
|
|
637
637
|
deps.stderr(
|
|
638
|
-
`Borg could not access its
|
|
638
|
+
`Borg could not access its private store for ${apiUrl}. ` +
|
|
639
639
|
`Ensure its directory on this machine is readable and writable, then rerun ${retryCommand}.\n`,
|
|
640
640
|
);
|
|
641
641
|
return 1;
|
|
@@ -680,7 +680,7 @@ function reportServerFailure(
|
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
function resetLocalSeatCommand(apiUrl: string): string {
|
|
683
|
-
return `\`borg reset-local-
|
|
683
|
+
return `\`borg reset-local-connection --host ${apiUrl}\``;
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
// Pin-matched terminal session diagnosis. This is intentionally output-only:
|
|
@@ -699,7 +699,7 @@ function diagnoseSessionTermination(
|
|
|
699
699
|
: `borg assimilate --host ${apiUrl} --enroll`;
|
|
700
700
|
deps.stderr(
|
|
701
701
|
`${message}\n` +
|
|
702
|
-
`Next: run borg reset-local-
|
|
702
|
+
`Next: run borg reset-local-connection, then ${recovery}.\n`,
|
|
703
703
|
);
|
|
704
704
|
return 1;
|
|
705
705
|
}
|
|
@@ -1287,9 +1287,9 @@ export async function runAssimilate(
|
|
|
1287
1287
|
}
|
|
1288
1288
|
} else {
|
|
1289
1289
|
deps.stderr(
|
|
1290
|
-
`This worktree has saved
|
|
1291
|
-
'credential could not be loaded from the
|
|
1292
|
-
`${resetLocalSeatCommand(authority.apiUrl)} to clear this worktree's saved
|
|
1290
|
+
`This worktree has a saved connection to ${authority.apiUrl}, but its local session ` +
|
|
1291
|
+
'credential could not be loaded from the private store. No drone was created. Run ' +
|
|
1292
|
+
`${resetLocalSeatCommand(authority.apiUrl)} to clear this worktree's saved connection, then ` +
|
|
1293
1293
|
`ask the operator for a new invitation and rerun ${localAssimilateCommand(authority.apiUrl, true)}.\n`,
|
|
1294
1294
|
);
|
|
1295
1295
|
return 1;
|
|
@@ -1301,8 +1301,8 @@ export async function runAssimilate(
|
|
|
1301
1301
|
existing.serverTrustIdentity !== auth.serverTrustIdentity)
|
|
1302
1302
|
) {
|
|
1303
1303
|
deps.stderr(
|
|
1304
|
-
`This worktree's saved
|
|
1305
|
-
'No
|
|
1304
|
+
`This worktree's saved connection does not match ${authority.apiUrl}. ` +
|
|
1305
|
+
'No drone was created. Restore the expected server identity or use a fresh ' +
|
|
1306
1306
|
`worktree, then rerun ${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1307
1307
|
);
|
|
1308
1308
|
return 1;
|
|
@@ -1323,7 +1323,7 @@ export async function runAssimilate(
|
|
|
1323
1323
|
);
|
|
1324
1324
|
// Canonical rotated/revoked path: a pin-matched 401 on THIS worktree's
|
|
1325
1325
|
// saved bearer. PURE DIAGNOSIS — attach never mutates local state on a
|
|
1326
|
-
// rejection; it points at the offline `borg reset-local-
|
|
1326
|
+
// rejection; it points at the offline `borg reset-local-connection` command.
|
|
1327
1327
|
// Distinct from unreachable/404/5xx/trust-mismatch, which stay
|
|
1328
1328
|
// indeterminate below.
|
|
1329
1329
|
if (status === 'revoked') {
|
|
@@ -1338,7 +1338,7 @@ export async function runAssimilate(
|
|
|
1338
1338
|
// The saved SESSION bearer was rejected WITHOUT the typed takeover code
|
|
1339
1339
|
// (a bare/other 401). Non-destructive: re-enroll, never a seat reset.
|
|
1340
1340
|
deps.stderr(
|
|
1341
|
-
`The saved enrollment for ${authority.apiUrl} was rejected. No
|
|
1341
|
+
`The saved enrollment for ${authority.apiUrl} was rejected. No drone was created ` +
|
|
1342
1342
|
`and nothing was changed. Re-enroll with ${localAssimilateCommand(authority.apiUrl, true)} ` +
|
|
1343
1343
|
'from the operator’s terminal.\n',
|
|
1344
1344
|
);
|
|
@@ -1349,7 +1349,7 @@ export async function runAssimilate(
|
|
|
1349
1349
|
// fix it — verify this is the expected server / re-initialization.
|
|
1350
1350
|
deps.stderr(
|
|
1351
1351
|
`Borg could not verify the expected server identity for ${authority.apiUrl}. ` +
|
|
1352
|
-
'No
|
|
1352
|
+
'No drone was created. Verify that this is the expected server; if it was ' +
|
|
1353
1353
|
're-initialized, restore the expected identity, then rerun ' +
|
|
1354
1354
|
`${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1355
1355
|
);
|
|
@@ -1361,7 +1361,7 @@ export async function runAssimilate(
|
|
|
1361
1361
|
// fix it; align versions. Non-destructive: no seat created, nothing reset.
|
|
1362
1362
|
deps.stderr(
|
|
1363
1363
|
`Borg reached ${authority.apiUrl} but it did not recognize this worktree's drone ` +
|
|
1364
|
-
'endpoint — the client and server versions are likely incompatible. No
|
|
1364
|
+
'endpoint — the client and server versions are likely incompatible. No drone ' +
|
|
1365
1365
|
'was created and nothing was changed. Update the Borg client and/or server so ' +
|
|
1366
1366
|
`their versions match, then rerun ${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1367
1367
|
);
|
|
@@ -1372,7 +1372,7 @@ export async function runAssimilate(
|
|
|
1372
1372
|
// check the server, then retry. Non-destructive.
|
|
1373
1373
|
deps.stderr(
|
|
1374
1374
|
`Borg reached ${authority.apiUrl} but it returned a server error while verifying ` +
|
|
1375
|
-
"this worktree's saved
|
|
1375
|
+
"this worktree's saved connection. No drone was created. Check the server (its logs / " +
|
|
1376
1376
|
`\`borg-mcp-server start\`), then rerun ${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1377
1377
|
);
|
|
1378
1378
|
return 1;
|
|
@@ -1381,16 +1381,16 @@ export async function runAssimilate(
|
|
|
1381
1381
|
// CR5: transport failure / timeout (unreachable) or a genuinely ambiguous
|
|
1382
1382
|
// failure (indeterminate) — both transient. Start or restart the server.
|
|
1383
1383
|
deps.stderr(
|
|
1384
|
-
`Borg could not verify this worktree's saved
|
|
1385
|
-
'No
|
|
1384
|
+
`Borg could not verify this worktree's saved connection to ${authority.apiUrl}. ` +
|
|
1385
|
+
'No drone was created. Start or restart the server with ' +
|
|
1386
1386
|
`\`borg-mcp-server start\`, then rerun ${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1387
1387
|
);
|
|
1388
1388
|
return 1;
|
|
1389
1389
|
}
|
|
1390
1390
|
if (status === 'live' && !savedLocalRole) {
|
|
1391
1391
|
deps.stderr(
|
|
1392
|
-
`Borg verified this worktree's saved
|
|
1393
|
-
'role is unavailable. No
|
|
1392
|
+
`Borg verified this worktree's saved connection to ${authority.apiUrl}, but its saved ` +
|
|
1393
|
+
'role is unavailable. No drone was created. Ask the server operator to restore ' +
|
|
1394
1394
|
`the role, then rerun ${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1395
1395
|
);
|
|
1396
1396
|
return 1;
|
|
@@ -1401,8 +1401,8 @@ export async function runAssimilate(
|
|
|
1401
1401
|
} else if (existing && args.flags.here) {
|
|
1402
1402
|
if (existing.serverTrustIdentity !== undefined || existing.apiUrl !== auth.apiUrl) {
|
|
1403
1403
|
deps.stderr(
|
|
1404
|
-
'This worktree\'s saved
|
|
1405
|
-
'No
|
|
1404
|
+
'This worktree\'s saved connection belongs to a different Borg authority. ' +
|
|
1405
|
+
'No drone was created; use a fresh worktree.\n',
|
|
1406
1406
|
);
|
|
1407
1407
|
return 1;
|
|
1408
1408
|
}
|
|
@@ -1583,7 +1583,7 @@ export async function runAssimilate(
|
|
|
1583
1583
|
// a non-reattach DroneEvictedError falls through to the generic message.
|
|
1584
1584
|
if (err instanceof DroneEvictedError && reattachPriorId != null) {
|
|
1585
1585
|
deps.stderr(
|
|
1586
|
-
`This worktree's
|
|
1586
|
+
`This worktree's drone on ${authority.apiUrl} was evicted. ` +
|
|
1587
1587
|
`Remove this worktree, or from a fresh worktree run ` +
|
|
1588
1588
|
`${localAssimilateCommand(authority.apiUrl)}.\n`,
|
|
1589
1589
|
);
|
|
@@ -1593,7 +1593,7 @@ export async function runAssimilate(
|
|
|
1593
1593
|
// Reached only after a successful pinned-TLS attach, so it is pin-matched by
|
|
1594
1594
|
// construction — a pin mismatch throws a distinct trust error and never
|
|
1595
1595
|
// enters this branch. Attach mutates NOTHING; it recommends the offline
|
|
1596
|
-
// `borg reset-local-
|
|
1596
|
+
// `borg reset-local-connection` command.
|
|
1597
1597
|
if (err instanceof BorgServerError && reattachPriorId != null) {
|
|
1598
1598
|
if (err.code === 'SESSION_REVOKED') {
|
|
1599
1599
|
return diagnoseSessionTermination(deps, authority.apiUrl, 'revoked');
|
|
@@ -1616,7 +1616,7 @@ export async function runAssimilate(
|
|
|
1616
1616
|
// concurrent offline reset, or a competing enroll). No FS/network mutation
|
|
1617
1617
|
// happened; never silently recreate.
|
|
1618
1618
|
deps.stderr(
|
|
1619
|
-
`This worktree's saved
|
|
1619
|
+
`This worktree's saved connection to ${authority.apiUrl} changed before the attach ` +
|
|
1620
1620
|
'(a concurrent reset or enroll); no credential was created or sent and nothing was ' +
|
|
1621
1621
|
`changed. Re-run ${localAssimilateCommand(authority.apiUrl)} to attach against the ` +
|
|
1622
1622
|
'current state.\n',
|
|
@@ -1641,16 +1641,16 @@ export async function runAssimilate(
|
|
|
1641
1641
|
const assignedRole =
|
|
1642
1642
|
cubeDetail.roles.find((r) => r.id === result.role_id) ?? resolvedRole;
|
|
1643
1643
|
if (result.result === 'reused') {
|
|
1644
|
-
// The
|
|
1644
|
+
// The drone's existing role is authoritative on an idempotent reattach —
|
|
1645
1645
|
// a role difference is expected, not a grant fallback. The bearer is
|
|
1646
1646
|
// reused, not rotated: no new drone minted.
|
|
1647
1647
|
deps.stderr(
|
|
1648
|
-
`re-attached
|
|
1648
|
+
`re-attached as ${result.drone_label} (same session, no new drone minted)\n`
|
|
1649
1649
|
);
|
|
1650
1650
|
} else if (assignedRole.id !== resolvedRole.id) {
|
|
1651
1651
|
deps.stderr(
|
|
1652
1652
|
`The requested role "${resolvedRole.name}" was unavailable; ` +
|
|
1653
|
-
`attached
|
|
1653
|
+
`attached under the "${assignedRole.name}" role instead.\n`
|
|
1654
1654
|
);
|
|
1655
1655
|
}
|
|
1656
1656
|
|
|
@@ -1814,7 +1814,7 @@ export async function runAssimilate(
|
|
|
1814
1814
|
}
|
|
1815
1815
|
deps.stderr(
|
|
1816
1816
|
`spawned sibling worktree at ${candidate} on branch ${wtBranch} (${startRef}); ` +
|
|
1817
|
-
`the original dir keeps its active drone binding — run \`borg reset-local-
|
|
1817
|
+
`the original dir keeps its active drone binding — run \`borg reset-local-connection\` there if that binding is stale.\n`
|
|
1818
1818
|
);
|
|
1819
1819
|
deps.chdir(candidate);
|
|
1820
1820
|
deps.stderr(renderWorktreeSteeringNote(candidate, wtBranch, projectRoot));
|
|
@@ -1903,7 +1903,7 @@ export async function runAssimilate(
|
|
|
1903
1903
|
// EXACT prior binding with its live-bearer digest; eviction remint = EXACT ref
|
|
1904
1904
|
// only, bearer intentionally replaced; fresh/sibling = ABSENT).
|
|
1905
1905
|
if (result.finalize === undefined || deps.finalizeServerSeat === undefined) {
|
|
1906
|
-
deps.stderr('Local Borg server session metadata is incomplete; no
|
|
1906
|
+
deps.stderr('Local Borg server session metadata is incomplete; no connection was saved.\n');
|
|
1907
1907
|
rollbackWorktree();
|
|
1908
1908
|
return 1;
|
|
1909
1909
|
}
|
|
@@ -1959,12 +1959,12 @@ export async function runAssimilate(
|
|
|
1959
1959
|
if (bindOutcome === 'bound') {
|
|
1960
1960
|
// The worktree now owns a durable locator (the bound-pending record points
|
|
1961
1961
|
// here). PRESERVE it. Truthful convergence copy: a rerun FROM here re-sends
|
|
1962
|
-
// the identical bearer (no duplicate), and `reset-local-
|
|
1962
|
+
// the identical bearer (no duplicate), and `reset-local-connection` from here now
|
|
1963
1963
|
// discovers + clears the bound-pending record.
|
|
1964
1964
|
deps.stderr(
|
|
1965
1965
|
`This worktree's secure session on ${auth.apiUrl} did not finish activating, but ` +
|
|
1966
|
-
'its resumable
|
|
1967
|
-
`here, re-run ${localAssimilateCommand(auth.apiUrl)} to converge (the identical
|
|
1966
|
+
'its resumable connection state was PRESERVED here. This worktree was NOT removed. From ' +
|
|
1967
|
+
`here, re-run ${localAssimilateCommand(auth.apiUrl)} to converge (the identical connection ` +
|
|
1968
1968
|
`is reused — no duplicate is minted), or run ${resetLocalSeatCommand(auth.apiUrl)} to ` +
|
|
1969
1969
|
'clear it.\n',
|
|
1970
1970
|
);
|
|
@@ -1977,17 +1977,17 @@ export async function runAssimilate(
|
|
|
1977
1977
|
// that can silently create a duplicate server seat (#35).
|
|
1978
1978
|
const bindFailure =
|
|
1979
1979
|
bindOutcome === 'missing'
|
|
1980
|
-
? 'the exact pending
|
|
1980
|
+
? 'the exact pending connection record went missing locally before it could be bound'
|
|
1981
1981
|
: bindOutcome === 'replaced'
|
|
1982
|
-
? 'the exact pending
|
|
1982
|
+
? 'the exact pending connection record was replaced locally before it could be bound; the replacement was left untouched'
|
|
1983
1983
|
: bindOutcome === 'threw'
|
|
1984
|
-
? 'the
|
|
1985
|
-
: 'this client did not receive a pending-
|
|
1984
|
+
? 'the private store could not be read or written while preserving the pending connection'
|
|
1985
|
+
: 'this client did not receive a pending-connection preservation handle';
|
|
1986
1986
|
deps.stderr(
|
|
1987
1987
|
`This worktree's secure session on ${auth.apiUrl} did not finish activating: ` +
|
|
1988
1988
|
`${bindFailure}. The spawned worktree will be removed. No client-only command can ` +
|
|
1989
|
-
'prove reuse or safely clear the possibly accepted server-side
|
|
1990
|
-
'operator to inspect that
|
|
1989
|
+
'prove reuse or safely clear the possibly accepted server-side drone; ask the server ' +
|
|
1990
|
+
'operator to inspect that drone before retrying.\n',
|
|
1991
1991
|
);
|
|
1992
1992
|
rollbackWorktree();
|
|
1993
1993
|
return 1;
|
|
@@ -1997,8 +1997,8 @@ export async function runAssimilate(
|
|
|
1997
1997
|
// reset or enroll). The composite scrubbed only our own pending record — no
|
|
1998
1998
|
// orphan ACTIVE credential — so a just-spawned worktree is safe to remove.
|
|
1999
1999
|
deps.stderr(
|
|
2000
|
-
`This worktree's saved
|
|
2001
|
-
'(a concurrent reset or enroll); no
|
|
2000
|
+
`This worktree's saved connection to ${auth.apiUrl} changed during attach ` +
|
|
2001
|
+
'(a concurrent reset or enroll); no drone was created and nothing was overwritten. ' +
|
|
2002
2002
|
`Re-run ${localAssimilateCommand(auth.apiUrl)} to attach against the current state.\n`,
|
|
2003
2003
|
);
|
|
2004
2004
|
rollbackWorktree();
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -474,13 +474,13 @@ export function buildDefaultAssimilateDeps(
|
|
|
474
474
|
if (params.remint_invalid_prior) {
|
|
475
475
|
if (prepared.result !== 'created' || prepared.drone.id === params.prior_drone_id) {
|
|
476
476
|
await prepared.scrubPending();
|
|
477
|
-
throw new Error('Borg server did not remint a fresh
|
|
477
|
+
throw new Error('Borg server did not remint a fresh drone after eviction');
|
|
478
478
|
}
|
|
479
479
|
} else if (prepared.result !== 'reused' || prepared.drone.id !== params.prior_drone_id) {
|
|
480
480
|
await prepared.scrubPending();
|
|
481
481
|
throw new BorgServerError(
|
|
482
482
|
'ATTACH_CONFLICT',
|
|
483
|
-
'Borg server did not reattach the saved
|
|
483
|
+
'Borg server did not reattach the saved connection',
|
|
484
484
|
);
|
|
485
485
|
}
|
|
486
486
|
}
|
package/src/assimilate-guard.ts
CHANGED
|
@@ -48,8 +48,8 @@ export function reattachOnlyRefusal(
|
|
|
48
48
|
): string {
|
|
49
49
|
if (decision.kind === 'no-identity') {
|
|
50
50
|
return (
|
|
51
|
-
`◼ This session has no drone
|
|
52
|
-
`re-attach-only (it never creates
|
|
51
|
+
`◼ This session has no drone for this worktree, and in-session borg_assimilate is ` +
|
|
52
|
+
`re-attach-only (it never creates drones — gh#780). To create a drone for cube ` +
|
|
53
53
|
`"${requestedCubeName}", run \`borg assimilate\` in a terminal — it spawns the worktree, ` +
|
|
54
54
|
`persists the identity, and launches the agent in one step.`
|
|
55
55
|
);
|
|
@@ -76,9 +76,9 @@ export function reattachFailureMessage(error: {
|
|
|
76
76
|
}): string {
|
|
77
77
|
const detail = error.message ?? String(error);
|
|
78
78
|
return (
|
|
79
|
-
`◼ Re-attach failed — this worktree's saved
|
|
79
|
+
`◼ Re-attach failed — this worktree's saved connection is unreachable (likely evicted or its ` +
|
|
80
80
|
`session was revoked). Server said: ${detail}\n` +
|
|
81
|
-
`Recover by running \`borg assimilate\` in a terminal to create a fresh
|
|
81
|
+
`Recover by running \`borg assimilate\` in a terminal to create a fresh drone; ` +
|
|
82
82
|
`in-session borg_assimilate never re-mints (gh#780).`
|
|
83
83
|
);
|
|
84
84
|
}
|
|
@@ -31,8 +31,8 @@ export function renderAssimilationWelcome(
|
|
|
31
31
|
return [
|
|
32
32
|
`${check} Attached \`${droneLabel}\` to \`${roleName}\` in cube \`${cubeName}\`.`,
|
|
33
33
|
``,
|
|
34
|
-
`In the launched agent, run \`borg_whoami\` and \`borg_roster
|
|
35
|
-
`and begin coordinating.`,
|
|
34
|
+
`In the launched agent, run \`borg_whoami\` and \`borg_roster\``,
|
|
35
|
+
`to verify the connection and begin coordinating.`,
|
|
36
36
|
`Launch future sessions in this repository with \`borg\`.`,
|
|
37
37
|
...teammateLines,
|
|
38
38
|
``,
|
package/src/bare-launch-menu.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* When `borg` is run with NO arguments in a TTY, offer a small launch selector
|
|
5
5
|
* instead of launching immediately:
|
|
6
6
|
* 1. Launch (default) — the configured agent (Enter selects).
|
|
7
|
-
* 2. Launch with <other> instead — the OTHER
|
|
7
|
+
* 2. Launch with <other> instead — the OTHER configured agent, ONE-SHOT
|
|
8
8
|
* (does NOT persist the preference).
|
|
9
9
|
* 3. Launch all — runLaunchAll for the active cube.
|
|
10
10
|
*
|
|
11
11
|
* The option-set, the selection→action mapping, and the show/collapse decision
|
|
12
12
|
* are pure functions so they're unit-testable without a real TTY. claude.ts
|
|
13
|
-
* main() is thin glue: it computes the inputs (default cli, other-
|
|
13
|
+
* main() is thin glue: it computes the inputs (default cli, other-configured cli,
|
|
14
14
|
* launch-all targets), gates on shouldShowLaunchMenu, runs the orchestrator with
|
|
15
15
|
* the real readline prompt, then dispatches the returned action.
|
|
16
16
|
*
|
|
@@ -33,8 +33,8 @@ export interface LaunchMenuOption {
|
|
|
33
33
|
export interface LaunchMenuInputs {
|
|
34
34
|
/** The configured/resolved current agent (option 1). */
|
|
35
35
|
defaultCli: BorgCli;
|
|
36
|
-
/** All
|
|
37
|
-
|
|
36
|
+
/** All configured agents that are NOT the default, in display order. */
|
|
37
|
+
otherConfiguredClis: BorgCli[];
|
|
38
38
|
/** True iff there's an active cube with >=1 discoverable drone (option 3). */
|
|
39
39
|
hasLaunchAllTargets: boolean;
|
|
40
40
|
}
|
|
@@ -44,7 +44,7 @@ export interface LaunchMenuInputs {
|
|
|
44
44
|
*
|
|
45
45
|
* This callback deliberately runs after the one-shot menu choice. A bare
|
|
46
46
|
* launch may resolve one default CLI first and then launch a different
|
|
47
|
-
*
|
|
47
|
+
* configured CLI for this invocation; only the final CLI may be self-healed.
|
|
48
48
|
*/
|
|
49
49
|
export function configureSelectedLaunchCli(
|
|
50
50
|
defaultCli: BorgCli,
|
|
@@ -105,7 +105,7 @@ export function buildLaunchMenuOptions(inputs: LaunchMenuInputs): LaunchMenuOpti
|
|
|
105
105
|
action: { kind: 'launch', cli: inputs.defaultCli },
|
|
106
106
|
},
|
|
107
107
|
];
|
|
108
|
-
for (const cli of inputs.
|
|
108
|
+
for (const cli of inputs.otherConfiguredClis) {
|
|
109
109
|
options.push({
|
|
110
110
|
key: String(options.length + 1),
|
|
111
111
|
label: `Launch with ${PRETTY[cli]} (one-shot)`,
|
package/src/claude.ts
CHANGED
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
* borg setup → Re-route to the setup wizard
|
|
13
13
|
* borg spawn <name> → Create a sibling git worktree + launch a
|
|
14
14
|
* fresh drone inside it (see spawn.ts)
|
|
15
|
-
* borg sync → Advance the current worktree across the 5
|
|
16
|
-
* lifecycle states (see sync.ts, gh#33)
|
|
17
15
|
* borg server <cmd> → Forward a lifecycle command to borg-mcp-server
|
|
18
16
|
*/
|
|
19
17
|
|
|
@@ -31,7 +29,6 @@ import { handleVersionFlag, getPackageVersion } from './version.js';
|
|
|
31
29
|
import { clientSubcommandHelpText, topLevelHelpText } from './cli-help.js';
|
|
32
30
|
import { runSpawn } from './spawn.js';
|
|
33
31
|
import { buildClaudeLaunchArgs } from './claude-launch-args.js';
|
|
34
|
-
import { parseSyncArgs, runSync } from './sync.js';
|
|
35
32
|
import { parseCleanupArgs, runCleanup } from './cleanup-cmd.js';
|
|
36
33
|
import { parseAssimilateArgs } from './parse-assimilate-args.js';
|
|
37
34
|
import { runAssimilate } from './assimilate-cmd.js';
|
|
@@ -58,7 +55,7 @@ import {
|
|
|
58
55
|
import { setTerminalTitle } from './terminal-title.js';
|
|
59
56
|
import { initConsolePrefix, consolePrefix } from './console-prefix.js';
|
|
60
57
|
import { initDebugFromArgv } from './debug.js';
|
|
61
|
-
import { defaultCliChoiceDeps, detectCliAvailability,
|
|
58
|
+
import { configuredCliNames, defaultCliChoiceDeps, detectCliAvailability, detectCliConfiguration, parseCliFlag, resolveCliChoice } from './cli-platform.js';
|
|
62
59
|
import { prepareCodexRemoteLaunch, resolveCodexLaunchCwd, withCodexCwdArg, defaultCodexRemoteDeps, checkCodexBridgeHealthy } from './codex-remote.js';
|
|
63
60
|
import {
|
|
64
61
|
BORG_CODEX_REMOTE_WAKE_ENV,
|
|
@@ -216,11 +213,11 @@ async function main() {
|
|
|
216
213
|
const code = await runAssimilateEntry(process.argv.slice(3));
|
|
217
214
|
process.exit(code);
|
|
218
215
|
}
|
|
219
|
-
if (process.argv[2] === 'reset-local-
|
|
216
|
+
if (process.argv[2] === 'reset-local-connection') {
|
|
220
217
|
const parsed = parseResetLocalSeatArgs(process.argv.slice(3));
|
|
221
218
|
if (!parsed.ok) {
|
|
222
219
|
process.stderr.write(
|
|
223
|
-
chalk.red(`${consolePrefix()}◼ borg reset-local-
|
|
220
|
+
chalk.red(`${consolePrefix()}◼ borg reset-local-connection: ${parsed.error}\n`)
|
|
224
221
|
);
|
|
225
222
|
process.stderr.write(`Run \`borg --help\` for usage.\n`);
|
|
226
223
|
process.exit(1);
|
|
@@ -254,18 +251,6 @@ async function main() {
|
|
|
254
251
|
const code = await runSpawn();
|
|
255
252
|
process.exit(code);
|
|
256
253
|
}
|
|
257
|
-
if (process.argv[2] === 'sync') {
|
|
258
|
-
const parsed = parseSyncArgs(process.argv.slice(3));
|
|
259
|
-
if (!parsed.ok) {
|
|
260
|
-
process.stderr.write(
|
|
261
|
-
chalk.red(`${consolePrefix()}◼ borg sync: ${parsed.error}\n`)
|
|
262
|
-
);
|
|
263
|
-
process.stderr.write(`Run \`borg --help\` for usage.\n`);
|
|
264
|
-
process.exit(1);
|
|
265
|
-
}
|
|
266
|
-
const code = await runSync({}, parsed.options);
|
|
267
|
-
process.exit(code);
|
|
268
|
-
}
|
|
269
254
|
if (process.argv[2] === 'cleanup') {
|
|
270
255
|
const parsed = parseCleanupArgs(process.argv.slice(3));
|
|
271
256
|
if (!parsed.ok) {
|
|
@@ -372,10 +357,12 @@ async function main() {
|
|
|
372
357
|
stdoutIsTTY,
|
|
373
358
|
})
|
|
374
359
|
) {
|
|
375
|
-
const
|
|
376
|
-
|
|
360
|
+
const otherConfiguredClis = configuredCliNames(
|
|
361
|
+
detectCliAvailability(),
|
|
362
|
+
detectCliConfiguration(),
|
|
363
|
+
).filter((c) => c !== cli);
|
|
377
364
|
const action = await runBareLaunchMenu(
|
|
378
|
-
{ defaultCli: cli,
|
|
365
|
+
{ defaultCli: cli, otherConfiguredClis, hasLaunchAllTargets: await hasLaunchAllTargets() },
|
|
379
366
|
prompt
|
|
380
367
|
);
|
|
381
368
|
if (action.kind === 'launch-all') {
|
package/src/cleanup-cmd.ts
CHANGED
|
@@ -74,7 +74,7 @@ export type CleanupReason =
|
|
|
74
74
|
| 'SURVIVES-live'
|
|
75
75
|
| 'SURVIVES-self'
|
|
76
76
|
| 'UNKNOWN-indeterminate'
|
|
77
|
-
| 'UNKNOWN-no-
|
|
77
|
+
| 'UNKNOWN-no-connection'
|
|
78
78
|
| 'LEGACY-manual-review';
|
|
79
79
|
|
|
80
80
|
export interface CleanupRow {
|
|
@@ -350,7 +350,7 @@ async function classifyWorktree(
|
|
|
350
350
|
case 'revoked':
|
|
351
351
|
return { reason: 'SURVIVES-revoked', detail: 'session revoked — recoverable via re-enroll, never delete' };
|
|
352
352
|
case 'live':
|
|
353
|
-
return { reason: 'SURVIVES-live', detail: '
|
|
353
|
+
return { reason: 'SURVIVES-live', detail: 'connection resolves (drone alive)' };
|
|
354
354
|
case 'indeterminate':
|
|
355
355
|
default:
|
|
356
356
|
return {
|
|
@@ -431,7 +431,7 @@ export async function buildCleanupReport(
|
|
|
431
431
|
// sibling → REPORT for manual review, never auto-prune (S5). No seat →
|
|
432
432
|
// an arbitrary user worktree we never touch or list.
|
|
433
433
|
if (seat) {
|
|
434
|
-
rows.push({ worktreePath: wt, wtBranch: null, reason: 'LEGACY-manual-review', detail: 'borg
|
|
434
|
+
rows.push({ worktreePath: wt, wtBranch: null, reason: 'LEGACY-manual-review', detail: 'borg connection outside worktreesHome (pre-gh#556 sibling)' });
|
|
435
435
|
}
|
|
436
436
|
continue;
|
|
437
437
|
}
|
|
@@ -439,7 +439,7 @@ export async function buildCleanupReport(
|
|
|
439
439
|
// Under worktreesHome but no saved seat → not eviction-orphaned
|
|
440
440
|
// (pre-assimilate or hand-made). Report, never auto-prune.
|
|
441
441
|
if (!seat) {
|
|
442
|
-
rows.push({ worktreePath: wt, wtBranch: null, reason: 'UNKNOWN-no-
|
|
442
|
+
rows.push({ worktreePath: wt, wtBranch: null, reason: 'UNKNOWN-no-connection', detail: 'no saved connection — manual review' });
|
|
443
443
|
continue;
|
|
444
444
|
}
|
|
445
445
|
|