borgmcp 3.8.0 → 3.10.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 (51) hide show
  1. package/SECURITY.md +1 -1
  2. package/dist/claude.js +3 -3
  3. package/dist/claude.js.map +1 -1
  4. package/dist/cli-help.js +5 -5
  5. package/dist/cli-help.js.map +1 -1
  6. package/dist/cubes.js +2 -2
  7. package/dist/cubes.js.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/log-stream.d.ts +6 -2
  12. package/dist/log-stream.d.ts.map +1 -1
  13. package/dist/log-stream.js +36 -11
  14. package/dist/log-stream.js.map +1 -1
  15. package/dist/opencode-drone.d.ts +7 -2
  16. package/dist/opencode-drone.d.ts.map +1 -1
  17. package/dist/opencode-drone.js +291 -64
  18. package/dist/opencode-drone.js.map +1 -1
  19. package/dist/opencode-plugin.d.ts.map +1 -1
  20. package/dist/opencode-plugin.js +12 -2
  21. package/dist/opencode-plugin.js.map +1 -1
  22. package/dist/regen-format.d.ts.map +1 -1
  23. package/dist/regen-format.js +15 -1
  24. package/dist/regen-format.js.map +1 -1
  25. package/dist/remote-client.d.ts +13 -0
  26. package/dist/remote-client.d.ts.map +1 -1
  27. package/dist/remote-client.js +25 -0
  28. package/dist/remote-client.js.map +1 -1
  29. package/dist/seat-commands.js +12 -12
  30. package/dist/seat-commands.js.map +1 -1
  31. package/dist/stream-status.d.ts.map +1 -1
  32. package/dist/stream-status.js +1 -0
  33. package/dist/stream-status.js.map +1 -1
  34. package/dist/unknown-subcommand.d.ts +1 -1
  35. package/dist/unknown-subcommand.d.ts.map +1 -1
  36. package/dist/unknown-subcommand.js +1 -1
  37. package/dist/unknown-subcommand.js.map +1 -1
  38. package/docs/RELEASING.md +96 -25
  39. package/package.json +2 -2
  40. package/src/claude.ts +3 -3
  41. package/src/cli-help.ts +5 -5
  42. package/src/cubes.ts +2 -2
  43. package/src/index.ts +4 -1
  44. package/src/log-stream.ts +49 -12
  45. package/src/opencode-drone.ts +328 -65
  46. package/src/opencode-plugin.ts +13 -1
  47. package/src/regen-format.ts +15 -1
  48. package/src/remote-client.ts +36 -0
  49. package/src/seat-commands.ts +12 -12
  50. package/src/stream-status.ts +3 -0
  51. package/src/unknown-subcommand.ts +1 -1
package/src/log-stream.ts CHANGED
@@ -59,6 +59,7 @@ import { formatCubeActivityWakeMessage } from './cube-activity-wake-copy.js';
59
59
  import { readBoundedResponseBody } from './server-response.js';
60
60
  import { BorgServerError } from './server-errors.js';
61
61
  import { markSeatRejected } from './seats.js';
