borgmcp 2.16.0 → 3.0.0

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.
Files changed (115) hide show
  1. package/README.md +38 -12
  2. package/dist/assimilate-cmd.js +43 -43
  3. package/dist/assimilate-cmd.js.map +1 -1
  4. package/dist/assimilate-deps.js +2 -2
  5. package/dist/assimilate-deps.js.map +1 -1
  6. package/dist/assimilate-guard.js +4 -4
  7. package/dist/assimilate-guard.js.map +1 -1
  8. package/dist/assimilate-welcome.js +2 -2
  9. package/dist/assimilate-welcome.js.map +1 -1
  10. package/dist/claude.d.ts +0 -2
  11. package/dist/claude.d.ts.map +1 -1
  12. package/dist/claude.js +2 -15
  13. package/dist/claude.js.map +1 -1
  14. package/dist/cleanup-cmd.d.ts +1 -1
  15. package/dist/cleanup-cmd.d.ts.map +1 -1
  16. package/dist/cleanup-cmd.js +3 -3
  17. package/dist/cleanup-cmd.js.map +1 -1
  18. package/dist/cli-help.d.ts +1 -2
  19. package/dist/cli-help.d.ts.map +1 -1
  20. package/dist/cli-help.js +15 -24
  21. package/dist/cli-help.js.map +1 -1
  22. package/dist/cubes.js +2 -2
  23. package/dist/cubes.js.map +1 -1
  24. package/dist/docs-sections.js +1 -1
  25. package/dist/docs-sections.js.map +1 -1
  26. package/dist/drone-lifecycle.d.ts.map +1 -1
  27. package/dist/drone-lifecycle.js +3 -3
  28. package/dist/drone-lifecycle.js.map +1 -1
  29. package/dist/drone-management.js +1 -1
  30. package/dist/drone-management.js.map +1 -1
  31. package/dist/evict-drone.js +2 -2
  32. package/dist/evict-drone.js.map +1 -1
  33. package/dist/foreign-path-reminder.js +1 -1
  34. package/dist/foreign-path-reminder.js.map +1 -1
  35. package/dist/inbox-monitor.js +2 -2
  36. package/dist/inbox-monitor.js.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/launch-all-cmd.js +12 -12
  40. package/dist/launch-all-cmd.js.map +1 -1
  41. package/dist/launch-all-discovery.js +1 -1
  42. package/dist/launch-all-discovery.js.map +1 -1
  43. package/dist/opencode-seat-identity.js +5 -5
  44. package/dist/opencode-seat-identity.js.map +1 -1
  45. package/dist/regen-format.d.ts.map +1 -1
  46. package/dist/regen-format.js +8 -1
  47. package/dist/regen-format.js.map +1 -1
  48. package/dist/remote-client.js +1 -1
  49. package/dist/remote-client.js.map +1 -1
  50. package/dist/repository-cube-init.js +1 -1
  51. package/dist/repository-cube-init.js.map +1 -1
  52. package/dist/reset-local-seat-cmd.d.ts +2 -2
  53. package/dist/reset-local-seat-cmd.d.ts.map +1 -1
  54. package/dist/reset-local-seat-cmd.js +17 -17
  55. package/dist/reset-local-seat-cmd.js.map +1 -1
  56. package/dist/seat-reattach-guard.js +1 -1
  57. package/dist/seat-reattach-guard.js.map +1 -1
  58. package/dist/seat-store.d.ts +1 -1
  59. package/dist/seat-store.js +6 -6
  60. package/dist/seat-store.js.map +1 -1
  61. package/dist/seats.js +1 -1
  62. package/dist/seats.js.map +1 -1
  63. package/dist/server-handshake.js +1 -1
  64. package/dist/server-handshake.js.map +1 -1
  65. package/dist/stream-status.js +3 -3
  66. package/dist/stream-status.js.map +1 -1
  67. package/dist/tool-manifest.js +1 -1
  68. package/dist/tool-manifest.js.map +1 -1
  69. package/dist/unknown-subcommand.d.ts +1 -1
  70. package/dist/unknown-subcommand.d.ts.map +1 -1
  71. package/dist/unknown-subcommand.js +1 -2
  72. package/dist/unknown-subcommand.js.map +1 -1
  73. package/dist/worktree-lifecycle.d.ts +0 -16
  74. package/dist/worktree-lifecycle.d.ts.map +1 -1
  75. package/dist/worktree-lifecycle.js +0 -38
  76. package/dist/worktree-lifecycle.js.map +1 -1
  77. package/docs/EXTRACTION_PROVENANCE.md +23 -10
  78. package/docs/LOCAL_SERVER.md +2 -2
  79. package/docs/SEAT_LIFECYCLE.md +19 -19
  80. package/package.json +1 -1
  81. package/src/assimilate-cmd.ts +43 -43
  82. package/src/assimilate-deps.ts +2 -2
  83. package/src/assimilate-guard.ts +4 -4
  84. package/src/assimilate-welcome.ts +2 -2
  85. package/src/claude.ts +2 -17
  86. package/src/cleanup-cmd.ts +4 -4
  87. package/src/cli-help.ts +15 -27
  88. package/src/cubes.ts +2 -2
  89. package/src/docs-sections.ts +1 -1
  90. package/src/drone-lifecycle.ts +3 -3
  91. package/src/drone-management.ts +1 -1
  92. package/src/evict-drone.ts +2 -2
  93. package/src/foreign-path-reminder.ts +1 -1
  94. package/src/inbox-monitor.ts +2 -2
  95. package/src/index.ts +1 -1
  96. package/src/launch-all-cmd.ts +12 -12
  97. package/src/launch-all-discovery.ts +1 -1
  98. package/src/opencode-seat-identity.ts +5 -5
  99. package/src/regen-format.ts +8 -1
  100. package/src/remote-client.ts +1 -1
  101. package/src/repository-cube-init.ts +1 -1
  102. package/src/reset-local-seat-cmd.ts +17 -17
  103. package/src/seat-reattach-guard.ts +1 -1
  104. package/src/seat-store.ts +6 -6
  105. package/src/seats.ts +1 -1
  106. package/src/server-handshake.ts +1 -1
  107. package/src/stream-status.ts +3 -3
  108. package/src/tool-manifest.ts +1 -1
  109. package/src/unknown-subcommand.ts +1 -2
  110. package/src/worktree-lifecycle.ts +0 -45
  111. package/dist/sync.d.ts +0 -129
  112. package/dist/sync.d.ts.map +0 -1
  113. package/dist/sync.js +0 -286
  114. package/dist/sync.js.map +0 -1
  115. package/src/sync.ts +0 -363
