borgmcp 3.15.1 → 3.16.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 (58) hide show
  1. package/README.md +2 -0
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/codex-launch.d.ts.map +1 -1
  4. package/dist/codex-launch.js +3 -2
  5. package/dist/codex-launch.js.map +1 -1
  6. package/dist/docs-sections.d.ts.map +1 -1
  7. package/dist/docs-sections.js +8 -0
  8. package/dist/docs-sections.js.map +1 -1
  9. package/dist/document-render.d.ts +6 -0
  10. package/dist/document-render.d.ts.map +1 -0
  11. package/dist/document-render.js +34 -0
  12. package/dist/document-render.js.map +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +33 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/launch-all-cmd.d.ts.map +1 -1
  17. package/dist/launch-all-cmd.js +12 -0
  18. package/dist/launch-all-cmd.js.map +1 -1
  19. package/dist/launch-all-deps.d.ts +2 -0
  20. package/dist/launch-all-deps.d.ts.map +1 -1
  21. package/dist/launch-all-deps.js +13 -0
  22. package/dist/launch-all-deps.js.map +1 -1
  23. package/dist/log-stream.d.ts +2 -1
  24. package/dist/log-stream.d.ts.map +1 -1
  25. package/dist/log-stream.js +16 -3
  26. package/dist/log-stream.js.map +1 -1
  27. package/dist/regen-format.d.ts.map +1 -1
  28. package/dist/regen-format.js +6 -1
  29. package/dist/regen-format.js.map +1 -1
  30. package/dist/remote-client.d.ts +6 -1
  31. package/dist/remote-client.d.ts.map +1 -1
  32. package/dist/remote-client.js +41 -7
  33. package/dist/remote-client.js.map +1 -1
  34. package/dist/server-handshake.d.ts +4 -3
  35. package/dist/server-handshake.d.ts.map +1 -1
  36. package/dist/server-handshake.js.map +1 -1
  37. package/dist/tool-manifest.d.ts.map +1 -1
  38. package/dist/tool-manifest.js +65 -2
  39. package/dist/tool-manifest.js.map +1 -1
  40. package/dist/update-cmd.d.ts +5 -1
  41. package/dist/update-cmd.d.ts.map +1 -1
  42. package/dist/update-cmd.js +54 -14
  43. package/dist/update-cmd.js.map +1 -1
  44. package/docs/DOCUMENTS.md +49 -0
  45. package/docs/RELEASING.md +2 -2
  46. package/package.json +2 -2
  47. package/src/codex-launch.ts +3 -2
  48. package/src/docs-sections.ts +8 -0
  49. package/src/document-render.ts +41 -0
  50. package/src/index.ts +44 -1
  51. package/src/launch-all-cmd.ts +14 -0
  52. package/src/launch-all-deps.ts +15 -0
  53. package/src/log-stream.ts +21 -3
  54. package/src/regen-format.ts +6 -1
  55. package/src/remote-client.ts +108 -15
  56. package/src/server-handshake.ts +4 -3
  57. package/src/tool-manifest.ts +69 -2
  58. package/src/update-cmd.ts +67 -17
package/src/update-cmd.ts CHANGED
@@ -63,7 +63,7 @@ export interface UpdateDeps {
63
63
  options?: { ignoreScripts?: boolean },
64
64
  ): Promise<void>;
65
65
  reenter(binPath: string, args: readonly string[]): Promise<number>;
66
- serverJson(binPath: string, command: 'update' | 'status'): Promise<unknown>;
66
+ serverJson(binPath: string, command: 'update' | 'status'): Promise<ServerJsonExecution>;
67
67
  verifyRunningProtocol(origin: string): Promise<void>;
68
68
  refreshAgentIntegrations(): Promise<void>;
69
69
  confirm(message: string): Promise<'yes' | 'no' | 'eof' | 'interrupted'>;
@@ -73,6 +73,11 @@ export interface UpdateDeps {
73
73
  calls?: string[];
74
74
  }
75
75
 
