borgmcp 3.0.0 → 3.1.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 (41) hide show
  1. package/dist/bare-launch-menu.d.ts +5 -5
  2. package/dist/bare-launch-menu.d.ts.map +1 -1
  3. package/dist/bare-launch-menu.js +2 -2
  4. package/dist/bare-launch-menu.js.map +1 -1
  5. package/dist/claude.js +3 -3
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli-platform.d.ts +4 -0
  8. package/dist/cli-platform.d.ts.map +1 -1
  9. package/dist/cli-platform.js +29 -10
  10. package/dist/cli-platform.js.map +1 -1
  11. package/dist/cubes.d.ts.map +1 -1
  12. package/dist/cubes.js +28 -10
  13. package/dist/cubes.js.map +1 -1
  14. package/dist/first-run-server.d.ts.map +1 -1
  15. package/dist/first-run-server.js +4 -1
  16. package/dist/first-run-server.js.map +1 -1
  17. package/dist/launch-all-cmd.js +1 -1
  18. package/dist/launch-all-cmd.js.map +1 -1
  19. package/dist/lifecycle-log-guard.d.ts.map +1 -1
  20. package/dist/lifecycle-log-guard.js +12 -3
  21. package/dist/lifecycle-log-guard.js.map +1 -1
  22. package/dist/remote-client.d.ts.map +1 -1
  23. package/dist/remote-client.js +36 -14
  24. package/dist/remote-client.js.map +1 -1
  25. package/dist/server-handshake.d.ts +1 -1
  26. package/dist/tool-manifest.js +3 -3
  27. package/dist/tool-manifest.js.map +1 -1
  28. package/docs/EXTRACTION_PROVENANCE.md +16 -28
  29. package/docs/LOCAL_SERVER.md +1 -1
  30. package/docs/RELEASING.md +3 -3
  31. package/package.json +2 -2
  32. package/src/bare-launch-menu.ts +6 -6
  33. package/src/claude.ts +6 -4
  34. package/src/cli-platform.ts +42 -10
  35. package/src/cubes.ts +27 -12
  36. package/src/first-run-server.ts +4 -1
  37. package/src/launch-all-cmd.ts +1 -1
  38. package/src/lifecycle-log-guard.ts +15 -3
  39. package/src/remote-client.ts +57 -15
  40. package/src/server-handshake.ts +1 -1
  41. package/src/tool-manifest.ts +3 -3
@@ -17,17 +17,23 @@ import { randomUUID } from 'node:crypto';
17
17
  import {
18
18
  createProtocolEnvelope,
19
19
  decodeDeleteCubeResponse,
20
+ decodeDeleteRoleRequest,
21
+ decodeDeleteRoleResult,
20
22
  decodeDroneRuntimeMetadataState,
21
23
  decodeEvictDroneResult,
22
24
  decodeProtocolEnvelope,
23
25
  decodeProtocolErrorEnvelope,
24
26
  decodeReassignDroneResult,
27
+ decodeRoleRationaleRequest,
28
+ decodeRoleRationaleResult,
25
29
  decodeUpdateDroneRuntimeMetadataResponse,
26
30
  ErrorCode,
27
31
  ProtocolContractError,
28
32
  type AgentKind,
33
+ type DeleteRoleResult,
29
34
  type EvictDroneResult,
30
35
  type ReassignDroneResult,
36
+ type RoleRationaleResult,
31
37
  } from 'borgmcp-shared/protocol';
32
38
  import { consolePrefix } from './console-prefix.js';
33
39
  import { debugLog } from './debug.js';
@@ -95,16 +101,22 @@ export const LOCAL_SERVER_REQUEST_TIMEOUT_MS = 5_000;
95
101
  const LOCAL_SERVER_RESPONSE_LIMIT_MESSAGE =
96
102
  'Local Borg server response exceeded the response limit';
97
103
 
104
+ export const SERVER_ADVISORY_MAX_CHARS = 512;
105
+ // Keep enough source headroom to remove control sequences that cross the
106
+ // visible 512-character boundary without letting regex work scale with the
107
+ // 32 MiB response-body limit.
108
+ const SERVER_MESSAGE_SANITIZE_MAX_CHARS = SERVER_ADVISORY_MAX_CHARS * 8;
109
+
98
110
  function sanitizeServerMessage(message: string): string {
99
- return message
100
- .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, '')
101
- .replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '')
111
+ const bounded = message.slice(0, SERVER_MESSAGE_SANITIZE_MAX_CHARS);
112
+
113
+ return bounded
114
+ .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\|$)/g, '')
115
+ .replace(/\u001b\[[0-?]*[ -/]*(?:[@-~]|$)/g, '')
102
116
  .replace(/[\u0000-\u001f\u007f-\u009f]/g, '')
103
117
  .replace(/\\u(?:000[0-9a-f]|001[0-9a-f]|007f|008[0-9a-f]|009[0-9a-f])/gi, '');
104
118
  }
105
119
 