62
+ import { hasPendingWakeEntry as hasPendingDurableWakeEntry } from './remote-client.js';
62
63
  import {
63
64
  acquireStreamLease,
64
65
  readOwnershipSnapshot,
@@ -124,13 +125,16 @@ function isProcessAlive(pid: number): boolean {
124
125
  * Used by defaultDeps when no explicit injectOpenCode is supplied.
125
126
  */
126
127
  let _moduleInjectOpenCode:
127
- | ((text: string, entryId: string, allowSubmit: boolean) => Promise<boolean>)
128
+ | ((text: string, entryId: string, allowSubmit: boolean, sourceEntryId?: string) => Promise<boolean>)
128
129
  | undefined;
130
+ let _moduleSettleOpenCodeEntry: ((sourceEntryId: string) => void) | undefined;
129
131
 
130
132
  export function setModuleInjectOpenCode(
131
- fn: (text: string, entryId: string, allowSubmit: boolean) => Promise<boolean>
133
+ fn: (text: string, entryId: string, allowSubmit: boolean, sourceEntryId?: string) => Promise<boolean>,
134
+ settle: (sourceEntryId: string) => void,
132
135
  ): void {
133
136
  _moduleInjectOpenCode = fn;
137
+ _moduleSettleOpenCodeEntry = settle;
134
138
  }
135
139
 
136
140
  /**
@@ -403,8 +407,13 @@ export interface StreamDeps {
403
407
  injectOpenCode?: (
404
408
  text: string,
405
409
  entryId: string,
406
- allowSubmit: boolean
410
+ allowSubmit: boolean,
411
+ sourceEntryId?: string,
407
412
  ) => Promise<boolean>;
413
+ /** Inspect whether one retry source remains beyond the durable unread cursor. */
414
+ hasPendingWakeEntry?: (active: ActiveCube, entryId: string) => Promise<boolean>;
415
+ /** Clear in-process retry diagnostics after the durable entry is consumed. */
416
+ settleOpenCodeEntry?: (sourceEntryId: string) => void;
408
417
  }
409
418
 
410
419
  const defaultDeps: Required<StreamDeps> = {
@@ -420,10 +429,15 @@ const defaultDeps: Required<StreamDeps> = {
420
429
  abortSignal: new AbortController().signal,
421
430
  ownerDeps: {},
422
431
  ownerStaleMs: 70_000,
423
- injectOpenCode: (text, entryId, allowSubmit) =>
432
+ injectOpenCode: (text, entryId, allowSubmit, sourceEntryId) =>
424
433
  _moduleInjectOpenCode
425
- ? _moduleInjectOpenCode(text, entryId, allowSubmit)
434
+ ? _moduleInjectOpenCode(text, entryId, allowSubmit, sourceEntryId)
426
435
  : Promise.resolve(false),
436
+ hasPendingWakeEntry: (active, entryId) => hasPendingDurableWakeEntry(
437
+ active as Parameters<typeof hasPendingDurableWakeEntry>[0],
438
+ entryId,
439
+ ),
440
+ settleOpenCodeEntry: (sourceEntryId) => _moduleSettleOpenCodeEntry?.(sourceEntryId),
427
441
  };
428
442
 
429
443
  // ------------------------------------------------------------------
@@ -678,6 +692,8 @@ export async function streamOnce(
678
692
  hwmDivergenceGraceMs,
679
693
  abortSignal,
680
694
  injectOpenCode,
695
+ hasPendingWakeEntry,
696
+ settleOpenCodeEntry,
681
697
  } = { ...defaultDeps, ...deps };
682
698
 
683
699
  assertUuidShape(active.cubeId, 'cube_id');
@@ -819,6 +835,17 @@ export async function streamOnce(
819
835
  pendingHwmDivergence = { hwm, timer };
820
836
  };
821
837
 
838
+ const shouldDeliverWakeRetry = async (entryId: string): Promise<boolean> => {
839
+ try {
840
+ if (await hasPendingWakeEntry(active, entryId)) return true;
841
+ settleOpenCodeEntry(entryId);
842
+ } catch {
843
+ // A retry is never authoritative enough to cross the model boundary on
844
+ // its own. The durable inbox and a later retry remain available.
845
+ }
846
+ return false;
847
+ };
848
+
822
849
  // Bounded recent-id set for replay-on-reconnect dedup per spec §(3).
823
850
  // Set + FIFO array for O(1) membership + bounded memory.
824
851
  const recentIds = new Set<string>();
@@ -844,6 +871,10 @@ export async function streamOnce(
844
871
  const line = formatInboxLine(withSseEventId(ev.data, ev.id));
845
872
  const deliveryId = ev.wake_nonce ?? ev.id;
846
873
  const isReping = ev.wake_nonce !== undefined;
874
+ if (isReping && !(await shouldDeliverWakeRetry(ev.id))) {
875
+ markEventPersisted(ev.id, ev.data?.created_at ?? '');
876
+ return 'persisted-skip';
877
+ }
847
878
  const eventHwm = ev.data?.created_at
848
879
  ? { id: ev.id, created_at: ev.data.created_at }
849
880
  : null;
@@ -867,12 +898,11 @@ export async function streamOnce(
867
898
  await hasInboxEntryId(active.cubeId, active.droneId, ev.id, line)
868
899
  );
869
900
  if (alreadyPersisted) {
870
- // An ordinary entry at/before the persisted cursor is already delivered
871
- // and must not re-enter the OpenCode queue. Re-pings are the explicit
872
- // exception: each wake nonce is a new delivery identity even when the
873
- // underlying inbox line is already durable.
901
+ // An ordinary entry at/before the persisted cursor must not re-enter the
902
+ // OpenCode queue. A still-unread re-ping may reconcile the prior attempt;
903
+ // its source entry ID prevents a new nonce from resubmitting that prompt.
874
904
  if (isReping) {
875
- await injectOpenCode(formatOpenCodeWakePrompt(line), deliveryId, true);
905
+ await injectOpenCode(formatOpenCodeWakePrompt(line), deliveryId, true, ev.id);
876
906
  }
877
907
  markEventPersisted(ev.id, ev.data?.created_at ?? '');
878
908
  return 'persisted-skip';
@@ -880,7 +910,10 @@ export async function streamOnce(
880
910
  // The inbox append is the durable record. OpenCode injection is only the
881
911
  // wake attempt and may return before the agent finishes processing.
882
912
  await appendLine(active.cubeId, active.droneId, line);
883
- if (!(await injectOpenCode(formatOpenCodeWakePrompt(line), deliveryId, true))) {
913
+ const openCodeDelivered = isReping
914
+ ? await injectOpenCode(formatOpenCodeWakePrompt(line), deliveryId, true, ev.id)
915
+ : await injectOpenCode(formatOpenCodeWakePrompt(line), deliveryId, true);
916
+ if (!openCodeDelivered) {
884
917
  if (ev.wake_nonce === undefined) wakeCodex(formatCodexWakePrompt(line));
885
918
  else wakeCodex(formatCodexWakePrompt(line), ev.wake_nonce);
886
919
  }
@@ -1198,7 +1231,11 @@ export async function streamOnce(
1198
1231
  if (isRecentWakeReplay) {
1199
1232
  const line = formatInboxLine(withSseEventId(event.data, event.id));
1200
1233
  const wakeNonce = event.wake_nonce;
1201
- if (wakeNonce !== undefined && !(await injectOpenCode(formatOpenCodeWakePrompt(line), wakeNonce, true))) {
1234
+ if (
1235
+ wakeNonce !== undefined &&
1236
+ await shouldDeliverWakeRetry(event.id) &&
1237
+ !(await injectOpenCode(formatOpenCodeWakePrompt(line), wakeNonce, true, event.id))
1238
+ ) {
1202
1239
  wakeCodex(formatCodexWakePrompt(line), wakeNonce);
1203
1240
  }
1204
1241
  continue;