@songsid/agend 2.1.7-beta.4 → 2.1.7-beta.6

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 (98) hide show
  1. package/README.md +2 -0
  2. package/README.zh-TW.md +2 -0
  3. package/dist/agent-cli-instructions.md +5 -0
  4. package/dist/agent-cli.js +13 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +1 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/backend/codex-session-lookup.d.ts +59 -0
  9. package/dist/backend/codex-session-lookup.js +139 -0
  10. package/dist/backend/codex-session-lookup.js.map +1 -0
  11. package/dist/backend/codex.d.ts +37 -9
  12. package/dist/backend/codex.js +204 -38
  13. package/dist/backend/codex.js.map +1 -1
  14. package/dist/backend/grok.js +51 -0
  15. package/dist/backend/grok.js.map +1 -1
  16. package/dist/backend/types.d.ts +12 -0
  17. package/dist/backend/types.js.map +1 -1
  18. package/dist/channel/mcp-tools.js +2 -0
  19. package/dist/channel/mcp-tools.js.map +1 -1
  20. package/dist/cli.js +137 -124
  21. package/dist/cli.js.map +1 -1
  22. package/dist/completion-install.d.ts +32 -0
  23. package/dist/completion-install.js +125 -1
  24. package/dist/completion-install.js.map +1 -1
  25. package/dist/config-validator.js +4 -0
  26. package/dist/config-validator.js.map +1 -1
  27. package/dist/daemon.d.ts +12 -1
  28. package/dist/daemon.js +186 -27
  29. package/dist/daemon.js.map +1 -1
  30. package/dist/delivery-outbox.d.ts +199 -6
  31. package/dist/delivery-outbox.js +602 -44
  32. package/dist/delivery-outbox.js.map +1 -1
  33. package/dist/delivery-outbox.test.js +268 -14
  34. package/dist/delivery-outbox.test.js.map +1 -1
  35. package/dist/delivery-queue-evidence.d.ts +15 -0
  36. package/dist/delivery-queue-evidence.js +35 -0
  37. package/dist/delivery-queue-evidence.js.map +1 -0
  38. package/dist/delivery-queue-evidence.test.d.ts +1 -0
  39. package/dist/delivery-queue-evidence.test.js +23 -0
  40. package/dist/delivery-queue-evidence.test.js.map +1 -0
  41. package/dist/delivery-reconciliation.d.ts +49 -0
  42. package/dist/delivery-reconciliation.js +296 -0
  43. package/dist/delivery-reconciliation.js.map +1 -0
  44. package/dist/delivery-reconciliation.test.d.ts +1 -0
  45. package/dist/delivery-reconciliation.test.js +411 -0
  46. package/dist/delivery-reconciliation.test.js.map +1 -0
  47. package/dist/fleet-manager.d.ts +10 -1
  48. package/dist/fleet-manager.js +109 -32
  49. package/dist/fleet-manager.js.map +1 -1
  50. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +10 -0
  51. package/dist/instance-lifecycle.d.ts +13 -0
  52. package/dist/instance-lifecycle.js +105 -21
  53. package/dist/instance-lifecycle.js.map +1 -1
  54. package/dist/instructions.d.ts +6 -0
  55. package/dist/instructions.js +9 -0
  56. package/dist/instructions.js.map +1 -1
  57. package/dist/locale.js +8 -0
  58. package/dist/locale.js.map +1 -1
  59. package/dist/ls-rows.d.ts +50 -0
  60. package/dist/ls-rows.js +152 -0
  61. package/dist/ls-rows.js.map +1 -0
  62. package/dist/outbound-handlers.d.ts +3 -0
  63. package/dist/outbound-handlers.js +38 -2
  64. package/dist/outbound-handlers.js.map +1 -1
  65. package/dist/outbound-schemas.d.ts +8 -0
  66. package/dist/outbound-schemas.js +15 -0
  67. package/dist/outbound-schemas.js.map +1 -1
  68. package/dist/pane-write-lock.d.ts +2 -0
  69. package/dist/pane-write-lock.js +19 -0
  70. package/dist/pane-write-lock.js.map +1 -1
  71. package/dist/quickstart.js +9 -3
  72. package/dist/quickstart.js.map +1 -1
  73. package/dist/remote-credential-check.d.ts +26 -7
  74. package/dist/remote-credential-check.js +157 -10
  75. package/dist/remote-credential-check.js.map +1 -1
  76. package/dist/scheduler/scheduler.d.ts +5 -1
  77. package/dist/scheduler/scheduler.js +60 -20
  78. package/dist/scheduler/scheduler.js.map +1 -1
  79. package/dist/scheduler/scheduler.test.js +41 -2
  80. package/dist/scheduler/scheduler.test.js.map +1 -1
  81. package/dist/scheduler/types.d.ts +1 -1
  82. package/dist/tmux-manager.d.ts +3 -3
  83. package/dist/tmux-manager.js +6 -6
  84. package/dist/tmux-manager.js.map +1 -1
  85. package/dist/tool-permissions.js +6 -6
  86. package/dist/tool-permissions.js.map +1 -1
  87. package/dist/transcript-monitor.d.ts +3 -1
  88. package/dist/transcript-monitor.js +15 -0
  89. package/dist/transcript-monitor.js.map +1 -1
  90. package/dist/transcript-sources.d.ts +9 -0
  91. package/dist/transcript-sources.js +14 -0
  92. package/dist/transcript-sources.js.map +1 -1
  93. package/dist/types.d.ts +6 -0
  94. package/dist/ui/dashboard.html +7 -5
  95. package/dist/ui/view.html +138 -45
  96. package/dist/usage/usage-api.js +30 -0
  97. package/dist/usage/usage-api.js.map +1 -1
  98. package/package.json +1 -1