76
+ export interface ServerJsonExecution {
77
+ exitCode: number;
78
+ value: unknown;
79
+ }
80
+
76
81
  interface ServerStatus {
77
82
  state: 'running' | 'stopped';
78
83
  installedController: string;
@@ -85,6 +90,10 @@ interface ServerStatus {
85
90
  mode: 'foreground' | 'managed' | 'legacy' | 'stopped';
86
91
  serviceAdapter: 'launchd' | 'systemd' | null;
87
92
  serviceRecovery: { command: string[] } | null;
93
+ runtimeLock:
94
+ | { state: 'clear' }
95
+ | { state: 'stale'; recoveryAction: 'borg-mcp-server recover-stale-lock' }
96
+ | null;
88
97
  dataIdentity: 'available' | 'unavailable';
89
98
  nextAction: string | null;
90
99
  }
@@ -375,6 +384,27 @@ function decodeManagedServiceRecovery(value: unknown): { command: string[] } | n
375
384
  return { command: record.command as string[] };
376
385
  }
377
386
 
387
+ function decodeStoppedRuntimeLock(value: unknown): Exclude<ServerStatus['runtimeLock'], null> | null {
388
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
389
+ const record = value as Record<string, unknown>;
390
+ if (record.state === 'clear') return { state: 'clear' };
391
+ if (
392
+ record.state !== 'stale' ||
393
+ !Number.isSafeInteger(record.pid) || (record.pid as number) <= 0 ||
394
+ record.process_state !== 'absent' ||
395
+ typeof record.runtime !== 'string' || record.runtime.length === 0 ||
396
+ (record.runtime_integrity !== null &&
397
+ (typeof record.runtime_integrity !== 'string' || !isCanonicalSha512Integrity(record.runtime_integrity))) ||
398
+ (record.build_identity !== null && typeof record.build_identity !== 'string') ||
399
+ (record.endpoint !== null && typeof record.endpoint !== 'string') ||
400
+ !['foreground', 'managed', 'legacy'].includes(record.mode as string) ||
401
+ record.recovery_action !== 'borg-mcp-server recover-stale-lock'
402
+ ) {
403
+ return null;
404
+ }
405
+ return { state: 'stale', recoveryAction: record.recovery_action };
406
+ }
407
+
378
408
  function decodeServerStatus(value: unknown): ServerStatus {
379
409
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
380
410
  throw new Error('server returned invalid JSON status');
@@ -414,12 +444,24 @@ function decodeServerStatus(value: unknown): ServerStatus {
414
444
  ) {
415
445
  throw new Error('server returned inconsistent JSON status');
416
446
  }
417
- const managedRecovery = record.status === 'stopped' && record.service_adapter !== null
418
- ? decodeManagedServiceRecovery(record.service_recovery)
419
- : null;
420
- if (record.status === 'stopped' && record.service_adapter !== null &&
421
- (record.service_state !== 'inactive' || managedRecovery === null)) {
422
- throw new Error('server returned invalid managed-service recovery status');
447
+ let managedRecovery: { command: string[] } | null = null;
448
+ let runtimeLock: ServerStatus['runtimeLock'] = null;
449
+ if (record.status === 'stopped') {
450
+ runtimeLock = decodeStoppedRuntimeLock(record.runtime_lock);
451
+ if (runtimeLock === null) throw new Error('server returned invalid JSON status runtime lock');
452
+ if (!['active', 'inactive', 'absent'].includes(record.service_state as string)) {
453
+ throw new Error('server returned invalid managed-service recovery status');
454
+ }
455
+ managedRecovery = record.service_adapter !== null && record.service_state === 'inactive'
456
+ ? decodeManagedServiceRecovery(record.service_recovery)
457
+ : null;
458
+ if (
459
+ (record.service_adapter !== null && record.service_state === 'inactive' && managedRecovery === null) ||
460
+ (record.service_state !== 'inactive' && record.service_recovery !== null) ||
461
+ (record.service_adapter === null && record.service_state !== 'absent')
462
+ ) {
463
+ throw new Error('server returned invalid managed-service recovery status');
464
+ }
423
465
  }
424
466
  return {
425
467
  state: record.status,
@@ -433,11 +475,21 @@ function decodeServerStatus(value: unknown): ServerStatus {
433
475
  mode: record.mode as ServerStatus['mode'],
434
476
  serviceAdapter: record.service_adapter,
435
477
  serviceRecovery: managedRecovery,
478
+ runtimeLock,
436
479
  dataIdentity: record.data_identity,
437
480
  nextAction: record.next_action,
438
481
  };
439
482
  }
440
483
 
484
+ function decodeServerStatusExecution(execution: ServerJsonExecution): ServerStatus {
485
+ const status = decodeServerStatus(execution.value);
486
+ const stale = status.state === 'stopped' && status.runtimeLock?.state === 'stale';
487
+ if ((stale && execution.exitCode !== 1) || (!stale && execution.exitCode !== 0)) {
488
+ throw new Error(`server returned invalid JSON status exit pairing (${execution.exitCode})`);
489
+ }
490
+ return status;
491
+ }
492
+
441
493
  function decodeServerUpdate(value: unknown): ServerUpdateResult {
442
494
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
443
495
  throw new Error('server returned invalid JSON update result');
@@ -551,6 +603,9 @@ function renderServerFailureRecovery(
551
603
  }
552
604
 
553
605
  function renderStoppedServiceRecovery(status: ServerStatus): string {
606
+ if (status.runtimeLock?.state === 'stale') {
607
+ return `Local server runtime lock is stale.\nRecover it with: borg-mcp-server recover-stale-lock\n`;
608
+ }
554
609
  if (status.serviceRecovery !== null) {
555
610
  const command = status.serviceRecovery.command.map(shellEscape).join(' ');
556
611
  return `Local server service is stopped.\nRestart it with: ${command}\n`;
@@ -754,13 +809,13 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
754
809
  const observeStatusAfterFailure = async (): Promise<void> => {
755
810
  recoveryStatusAttempted = true;
756
811
  try {
757
- observedStatus = decodeServerStatus(await deps.serverJson(server.binPath, 'status'));
812
+ observedStatus = decodeServerStatusExecution(await deps.serverJson(server.binPath, 'status'));
758
813
  } catch {
759
814
  observedStatus = null;
760
815
  }
761
816
  };
762
817
  try {
763
- let status = decodeServerStatus(await deps.serverJson(server.binPath, 'status'));
818
+ let status = decodeServerStatusExecution(await deps.serverJson(server.binPath, 'status'));
764
819
  observedStatus = status;
765
820
  initialServerState = status.state;
766
821
  if (status.installedController !== exactServerIdentity(pair.server.version)) {
@@ -775,7 +830,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
775
830
  updateAttempted = true;
776
831
  failureStage = 'server runtime activation';
777
832
  retryCommand = 'borg server update';
778
- const update = decodeServerUpdate(await deps.serverJson(server.binPath, 'update'));
833
+ const update = decodeServerUpdate((await deps.serverJson(server.binPath, 'update')).value);
779
834
  observedUpdate = update;
780
835
  if (update.status === 'failed') {
781
836
  await observeStatusAfterFailure();
@@ -794,7 +849,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
794
849
  failureStage = 'post-update server status check';
795
850
  retryCommand = 'borg server status';
796
851
  recoveryStatusAttempted = true;
797
- status = decodeServerStatus(await deps.serverJson(server.binPath, 'status'));
852
+ status = decodeServerStatusExecution(await deps.serverJson(server.binPath, 'status'));
798
853
  observedStatus = status;
799
854
  }
800
855
  failureStage = 'final server state verification';
@@ -1221,12 +1276,7 @@ export function buildDefaultUpdateDeps(): UpdateDeps {
1221
1276
  } catch {
1222
1277
  throw new Error(`server ${command} returned invalid JSON${serverCommandStderr(result.stderr)}`);
1223
1278
  }
1224
- if (result.code !== 0 && command !== 'update') {
1225
- throw new Error(
1226
- `server ${command} exited ${result.code}${serverCommandStderr(result.stderr)}`,
1227
- );
1228
- }
1229
- return parsed;
1279
+ return { exitCode: result.code, value: parsed };
1230
1280
  },
1231
1281
  verifyRunningProtocol: async (origin) => {
1232
1282
  const trust = await loadBorgServerTrust(origin);