106
- export const SERVER_ADVISORY_MAX_CHARS = 512;
107
-
108
120
  export function sanitizeServerAdvisory(value: unknown): string | undefined {
109
121
  if (typeof value !== 'string') return undefined;
110
122
  const sanitized = sanitizeServerMessage(value).trim();
@@ -295,7 +307,10 @@ async function localServerRequest<T>(
295
307
  path: string,
296
308
  method: 'GET' | 'POST' | 'PUT' | 'PATCH',
297
309
  payload?: Record<string, unknown>,
298
- options: { retryMode?: AuthedFetchRetryMode } = {},
310
+ options: {
311
+ retryMode?: AuthedFetchRetryMode;
312
+ decodePayload?: (value: unknown) => T;
313
+ } = {},
299
314
  ): Promise<T | null> {
300
315
  return decodeLocalProtocolResponse<T>((signal) => authedFetch(path, {
301
316
  method,
@@ -312,7 +327,7 @@ async function localServerRequest<T>(
312
327
  body: JSON.stringify(createProtocolEnvelope(randomUUID(), payload)),
313
328
  }),
314
329
  retryMode: options.retryMode,
315
- }), true);
330
+ }), true, options.decodePayload);
316
331
  }
317
332
 
318
333
  export interface LocalManageOperation {
@@ -1247,12 +1262,21 @@ export async function roleRationale(
1247
1262
  section: string,
1248
1263
  serverTrustIdentity?: string,
1249
1264
  ): Promise<{ role: string; section: string; body: string }> {
1250
- void sessionToken;
1251
- void apiUrl;
1252
- void role;
1253
- void section;
1254
- void serverTrustIdentity;
1255
- localUnsupported('role rationale sections');
1265
+ const local = await localAuthorityContext(sessionToken, apiUrl, serverTrustIdentity);
1266
+ const request = decodeRoleRationaleRequest({ role, section });
1267
+ const result = await localServerRequest<RoleRationaleResult>(
1268
+ local,
1269
+ `/api/cubes/${local.cubeId}/role-rationale`,
1270
+ 'POST',
1271
+ { ...request },
1272
+ { decodePayload: decodeRoleRationaleResult },
1273
+ );
1274
+ if (!result) throw new Error('Local Borg server returned an empty role rationale response');
1275
+ return {
1276
+ role: result.role_name,
1277
+ section: result.section.heading,
1278
+ body: result.section.body,
1279
+ };
1256
1280
  }
1257
1281
 
1258
1282
  /**
@@ -1656,8 +1680,26 @@ export async function patchRoleSection(
1656
1680
  * (reassign or evict those drones first).
1657
1681
  */
1658
1682
  export async function deleteRole(roleId: string): Promise<void> {
1659
- void roleId;
1660
- localUnsupported('role deletion');
1683
+ assertUuidShape(roleId, 'role_id');
1684
+ const active = await getActiveCube();
1685
+ if (!active?.serverTrustIdentity) throw new Error('Selected Borg server authority state is missing or unreadable');
1686
+ assertUuidShape(active.cubeId, 'cube_id');
1687
+ const result = await localManageRequest<DeleteRoleResult>(
1688
+ active,
1689
+ `/api/cubes/${active.cubeId}/roles/${roleId}`,
1690
+ 'DELETE',
1691
+ {
1692
+ operation: `delete role ${manageCopyValue(roleId)} from cube ${manageCopyValue(active.name)}`,
1693
+ cubeName: active.name,
1694
+ noMutation: 'No role was deleted.',
1695
+ },
1696
+ decodeDeleteRoleRequest({}),
1697
+ decodeDeleteRoleResult,
1698
+ );
1699
+ if (!result) throw new Error('Local Borg server returned an empty role deletion response');
1700
+ if (result.role_id !== roleId) {
1701
+ throw new Error('Local Borg server returned a deletion response for an unexpected role');
1702
+ }
1661
1703
  }
1662
1704
 
1663
1705
  /**
@@ -214,7 +214,7 @@ export interface ServerAttachResult {
214
214
  }
215
215
 
216
216
  /**
217
- * Attach an enrolled client principal to one granted cube/role over protocol v7.
217
+ * Attach an enrolled client principal to one granted cube/role over protocol v8.
218
218
  * The client CSPRNG-generates the session bearer and persists it PENDING in the
219
219
  * OS keychain (keyed by the stable per-seat identity) BEFORE this request, so an
220
220
  * interrupted/lost response is recovered by re-sending the exact same bearer —
@@ -154,8 +154,8 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
154
154
  {
155
155
  name: 'borg_role-rationale',
156
156
  description:
157
- "Fetch an on-demand rationale/case-study section for a role playbook. " +
158
- "Pass a role name/id and a plain-label section key to read the rationale without expanding every context refresh.",
157
+ "Fetch exactly one named section from a role's detailed playbook using the current drone session. " +
158
+ "Pass a role name/UUID and a plain-label section key. Role names and section keys match case-insensitively; UUIDs match exactly. Malformed selectors, ambiguous role names, and unknown roles/sections refuse. Returns the server's canonical role name, role_id, section heading, and the section body in full, refusing rather than truncating when it exceeds the server's role-text size limit, so a drone can read one section on demand instead of carrying the whole playbook in every borg_regen.",
159
159
  inputSchema: {
160
160
  type: 'object',
161
161
  properties: {
@@ -481,7 +481,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
481
481
  },
482
482
  {
483
483
  name: 'borg_delete-role',
484
- description: 'Delete a role. Refuses if any drone is still assigned reassign them with borg_reassign-drone or remove them with borg_evict-drone first.',
484
+ description: 'Delete a role using the selected local client\'s cube-management grant. Unknown or inaccessible roles refuse. Also refuses for the default, mandatory, or human-seat role; a role referenced by message-taxonomy routing; or a role assigned to an active drone. Reassign active drones with borg_reassign-drone or remove them with borg_evict-drone first. Evicted drones that held the deleted role are reassigned to the cube\'s default role; their activity-log attribution is unaffected.',
485
485
  inputSchema: {
486
486
  type: 'object',
487
487
  properties: {