@@ -286,6 +286,10 @@ export function deriveSpawnConcurrency(freeMemMB, cores) {
286
286
  const byMemory = Math.floor(freeMemMB / 300);
287
287
  return Math.max(2, Math.min(10, byMemory, Math.max(1, cores)));
288
288
  }
289
+ /** #926: how often reply obligations are checked, the owner-reminder grace, and the default overdue window. */
290
+ const REPLY_OBLIGATION_SWEEP_MS = 30_000;
291
+ const REPLY_REMINDER_GRACE_MS = 60_000;
292
+ const DEFAULT_REPLY_OVERDUE_MINUTES = 15;
289
293
  export class FleetManager {
290
294
  dataDir;
291
295
  static signalTarget = null;
@@ -303,6 +307,8 @@ export class FleetManager {
303
307
  deliveryPumpScheduled = false;
304
308
  deliveryPumpRunning = false;
305
309
  deliveryPumpTimer = null;
310
+ /** #926: periodic reply-obligation sweep (owner reminders, requester overdue notices). */
311
+ replyObligationTimer = null;
306
312
  activeDurableTargets = new Set();
307
313
  /** Live view of lifecycle.daemons — used throughout; not deprecated. */
308
314
  get daemons() { return this.lifecycle.daemons; }
@@ -580,9 +586,6 @@ export class FleetManager {
580
586
  if (!this.deliveryOutboxRecovered) {
581
587
  const recovered = outbox.recoverForBoot(this.managerBootId);
582
588
  this.logger.info({ ...recovered }, "Recovered durable delivery outbox for this process boot");
583
- if (recovered.uncertain > 0) {
584
- this.notifyFleetError(`${recovered.uncertain} durable delivery outcome(s) became uncertain during process restart; inspect the sender's delivery notice before retrying.`);
585
- }
586
589
  this.deliveryOutboxRecovered = true;
587
590
  }
588
591
  outbox.on("admitted", () => this.scheduleDeliveryOutboxPump());
@@ -598,10 +601,18 @@ export class FleetManager {
598
601
  }
599
602
  });
600
603
  outbox.on("generation_recovered", () => this.scheduleDeliveryOutboxPump());
604
+ this.replyObligationTimer = setInterval(() => this.sweepReplyObligations(), REPLY_OBLIGATION_SWEEP_MS);
605
+ this.replyObligationTimer.unref?.();
606
+ // #856: the text the target daemon received differs from what was
607
+ // admitted. Transport has never been seen to do this; a warning and the
608
+ // attempt row's flag are the whole response until it has.
609
+ outbox.on("content_digest_mismatch", (event) => {
610
+ this.logger.warn(event, "Delivered text differs from the admitted text (content digest mismatch)");
611
+ });
601
612
  outbox.on("expired", (event) => {
602
613
  this.scheduleDeliveryOutboxPump();
603
614
  if (event.count)
604
- this.notifyFleetError(`${event.count} durable delivery row(s) expired before their target became available; sender outcome notice queued.`);
615
+ this.notifyFleetError(`${event.count} durable delivery row(s) reached the 24-hour outbox bound; ${event.uncertain ?? 0} unresolved submissions are uncertain and sender notices were queued.`);
605
616
  });
606
617
  this.scheduleDeliveryOutboxPump();
607
618
  }
@@ -632,6 +643,10 @@ export class FleetManager {
632
643
  duplicate: !admitted.inserted,
633
644
  };
634
645
  }
646
+ queryDurableDeliveryStatus(callerInstance, selector) {
647
+ this.ensureDeliveryOutbox();
648
+ return this.deliveryOutbox.queryStatusForInstance(callerInstance, selector);
649
+ }
635
650
  getDaemonBootId(instanceName) {
636
651
  return this.daemons.get(instanceName)?.bootId;
637
652
  }