package/src/cubes.ts CHANGED
@@ -110,7 +110,7 @@ let pinnedMcpSeatIdentity: {
110
110
  export class McpSeatIdentityChangedError extends Error {
111
111
  readonly code = 'SEAT_IDENTITY_CHANGED';
112
112
  constructor() {
113
- super('The saved Borg seat changed after this MCP session pinned its identity. Exit this session and relaunch from the intended worktree.');
113
+ super('This worktree\'s saved connection changed after this MCP session pinned its identity. Exit this session and relaunch from the intended worktree.');
114
114
  this.name = 'McpSeatIdentityChangedError';
115
115
  }
116
116
  }
@@ -439,7 +439,7 @@ export async function snapshotLocalSeat(): Promise<LocalSeatSnapshot | null> {
439
439
  // CR#4: discover an ACTIVE seat OR a bound-PENDING record (a sibling whose
440
440
  // activation failed, bound to THIS worktree by the attach bind-pending step).
441
441
  // getActiveSeatForWorktree would MISS the bound-pending record (it requires
442
- // state==='active' + a drone id), so `reset-local-seat` would FALSELY report
442
+ // state==='active' + a drone id), so `reset-local-connection` would FALSELY report
443
443
  // "nothing to reset" (exit 0) while a resumable, server-digest-bound bearer
444
444
  // persists at rest — a FALSE-SUCCESS revocation failure. getSeatForWorktree sees
445
445
  // both, and the offline reset's exact re-check + delete cover the bound-pending
@@ -78,7 +78,7 @@ export const DOCS_SECTIONS: DocsSection[] = [
78
78
  url: SEAT_LIFECYCLE_URL,
79
79
  page: "docs/SEAT_LIFECYCLE.md",
80
80
  summary: "Saved-seat states, re-attach and reset recovery, duplicate-session guards, and deterministic multi-seat selection.",
81
- keywords: ["seat", "lifecycle", "reattach", "re-attach", "reset-local-seat", "evicted", "revoked", "superseded", "inbox monitor", "multiple seats", "silent deafness"],
81
+ keywords: ["seat", "lifecycle", "reattach", "re-attach", "reset-local-connection", "evicted", "revoked", "superseded", "inbox monitor", "multiple seats", "silent deafness"],
82
82
  },
83
83
  {
84
84
  slug: "self-hosting",
@@ -16,7 +16,7 @@ export const CUBE_DELETED_CODE = 'CUBE_DELETED';
16
16
 
17
17
  export class DroneEvictedError extends Error {
18
18
  constructor(
19
- message = 'This seat was removed from the cube.'
19
+ message = 'This drone was removed from the cube.'
20
20
  ) {
21
21
  super(message);
22
22
  this.name = 'DroneEvictedError';
@@ -45,8 +45,8 @@ export const CUBE_DELETED_RESULT_MARKER = '[CUBE-DELETED]';
45
45
  export function formatEvictedToolResult(cubeName?: string): string {
46
46
  const cube = cubeName ?? 'the selected cube';
47
47
  return (
48
- `${EVICTED_RESULT_MARKER} This seat was removed from cube ${cube}.\n\n` +
49
- 'Borg has stopped listening for activity for this seat. Do not retry this request or restart the loop.\n\n' +
48
+ `${EVICTED_RESULT_MARKER} This drone was removed from cube ${cube}.\n\n` +
49
+ 'Borg has stopped listening for activity for this drone. Do not retry this request or restart the loop.\n\n' +
50
50
  'Your worktree and project files are unchanged. Finish any local file safety checks, then end this agent session.\n\n' +
51
51
  'To rejoin later, start a new session and use a new invitation from the server operator. Do not re-assimilate from this evicted session.'
52
52
  );
@@ -50,7 +50,7 @@ const defaultDeps: DroneManagementDeps = {
50
50
  };
51
51
 
52
52
  export const STALE_ROLE_DISPLAY_WARNING =
53
- 'Local display warning: The server committed this change, but Borg could not refresh this worktree\'s saved seat metadata. Local role details may be stale. Do not retry the reassignment.\n\n' +
53
+ 'Local display warning: The server committed this change, but Borg could not refresh this worktree\'s saved connection metadata. Local role details may be stale. Do not retry the reassignment.\n\n' +
54
54
  'Run `borg_regen` to refresh the server-authoritative role. If this session still shows the previous role, restart this agent session once. Do not re-assimilate or repeat the management request.';
55
55
 
56
56
  function opaqueNotFound(): never {
@@ -75,8 +75,8 @@ export function formatReassignDroneSuccess(input: {
75
75
  export function formatEvictDroneSuccess(droneLabel: string, cubeName: string): string {
76
76
  return (
77
77
  `Removed ${droneLabel} from cube ${cubeName}.\n` +
78
- 'The seat credential is revoked. The session will stop after its next Borg request.\n' +
79
- 'The worktree and project files were not deleted. Activity history remains attributed to the removed seat.\n' +
78
+ 'The drone\'s credential is revoked. The session will stop after its next Borg request.\n' +
79
+ 'The worktree and project files were not deleted. Activity history remains attributed to the removed drone.\n' +
80
80
  'After its work is merged, run `borg cleanup` to review whether the worktree can be pruned.'
81
81
  );
82
82
  }
@@ -17,7 +17,7 @@ import {
17
17
  BORG_LAUNCH_WORKTREE_ENV,
18
18
  } from './launch-access.js';
19
19
 
20
- const REMINDER = 'Reminder: this seat is scoped to its own worktree and scratch root; coordinate before working on a foreign path.';
20
+ const REMINDER = 'Reminder: this drone is scoped to its own worktree and scratch root; coordinate before working on a foreign path.';
21
21
 
22
22
  const PATH_KEYS = new Set([
23
23
  'cwd',
@@ -797,8 +797,8 @@ function liveMonitorYieldLine(
797
797
  : null;
798
798
  const holder = livePid === null ? 'another monitor instance' : `a live instance (pid ${livePid})`;
799
799
  return (
800
- `borg-inbox-monitor: seat inbox ${JSON.stringify(inboxPath)} is already monitored by ${holder}; ` +
801
- 'yielding — another session likely holds this seat.'
800
+ `borg-inbox-monitor: inbox ${JSON.stringify(inboxPath)} is already monitored by ${holder}; ` +
801
+ 'yielding — another session likely holds this worktree\'s connection.'
802
802
  );
803
803
  }
804
804
 
package/src/index.ts CHANGED
@@ -561,7 +561,7 @@ export async function main() {
561
561
  `# Re-attached to cube: ${displayIdentity.cubeName}`,
562
562
  ``,
563
563
  `**Drone label:** ${displayIdentity.droneLabel}`,
564
- `**Seat:** existing identity reused — no new drone minted (gh#780)`,
564
+ `**Drone:** existing identity reused — no new drone minted (gh#780)`,
565
565
  ``,
566
566
  ``,
567
567
  ].join('\n');
@@ -53,7 +53,7 @@ async function resolveTargetCube(
53
53
  const identities = await deps.readAllProjectIdentities();
54
54
  const matches = identities.filter((e) => e.cube.name === args.cubeName);
55
55
  if (matches.length === 0) {
56
- return { error: `no cube named '${args.cubeName}' found among this machine's saved seats — has any drone assimilated into it?` };
56
+ return { error: `no cube named '${args.cubeName}' found among this machine's saved connections — has any drone assimilated into it?` };
57
57
  }
58
58
  // gh#850: distinct cubes can share a name (same name across accounts/
59
59
  // environments, or a stale seat). Silently taking matches[0] could launch
@@ -61,13 +61,13 @@ async function resolveTargetCube(
61
61
  // cubeId + the project that holds the seat and refuse to guess.
62
62
  if (matches.length > 1) {
63
63
  const list = matches
64
- .map((m) => ` ${m.cube.cubeId} (seat in ${m.projectPath})`)
64
+ .map((m) => ` ${m.cube.cubeId} (drone in ${m.projectPath})`)
65
65
  .join('\n');
66
66
  return {
67
67
  error:
68
- `'${args.cubeName}' is ambiguous — ${matches.length} saved seats on this machine share that name:\n${list}\n` +
68
+ `'${args.cubeName}' is ambiguous — ${matches.length} saved connections on this machine share that name:\n${list}\n` +
69
69
  'cd into the intended project and re-run without --cube-name (resolves the active cube), ' +
70
- 'or clear the stale seat(s) by running `borg reset-local-seat` from the worktree that holds each.',
70
+ 'or clear the stale connection(s) by running `borg reset-local-connection` from the worktree that holds each.',
71
71
  };
72
72
  }
73
73
  return { cubeId: matches[0].cube.cubeId, name: args.cubeName };
@@ -237,7 +237,7 @@ export async function runLaunchAll(
237
237
  }
238
238
  } else {
239
239
  deps.stdout(
240
- `No worktrees found for cube '${cubeName}' — have you run \`borg assimilate --worktree\` to create any drone seats?\n`
240
+ `No worktrees found for cube '${cubeName}' — have you run \`borg assimilate --worktree\` to create any drones?\n`
241
241
  );
242
242
  }
243
243
  return 0;
@@ -290,8 +290,8 @@ export async function runLaunchAll(
290
290
  if (status === 'evicted') {
291
291
  evictedCount += 1;
292
292
  deps.stderr(
293
- `skipping ${c.droneLabel} (${c.worktreeDir}): seat no longer in cube (evicted) — ` +
294
- `run \`borg cleanup --prune\` to remove the worktree, or \`borg assimilate\` to re-seat fresh.\n`
293
+ `skipping ${c.droneLabel} (${c.worktreeDir}): drone no longer in cube (evicted) — ` +
294
+ `run \`borg cleanup --prune\` to remove the worktree, or \`borg assimilate\` to start fresh.\n`
295
295
  );
296
296
  continue;
297
297
  }
@@ -299,7 +299,7 @@ export async function runLaunchAll(
299
299
  revokedCount += 1;
300
300
  deps.stderr(
301
301
  `Local session was revoked.\n` +
302
- `Next: run borg reset-local-seat, then borg assimilate --host ${c.apiUrl} --enroll.\n`
302
+ `Next: run borg reset-local-connection, then borg assimilate --host ${c.apiUrl} --enroll.\n`
303
303
  );
304
304
  continue;
305
305
  }
@@ -307,7 +307,7 @@ export async function runLaunchAll(
307
307
  rejectedCount += 1;
308
308
  deps.stderr(
309
309
  `Local session was superseded by a newer enrollment.\n` +
310
- `Next: run borg reset-local-seat, then borg assimilate --host ${c.apiUrl} --enroll.\n`
310
+ `Next: run borg reset-local-connection, then borg assimilate --host ${c.apiUrl} --enroll.\n`
311
311
  );
312
312
  continue;
313
313
  }
@@ -336,7 +336,7 @@ export async function runLaunchAll(
336
336
  // cause-accurate note. Only the authoritative/terminal causes above skip.
337
337
  if (status === 'unreachable') {
338
338
  deps.stderr(
339
- `note: could not reach ${c.droneLabel}'s server to confirm its seat (network/timeout) — launching anyway.\n`
339
+ `note: could not reach ${c.droneLabel}'s server to confirm that drone (network/timeout) — launching anyway.\n`
340
340
  );
341
341
  } else if (status === 'endpoint-mismatch') {
342
342
  deps.stderr(
@@ -344,11 +344,11 @@ export async function runLaunchAll(
344
344
  );
345
345
  } else if (status === 'server-failure') {
346
346
  deps.stderr(
347
- `note: ${c.droneLabel}'s server returned an error while confirming its seat (transient) — launching anyway.\n`
347
+ `note: ${c.droneLabel}'s server returned an error while confirming that drone (transient) — launching anyway.\n`
348
348
  );
349
349
  } else if (status === 'indeterminate') {
350
350
  deps.stderr(
351
- `note: could not confirm ${c.droneLabel}'s seat is live (network/transient) — launching anyway.\n`
351
+ `note: could not confirm ${c.droneLabel} is live (network/transient) — launching anyway.\n`
352
352
  );
353
353
  }
354
354
  launchable.push(c);
@@ -95,7 +95,7 @@ export async function discoverDroneCandidates(
95
95
  // 4. malformed entry (cubeId/droneId not UUID) → warn + skip
96
96
  if (!isUuid(cube.cubeId) || !isUuid(cube.droneId)) {
97
97
  deps.stderr(
98
- `skipping ${worktreeDir}: its saved seat has a malformed cubeId/droneId — re-assimilate to fix\n`
98
+ `skipping ${worktreeDir}: its saved connection has a malformed cubeId/droneId — re-assimilate to fix\n`
99
99
  );
100
100
  continue;
101
101
  }
@@ -71,14 +71,14 @@ export async function resolveOpenCodeSeatIdentity(
71
71
  if (!active) {
72
72
  throw new OpenCodeSeatIdentityError(
73
73
  'SEAT_NOT_FOUND',
74
- 'No active Borg seat is bound to the OpenCode session directory.',
74
+ 'No Borg drone is bound to the OpenCode session directory.',
75
75
  sessionWorktree,
76
76
  );
77
77
  }
78
78
  if (typeof active.worktree !== 'string' || resolve(active.worktree) !== resolve(sessionWorktree)) {
79
79
  throw new OpenCodeSeatIdentityError(
80
80
  'SEAT_WORKTREE_MISMATCH',
81
- 'The resolved Borg seat belongs to a different worktree than the OpenCode session.',
81
+ 'The resolved Borg drone belongs to a different worktree than the OpenCode session.',
82
82
  sessionWorktree,
83
83
  active,
84
84
  );
@@ -93,19 +93,19 @@ export function formatOpenCodeSeatIdentityError(
93
93
  childCwd: string,
94
94
  ): string {
95
95
  const lines = [
96
- `Borg OpenCode seat identity error [${error.code}]`,
96
+ `Borg OpenCode identity error [${error.code}]`,
97
97
  '',
98
98
  error.message,
99
99
  `- OpenCode session directory: ${error.sessionDirectory ?? 'unavailable'}`,
100
100
  `- Borg MCP child cwd: ${childCwd}`,
101
101
  ];
102
102
  if (error.seat) {
103
- lines.push(`- Resolved seat: ${error.seat.droneLabel} (${error.seat.worktree})`);
103
+ lines.push(`- Resolved drone: ${error.seat.droneLabel} (${error.seat.worktree})`);
104
104
  }
105
105
  lines.push(
106
106
  '',
107
107
  'The Borg stream and OpenCode wake injection were not started.',
108
- 'Exit this session and run `borg --cli opencode` from the intended worktree. If that worktrees saved seat is stale, run `borg reset-local-seat` from that exact worktree before assimilating again.',
108
+ 'Exit this session and run `borg --cli opencode` from the intended worktree. If that worktree\'s saved connection is stale, run `borg reset-local-connection` from that exact worktree before assimilating again.',
109
109
  );
110
110
  return lines.join('\n');
111
111
  }
@@ -6,6 +6,7 @@
6
6
  * index.ts's stdio MCP server bootstrap.
7
7
  */
8
8
 
9
+ import { hostname as osHostname } from 'node:os';
9
10
  import {
10
11
  ROLE_SCOPED_SAFETY_DISCIPLINES,
11
12
  UNIVERSAL_SAFETY_DISCIPLINES,
@@ -243,7 +244,7 @@ export function markArrivalAnnouncedThisProcess(): void {
243
244
  export function getDronePlaybook(): string {
244
245
  const arrivalInstruction = arrivalAnnouncedThisProcess
245
246
  ? ''
246
- : '\n**When this MCP session first starts:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname>` (run `hostname`). After the post succeeds, the client suppresses this instruction until the MCP process restarts; an explicit `/mcp` reconnect may show it again.\n';
247
+ : `\n**When this MCP session first starts:** post one \`ARRIVAL: <your-label> (<your-role>) online on ${osHostname()}\`. After the post succeeds, the client suppresses this instruction until the MCP process restarts; an explicit \`/mcp\` reconnect may show it again.\n`;
247
248
  return `## How to operate as a Drone
248
249
 
249
250
  You're a Drone in a Cube. Coordinate with other drones through the activity log.
@@ -261,6 +262,12 @@ You're a Drone in a Cube. Coordinate with other drones through the activity log.
261
262
 
262
263
  **How coordination works:** the Cube gives primitives, not workflows. Your role's \`detailed_description\` (above) is your playbook — its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.
263
264
 
265
+ **Communication discipline for non-human seats:**
266
+ - **Console:** write nothing except harness-required output. Surface something to the operator only when blocked and needing unblocking; do not narrate plans, progress, method, or results.
267
+ - **Log:** a post must change what another seat does. Otherwise, do not write it. Keep posts short: lifecycle signal + SHA and nothing else; defect + location/evidence; correction to your live claim; or a genuine blocking question.
268
+ - **Do not post:** plans, work-in-progress/progress narration, method or reasoning, restatements/agreement/credit, self-examination, framing phrases, or coordination commentary.
269
+ - **Evidence boundary:** state what a verdict did not exercise and any unavailable control in the same short clause. The human seat is excluded so its dispatches can explain constraints without being misapplied.
270
+
264
271
  **Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input → post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone — one continuous seat. Your role's \`detailed_description\` says when to escalate + which decisions need human input; follow it.
265
272
 
266
273
  **Operating loop — each wake, in order:**
@@ -474,7 +474,7 @@ async function localCubeComposition(active: ActiveCube): Promise<{
474
474
  const drones = dronePayload.drones.map(withValidatedRuntimeMetadata);
475
475
  const drone = drones.find((candidate) => candidate.id === active.droneId);
476
476
  const role = rolePayload.roles.find((candidate) => candidate.id === drone?.role_id);
477
- if (!drone || !role) throw new Error('Local Borg server no longer recognizes this drone seat');
477
+ if (!drone || !role) throw new Error('Local Borg server no longer recognizes this drone');
478
478
  return {
479
479
  cube: cubePayload.cube,
480
480
  roles: rolePayload.roles,
@@ -169,7 +169,7 @@ function renderResult(
169
169
  `Next: borg assimilate --host ${shellEscape(input.serverOrigin)}`,
170
170
  );
171
171
  } else {
172
- lines.push('Continuing with role and seat setup…');
172
+ lines.push('Continuing with role and connection setup…');
173
173
  }
174
174
  resultWriter(`${lines.join('\n')}\n`);
175
175
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `borg reset-local-seat [--host <host>] [--yes]` — the dedicated LOCAL/OFFLINE
2
+ * `borg reset-local-connection [--host <host>] [--yes]` — the dedicated LOCAL/OFFLINE
3
3
  * seat reset from the ratified client-seat-reset-state-model (Option W).
4
4
  *
5
5
  * Attach is PURE DIAGNOSIS on a pin-matched SESSION_REJECTED (it mutates
@@ -95,7 +95,7 @@ export async function runResetLocalSeat(
95
95
  const snapshot = await deps.snapshotLocalSeat();
96
96
  if (!snapshot) {
97
97
  deps.stdout(
98
- `No saved local seat was found for this worktree (${worktree}); nothing to reset.\n`,
98
+ `No saved connection was found for this worktree (${worktree}); nothing to reset.\n`,
99
99
  );
100
100
  return 0;
101
101
  }
@@ -105,9 +105,9 @@ export async function runResetLocalSeat(
105
105
  // operator asked to reset.
106
106
  if (requestedHost !== undefined && requestedHost !== snapshot.apiUrl) {
107
107
  deps.stdout(
108
- `This worktree's saved local seat is on ${snapshot.apiUrl}, not ${requestedHost}; ` +
109
- `nothing was changed. Re-run \`borg reset-local-seat --host ${snapshot.apiUrl}\` to ` +
110
- "reset this worktree's seat.\n",
108
+ `This worktree connects to ${snapshot.apiUrl}, not ${requestedHost}; ` +
109
+ `nothing was changed. Re-run \`borg reset-local-connection --host ${snapshot.apiUrl}\` to ` +
110
+ "reset this worktree's saved connection.\n",
111
111
  );
112
112
  return 0;
113
113
  }
@@ -117,7 +117,7 @@ export async function runResetLocalSeat(
117
117
  ? `a saved local session credential is present (${snapshot.observation.state})`
118
118
  : 'the saved local session credential is already cleared (only the binding remains)';
119
119
  deps.stderr(
120
- `This will clear ONLY this worktree's saved local seat on ${snapshot.apiUrl} ` +
120
+ `This will clear ONLY this worktree's saved connection to ${snapshot.apiUrl} ` +
121
121
  `(worktree ${worktree}) — ${observed}. Server, trust anchor, cube, and sibling ` +
122
122
  'worktrees are left untouched. It makes no network call and revokes nothing server-side.\n',
123
123
  );
@@ -125,17 +125,17 @@ export async function runResetLocalSeat(
125
125
  // ----- S1: consent OUTSIDE any lock -----
126
126
  if (deps.isTTY()) {
127
127
  const answer = await deps.prompt(
128
- `Reset this worktree's saved local seat now? [y/N]: `,
128
+ `Reset this worktree's saved connection now? [y/N]: `,
129
129
  );
130
130
  const normalized = answer.trim().toLowerCase();
131
131
  if (normalized !== 'y' && normalized !== 'yes') {
132
- deps.stderr("audit: no changes made — this worktree's saved seat was left in place.\n");
132
+ deps.stderr("audit: no changes made — this worktree's saved connection was left in place.\n");
133
133
  return 0;
134
134
  }
135
135
  } else if (flags.yes !== true) {
136
136
  deps.stderr(
137
137
  'audit: no changes made — stdin is non-interactive and --yes was not passed. Re-run ' +
138
- '`borg reset-local-seat --yes` to clear this worktree\'s saved seat without a prompt.\n',
138
+ '`borg reset-local-connection --yes` to clear this worktree\'s saved connection without a prompt.\n',
139
139
  );
140
140
  return 1;
141
141
  }
@@ -146,7 +146,7 @@ export async function runResetLocalSeat(
146
146
  outcome = await deps.resetLocalSeatBinding(snapshot);
147
147
  } catch {
148
148
  deps.stderr(
149
- `audit: no changes made — the local seat reset for ${snapshot.apiUrl} (worktree ` +
149
+ `audit: no changes made — the connection reset for ${snapshot.apiUrl} (worktree ` +
150
150
  `${worktree}) could not complete (local credential store error). Retry — it is ` +
151
151
  'safe to re-run.\n',
152
152
  );
@@ -155,7 +155,7 @@ export async function runResetLocalSeat(
155
155
 
156
156
  if (outcome.outcome === 'reset') {
157
157
  deps.stderr(
158
- `audit: this worktree's saved local seat for ${snapshot.apiUrl} (worktree ${worktree}) ` +
158
+ `audit: this worktree's saved connection to ${snapshot.apiUrl} (worktree ${worktree}) ` +
159
159
  'was cleared; server, trust anchor, cube, and sibling worktrees unchanged.\n',
160
160
  );
161
161
  let remaining: Awaited<ReturnType<ResetLocalSeatDeps['findRemainingActiveSeat']>> = null;
@@ -167,9 +167,9 @@ export async function runResetLocalSeat(
167
167
  }
168
168
  if (remaining?.apiUrl === snapshot.apiUrl) {
169
169
  deps.stdout(
170
- `Another saved active seat remains for this worktree on ${snapshot.apiUrl}. ` +
170
+ `Another saved connection remains for this worktree on ${snapshot.apiUrl}. ` +
171
171
  `Re-attach with ${reattachCommand(snapshot.apiUrl)}; Borg will revalidate that ` +
172
- 'seat with the server before launch.\n',
172
+ 'it with the server before launch.\n',
173
173
  );
174
174
  } else {
175
175
  deps.stdout(recoveryGuidance(snapshot.apiUrl));
@@ -179,7 +179,7 @@ export async function runResetLocalSeat(
179
179
 
180
180
  if (outcome.outcome === 'no-binding') {
181
181
  deps.stdout(
182
- `No saved local seat remained for this worktree (${worktree}); nothing to reset.\n`,
182
+ `No saved connection remained for this worktree (${worktree}); nothing to reset.\n`,
183
183
  );
184
184
  return 0;
185
185
  }
@@ -188,7 +188,7 @@ export async function runResetLocalSeat(
188
188
  // (a concurrent re-enroll wrote a fresh bearer, or another process already
189
189
  // reset it). Never clobber a replacement — report the honest no-op.
190
190
  deps.stdout(
191
- `This worktree's saved local seat on ${snapshot.apiUrl} changed since it was read ` +
191
+ `This worktree's saved connection to ${snapshot.apiUrl} changed since it was read ` +
192
192
  "(a concurrent re-enroll or reset); nothing was changed. Re-run to observe the current " +
193
193
  'state.\n',
194
194
  );
@@ -221,7 +221,7 @@ export type ResetLocalSeatParseResult =
221
221
  | { ok: true; flags: ResetLocalSeatFlags }
222
222
  | { ok: false; error: string };
223
223
 
224
- /** Parse args after `borg reset-local-seat`. Supports `--host <h>` / `--host=<h>` / `--yes` / `-y`. */
224
+ /** Parse args after `borg reset-local-connection`. Supports `--host <h>` / `--host=<h>` / `--yes` / `-y`. */
225
225
  export function parseResetLocalSeatArgs(rawArgs: string[]): ResetLocalSeatParseResult {
226
226
  const flags: ResetLocalSeatFlags = {};
227
227
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -244,7 +244,7 @@ export function parseResetLocalSeatArgs(rawArgs: string[]): ResetLocalSeatParseR
244
244
  } else {
245
245
  return {
246
246
  ok: false,
247
- error: `unexpected argument: ${arg}. Usage: borg reset-local-seat [--host <host>] [--yes]`,
247
+ error: `unexpected argument: ${arg}. Usage: borg reset-local-connection [--host <host>] [--yes]`,
248
248
  };
249
249
  }
250
250
  }
@@ -76,7 +76,7 @@ export function formatSeatReattachRefusal(
76
76
  ? ''
77
77
  : ` Its heartbeat is ${holder.heartbeat}, so the process may be wedged.`;
78
78
  return (
79
- `This worktree's Borg seat already has a live session (inbox monitor pid ${holder.pid}).${heartbeat}\n` +
79
+ `This worktree's Borg drone already has a live session (inbox monitor pid ${holder.pid}).${heartbeat}\n` +
80
80
  'No agent was launched. Stop the existing session or use a fresh worktree with `borg assimilate --worktree <name>`. ' +
81
81
  `If the live monitor is wedged, override once with \`${forcedCommand}\`.\n`
82
82
  );
package/src/seat-store.ts CHANGED
@@ -165,7 +165,7 @@ function staleLockError(lockPath: string, held: LockPayload | null): Error {
165
165
  ? `its recorded owner process (pid ${held.pid}, started ${held.startTime}) is no longer running`
166
166
  : 'its lock file is missing a valid owner identity or is corrupt';
167
167
  return new Error(
168
- `Borg seat store lock file ${lockPath} is stale: ${who}. ` +
168
+ `Borg private store lock file ${lockPath} is stale: ${who}. ` +
169
169
  'Borg will NOT remove it automatically. If no borg process is running on this ' +
170
170
  `machine, delete ${lockPath} and retry; otherwise wait for the other borg process to finish.`,
171
171
  );
@@ -263,7 +263,7 @@ async function assertSecureStorePerms(
263
263
  ): Promise<void> {
264
264
  if ((fileMode & 0o077) !== 0) {
265
265
  throw new Error(
266
- `Borg seat store file ${filePath} has insecure permissions ` +
266
+ `Borg private store file ${filePath} has insecure permissions ` +
267
267
  `(0${(fileMode & 0o777).toString(8)}, expected 0600); refusing to read a credential from it`,
268
268
  );
269
269
  }
@@ -277,7 +277,7 @@ async function assertSecureStorePerms(
277
277
  }
278
278
  if ((dirStat.mode & 0o077) !== 0) {
279
279
  throw new Error(
280
- `Borg seat store directory ${dir} has insecure permissions ` +
280
+ `Borg private store directory ${dir} has insecure permissions ` +
281
281
  `(0${(dirStat.mode & 0o777).toString(8)}, expected 0700); refusing to read a credential under it`,
282
282
  );
283
283
  }
@@ -350,7 +350,7 @@ export async function readStoreFile(
350
350
  * release it on EVERY path (finally) by unlinking OUR OWN lock. Acquire is an atomic
351
351
  * `open(lockPath,'wx',0o600)`. On EEXIST the lock is held:
352
352
  * - holder PID ALIVE → bounded wait/retry (attempts×waitMs), then throw the truthful
353
- * transient 'Borg seat store is busy' error;
353
+ * transient 'Borg private store is busy' error;
354
354
  * - holder PID DEAD, or the payload is missing/unparseable → FAIL CLOSED naming the
355
355
  * exact lockfile path + the recorded dead pid/start-time. Borg NEVER auto-deletes
356
356
  * or steals it (no reclaim, no rename-claim). The operator clears it by hand only
@@ -422,7 +422,7 @@ export async function withStoreLock<T>(
422
422
  });
423
423
  }
424
424
  }
425
- throw new Error('Borg seat store is busy');
425
+ throw new Error('Borg private store is busy');
426
426
  } finally {
427
427
  await unlink(tmp).catch(() => {});
428
428
  }
@@ -469,7 +469,7 @@ export async function withStore<S, T>(
469
469
  }
470
470
  if (loaded === null) {
471
471
  throw new Error(
472
- 'Borg seat store is malformed or has an unsupported version; refusing to overwrite it',
472
+ 'Borg private store is malformed or has an unsupported version; refusing to overwrite it',
473
473
  );
474
474
  }
475
475
  data = loaded;
package/src/seats.ts CHANGED
@@ -242,7 +242,7 @@ async function readStore(): Promise<SeatsFile> {
242
242
  const parsed = parseStore(raw);
243
243
  if (parsed === null) {
244
244
  throw new Error(
245
- 'Borg seat store is malformed or has an unsupported version; refusing to read it',
245
+ 'Borg private store is malformed or has an unsupported version; refusing to read it',
246
246
  );
247
247
  }
248
248
  return parsed;
@@ -338,7 +338,7 @@ export async function sendBorgServerAttach(
338
338
  if (rejectedCode === ErrorCode.SESSION_REJECTED) {
339
339
  throw new BorgServerError(
340
340
  'SESSION_REJECTED',
341
- 'Borg server rejected the session: the seat is already bound to another session',
341
+ 'Borg server rejected the session: this worktree\'s connection is already bound to another session',
342
342
  );
343
343
  }
344
344
  if (rejectedCode === ErrorCode.SESSION_REVOKED) {
@@ -163,7 +163,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
163
163
  } else if (ownedByOther) {
164
164
  const owner = status.ownership!;
165
165
  summary = owner.droneLabel && owner.worktree
166
- ? `**Stream owned by seat ${owner.droneLabel} in \`${owner.worktree}\`.**`
166
+ ? `**Stream owned by ${owner.droneLabel} in \`${owner.worktree}\`.**`
167
167
  : '**Stream owned by another Borg MCP process.**';
168
168
  } else if (isNotStarted) {
169
169
  summary = '**Stream not started.**';
@@ -248,7 +248,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
248
248
 
249
249
  if (ownedByOther) {
250
250
  const owner = status.ownership!;
251
- lines.push(`- **stream owner seat**: ${owner.droneLabel ?? '_(unknown)_'}`);
251
+ lines.push(`- **stream owner**: ${owner.droneLabel ?? '_(unknown)_'}`);
252
252
  lines.push(`- **stream owner worktree**: ${owner.worktree ?? '_(unknown)_'}`);
253
253
  lines.push(`- **stream owner pid**: ${owner.pid ?? '_(unknown)_'}`);
254
254
  lines.push(`- **stream owner cwd**: ${owner.cwd ?? '_(unknown)_'}`);
@@ -260,7 +260,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
260
260
  }`
261
261
  );
262
262
  lines.push('');
263
- lines.push('Continue in the owning seat, or close its duplicate agent session before relaunching from the intended worktree. The live owner releases this lock on exit; a stale lock is reclaimed automatically.');
263
+ lines.push('Continue as the owning drone, or close its duplicate agent session before relaunching from the intended worktree. The live owner releases this lock on exit; a stale lock is reclaimed automatically.');
264
264
  }
265
265
 
266
266
  if (wakePath.agentKind === 'opencode' && wakePath.openCode) {
@@ -506,7 +506,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
506
506
  {
507
507
  name: 'borg_evict-drone',
508
508
  description:
509
- 'Remove a drone using the selected local client\'s cube-management grant. The seat credential is revoked, project files remain, and activity history keeps the removed seat\'s attribution. Accepts drone_id for the current cube or label with cube_id.',
509
+ 'Remove a drone using the selected local client\'s cube-management grant. The drone\'s credential is revoked, project files remain, and activity history keeps the removed drone\'s attribution. Accepts drone_id for the current cube or label with cube_id.',
510
510
  inputSchema: {
511
511
  type: 'object',
512
512
  properties: {
@@ -16,10 +16,9 @@ export const KNOWN_SUBCOMMANDS = [
16
16
  'setup',
17
17
  'update',
18
18
  'assimilate',
19
- 'reset-local-seat',
19
+ 'reset-local-connection',
20
20
  'recover-enrollment',
21
21
  'spawn',
22
- 'sync',
23
22
  'cleanup',
24
23
  'launch-all',
25
24
  'server',
@@ -9,7 +9,6 @@
9
9
  * Design spec: docs/superpowers/specs/2026-05-29-worktree-lifecycle-design.md
10
10
  * Q-resolutions baked in (SPEC-APPROVED 3a80412d):
11
11
  * Q1 branch naming — `wt-<suffix>` prefix-stripped, full-basename fallback.
12
- * Q2 idle-sync — ff-only, clean-gated; never merge/rebase; never over dirty.
13
12
  * Q3 post-merge — auto-return to wt-<basename>; ANNOUNCE the prunable
14
13
  * merged branch, prune only when explicitly requested.
15
14
  * Q4 uniform — no primary-worktree carve-out; main is never a working branch.
@@ -119,55 +118,11 @@ export function classifyDirty(runSync: RunSync, cwd: string): DirtyClassificatio
119
118
  return out;
120
119
  }
121
120
 
122
- /** True iff `branch` is an ancestor of `ref` — i.e. a clean fast-forward target. */
123
- export function isFastForward(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
124
- return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
125
- }
126
-
127
121
  /** True iff `branch`'s tip is an ancestor of `ref` — i.e. fully merged into it. */
128
122
  export function isMerged(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
129
123
  return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
130
124
  }
131
125
 
132
- export interface SyncResult {
133
- action: 'fast-forwarded' | 'already-current' | 'skipped-dirty' | 'skipped-diverged';
134
- message?: string;
135
- }
136
-
137
- /**
138
- * Idle-sync the current per-worktree branch to `ref` (Q2). NEVER discards
139
- * work: dirty -> skipped-dirty (no mutation). Only fast-forwards (no
140
- * merge/rebase): diverged -> skipped-diverged. The caller fetches first.
141
- *
142
- * `already-current` when the branch tip already equals `ref` (the common
143
- * no-op case on every launch).
144
- */
145
- export function syncWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): SyncResult {
146
- if (!isCleanTree(runSync, cwd)) {
147
- return {
148
- action: 'skipped-dirty',
149
- message: 'uncommitted changes present; sync skipped (nothing discarded)',
150
- };
151
- }
152
- if (!isFastForward(runSync, cwd, branch, ref)) {
153
- return {
154
- action: 'skipped-diverged',
155
- message: `${branch} has diverged from ${ref}; resolve manually (no auto-merge/rebase)`,
156
- };
157
- }
158
- // Already at ref? merge --ff-only is a no-op but we report it distinctly
159
- // so callers can stay quiet on the common case.
160
- const ahead = runSync('git', ['rev-list', '--count', `${branch}..${ref}`], cwd);
161
- if (ahead.status === 0 && ahead.stdout.trim() === '0') {
162
- return { action: 'already-current' };
163
- }
164
- const ff = runSync('git', ['merge', '--ff-only', ref], cwd);
165
- if (ff.status !== 0) {
166
- return { action: 'skipped-diverged', message: 'ff-only merge unexpectedly failed' };
167
- }
168
- return { action: 'fast-forwarded' };
169
- }
170
-
171
126
  export interface AdoptResult {
172
127
  action: 'adopted' | 'blocked-unmerged' | 'blocked-target-unmerged' | 'skipped-dirty';
173
128
  message?: string;