@@ -651,8 +666,38 @@ export class FleetManager {
651
666
  this.logger.error({ err, source: name, operationId: prior.operationId }, "Could not persist post-restart delivery outcome notice");
652
667
  }
653
668
  }
669
+ // #926: a restart kills the turn that was working on a request, and a
670
+ // resumed CLI does not pick it back up. Remind the owner once per restart.
671
+ try {
672
+ outbox.remindReplyObligations(name, { reason: "restart", graceMs: 0, since: new Date() });
673
+ }
674
+ catch (err) {
675
+ this.logger.error({ err, owner: name }, "Could not persist the post-restart reply reminder");
676
+ }
654
677
  this.scheduleDeliveryOutboxPump();
655
678
  }
679
+ /**
680
+ * #926: the two reply-obligation safety nets. Idle owners with an unanswered
681
+ * request get one reminder (after the grace); requesters get one overdue
682
+ * notice when the owner is not working and nothing came back in time.
683
+ */
684
+ sweepReplyObligations(now = new Date()) {
685
+ const outbox = this.deliveryOutbox;
686
+ if (!outbox || this.shuttingDown)
687
+ return;
688
+ const stateOf = (name) => this.daemons.get(name)?.getInstanceState();
689
+ try {
690
+ for (const name of this.daemons.keys()) {
691
+ if (stateOf(name) === "idle")
692
+ outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS, reason: "turn-ended" });
693
+ }
694
+ const minutes = this.fleetConfig?.defaults?.reply_overdue_minutes ?? DEFAULT_REPLY_OVERDUE_MINUTES;
695
+ outbox.notifyOverdueReplyObligations({ now, overdueMs: minutes * 60_000, ownerIdle: name => stateOf(name) !== "working" });
696
+ }
697
+ catch (err) {
698
+ this.logger.error({ err }, "Reply-obligation sweep failed");
699
+ }
700
+ }
656
701
  scheduleDeliveryOutboxPump(delayMs = 0) {
657
702
  if (!this.deliveryOutbox || this.shuttingDown)
658
703
  return;
@@ -716,26 +761,42 @@ export class FleetManager {
716
761
  const target = claimed.targetInstance;
717
762
  const attempt = claimed.attemptNo;
718
763
  const payload = { ...claimed.payload };
764
+ const laneReleased = this.waitForDurableLaneRelease(claimed);
719
765
  const rawMeta = payload.meta && typeof payload.meta === "object"
720
766
  ? payload.meta
721
767
  : {};
722
- payload.meta = {
723
- ...rawMeta,
724
- delivery_id: claimed.deliveryId,
725
- delivery_attempt: String(claimed.attemptNo),
726
- from_instance: typeof rawMeta.from_instance === "string" && rawMeta.from_instance
727
- ? rawMeta.from_instance
728
- : claimed.sourceInstance,
729
- correlation_id: claimed.correlationId ?? String(rawMeta.correlation_id ?? ""),
730
- };
731
768
  // Arm the visible liveness alert as soon as this generation owns the lane,
732
769
  // including time spent waiting for a handoff/idle gate to return.
733
- const laneReleased = this.waitForDurableLaneRelease(claimed);
734
770
  try {
735
- const sent = await this.deliverToInstance(target, payload, {
736
- isCrossInstance: true,
737
- waitForIdle: claimed.kind !== "steer",
738
- });
771
+ let sent;
772
+ if (claimed.kind === "raw_paste") {
773
+ if (payload.type !== "raw_paste" || typeof payload.content !== "string") {
774
+ throw new Error("Durable raw_paste row has an invalid payload");
775
+ }
776
+ // Preserve the schedule's exact bytes and raw route. The target daemon
777
+ // owns readiness, paste, one Enter, and the fenced outbox transition.
778
+ sent = await this.deliverToInstance(target, {
779
+ type: "raw_paste",
780
+ content: payload.content,
781
+ delivery_id: claimed.deliveryId,
782
+ delivery_attempt: String(claimed.attemptNo),
783
+ }, { waitForIdle: false });
784
+ }
785
+ else {
786
+ payload.meta = {
787
+ ...rawMeta,
788
+ delivery_id: claimed.deliveryId,
789
+ delivery_attempt: String(claimed.attemptNo),
790
+ from_instance: typeof rawMeta.from_instance === "string" && rawMeta.from_instance
791
+ ? rawMeta.from_instance
792
+ : claimed.sourceInstance,
793
+ correlation_id: claimed.correlationId ?? String(rawMeta.correlation_id ?? ""),
794
+ };
795
+ sent = await this.deliverToInstance(target, payload, {
796
+ isCrossInstance: true,
797
+ waitForIdle: claimed.kind !== "steer",
798
+ });
799
+ }
739
800
  if (!sent) {
740
801
  const current = outbox.get(claimed.deliveryId);
741
802
  if (current?.state === "delivering") {
@@ -3043,7 +3104,7 @@ export class FleetManager {
3043
3104
  ...DEFAULT_SCHEDULER_CONFIG,
3044
3105
  ...this.fleetConfig?.defaults.scheduler,
3045
3106
  };
3046
- this.scheduler = new Scheduler(join(this.dataDir, "scheduler.db"), (schedule) => this.handleScheduleTrigger(schedule), schedulerConfig, (name) => this.fleetConfig?.instances?.[name] != null || !!this.classicChannels?.getAll().some(ch => ch.instanceName === name));
3107
+ this.scheduler = new Scheduler(join(this.dataDir, "scheduler.db"), (schedule, runId) => this.handleScheduleTrigger(schedule, runId), schedulerConfig, (name) => this.fleetConfig?.instances?.[name] != null || !!this.classicChannels?.getAll().some(ch => ch.instanceName === name));
3047
3108
  this.scheduler.init();
3048
3109
  this.logger.info("Scheduler initialized");
3049
3110
  // Tips share the daily-report clock but remain an independent internal
@@ -5600,7 +5661,7 @@ export class FleetManager {
5600
5661
  }
5601
5662
  }
5602
5663
  // ===================== Scheduler =====================
5603
- async handleScheduleTrigger(schedule) {
5664
+ async handleScheduleTrigger(schedule, stableRunId = randomUUID()) {
5604
5665
  const { target, reply_chat_id, reply_thread_id, message, label, id, source, silent } = schedule;
5605
5666
  const RATE_LIMIT_DEFER_THRESHOLD = 85;
5606
5667
  const rl = this.statuslineWatcher.getRateLimits(target);
@@ -5618,20 +5679,32 @@ export class FleetManager {
5618
5679
  }
5619
5680
  // Silent mode: paste directly to tmux pane — no channel message.
5620
5681
  if (silent) {
5621
- const ipc = this.instanceIpcClients.get(target);
5622
- if (ipc) {
5623
- ipc.send({
5682
+ // The outbox commit is the schedule's durable acceptance point. The run
5683
+ // key is the scheduled fire instant (or an invocation UUID for a manual
5684
+ // trigger), so restart/catch-up reuses the same raw paste row.
5685
+ this.ensureDeliveryOutbox();
5686
+ const runKey = encodeURIComponent(stableRunId);
5687
+ const sourceKey = `schedule:${encodeURIComponent(id)}:${runKey}:${encodeURIComponent(target)}:raw_paste`;
5688
+ const operationId = `schedule:${id}:${stableRunId}`;
5689
+ const sourceBootId = this.daemons.get(source)?.bootId ?? this.managerBootId;
5690
+ const admitted = this.deliveryOutbox.admit({
5691
+ operationId,
5692
+ sourceKey,
5693
+ sourceInstance: source,
5694
+ sourceDaemonBootId: sourceBootId,
5695
+ targetInstance: target,
5696
+ kind: "raw_paste",
5697
+ correlationId: operationId,
5698
+ payload: {
5624
5699
  type: "raw_paste",
5625
5700
  content: message,
5626
- delivery_epoch: this.getDeliveryEpoch(target),
5627
- });
5628
- this.scheduler.recordRun(id, "delivered");
5629
- this.logger.info({ target, scheduleId: id, label }, "Silent schedule injected via raw_paste");
5630
- }
5631
- else {
5632
- this.scheduler.recordRun(id, "instance_offline", "IPC not connected");
5633
- this.logger.warn({ target, scheduleId: id }, "Silent schedule: IPC not connected, skipping");
5634
- }
5701
+ schedule_id: id,
5702
+ schedule_run_id: stableRunId,
5703
+ },
5704
+ });
5705
+ this.scheduler.recordRun(id, "queued", `durable raw_paste delivery_id=${admitted.delivery.deliveryId}`);
5706
+ this.logger.info({ target, scheduleId: id, runId: stableRunId, deliveryId: admitted.delivery.deliveryId, duplicate: !admitted.inserted }, "Silent schedule durably admitted as raw_paste");
5707
+ this.scheduleDeliveryOutboxPump();
5635
5708
  return;
5636
5709
  }
5637
5710
  const schedulerDefaults = this.fleetConfig?.defaults.scheduler;
@@ -11383,6 +11456,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
11383
11456
  clearInterval(this.eventLogPruneTimer);
11384
11457
  this.eventLogPruneTimer = null;
11385
11458
  }
11459
+ if (this.replyObligationTimer) {
11460
+ clearInterval(this.replyObligationTimer);
11461
+ this.replyObligationTimer = null;
11462
+ }
11386
11463
  if (this.logRotateTimer) {
11387
11464
  clearInterval(this.logRotateTimer);
11388
11465
  this.logRotateTimer = null;