@songsid/agend 2.3.2 → 2.3.3-beta.1

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/dist/agent-cli-instructions.md +2 -2
  2. package/dist/agent-whats-new.d.ts +21 -0
  3. package/dist/agent-whats-new.js +52 -23
  4. package/dist/agent-whats-new.js.map +1 -1
  5. package/dist/backend/claude-code.d.ts +13 -1
  6. package/dist/backend/claude-code.js +20 -1
  7. package/dist/backend/claude-code.js.map +1 -1
  8. package/dist/backend/kiro-reconcile-store.js +8 -1
  9. package/dist/backend/kiro-reconcile-store.js.map +1 -1
  10. package/dist/backend/kiro.d.ts +0 -5
  11. package/dist/backend/kiro.js +0 -10
  12. package/dist/backend/kiro.js.map +1 -1
  13. package/dist/backend/types.d.ts +7 -4
  14. package/dist/backend/types.js.map +1 -1
  15. package/dist/cli.js +2 -2
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config-validator.js +3 -1
  18. package/dist/config-validator.js.map +1 -1
  19. package/dist/cross-instance-envelope.d.ts +10 -5
  20. package/dist/cross-instance-envelope.js +20 -17
  21. package/dist/cross-instance-envelope.js.map +1 -1
  22. package/dist/daemon.d.ts +59 -63
  23. package/dist/daemon.js +275 -313
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/delivery-outbox.d.ts +27 -22
  26. package/dist/delivery-outbox.js +109 -161
  27. package/dist/delivery-outbox.js.map +1 -1
  28. package/dist/delivery-outbox.test.js +10 -25
  29. package/dist/delivery-outbox.test.js.map +1 -1
  30. package/dist/fleet-manager.d.ts +4 -12
  31. package/dist/fleet-manager.js +28 -92
  32. package/dist/fleet-manager.js.map +1 -1
  33. package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
  34. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
  35. package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
  36. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +1 -1
  37. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +1 -1
  38. package/dist/instance-lifecycle.js +21 -5
  39. package/dist/instance-lifecycle.js.map +1 -1
  40. package/dist/instructions.js +6 -7
  41. package/dist/instructions.js.map +1 -1
  42. package/dist/kiro-reconcile.js +3 -3
  43. package/dist/kiro-reconcile.js.map +1 -1
  44. package/dist/locale.js +4 -16
  45. package/dist/locale.js.map +1 -1
  46. package/dist/log-service.js +1 -1
  47. package/dist/log-service.js.map +1 -1
  48. package/dist/outbound-handlers.js +1 -1
  49. package/dist/outbound-handlers.js.map +1 -1
  50. package/dist/outbound-schemas.js +1 -1
  51. package/dist/outbound-schemas.js.map +1 -1
  52. package/dist/scheduler/task-board.d.ts +17 -12
  53. package/dist/scheduler/task-board.js +29 -25
  54. package/dist/scheduler/task-board.js.map +1 -1
  55. package/dist/sd-notify.d.ts +13 -14
  56. package/dist/sd-notify.js +62 -28
  57. package/dist/sd-notify.js.map +1 -1
  58. package/dist/settings-api.d.ts +0 -1
  59. package/dist/settings-api.js +1 -14
  60. package/dist/settings-api.js.map +1 -1
  61. package/dist/settings-effect.js +0 -2
  62. package/dist/settings-effect.js.map +1 -1
  63. package/dist/task-board-waker.d.ts +40 -1
  64. package/dist/task-board-waker.js +110 -9
  65. package/dist/task-board-waker.js.map +1 -1
  66. package/dist/tmux-control.d.ts +1 -1
  67. package/dist/tmux-control.js +3 -2
  68. package/dist/tmux-control.js.map +1 -1
  69. package/dist/tmux-manager.js +18 -16
  70. package/dist/tmux-manager.js.map +1 -1
  71. package/dist/tmux-read.d.ts +2 -0
  72. package/dist/tmux-read.js +7 -1
  73. package/dist/tmux-read.js.map +1 -1
  74. package/dist/topic-commands.d.ts +2 -0
  75. package/dist/topic-commands.js +2 -1
  76. package/dist/topic-commands.js.map +1 -1
  77. package/dist/types.d.ts +1 -5
  78. package/dist/ui/panel-log.js +2 -2
  79. package/dist/ui/settings-appearance.js +3 -35
  80. package/dist/ui/settings-registry.js +0 -1
  81. package/dist/ui/settings-strings.js +2 -2
  82. package/dist/update-check.d.ts +13 -4
  83. package/dist/update-check.js +17 -4
  84. package/dist/update-check.js.map +1 -1
  85. package/dist/web-experimental.d.ts +6 -2
  86. package/dist/web-experimental.js +8 -4
  87. package/dist/web-experimental.js.map +1 -1
  88. package/dist/web-settings-patch.d.ts +0 -1
  89. package/dist/web-settings-patch.js +3 -4
  90. package/dist/web-settings-patch.js.map +1 -1
  91. package/dist/workflow-templates/default.md +2 -2
  92. package/dist/working-recovery.d.ts +0 -2
  93. package/dist/working-recovery.js +2 -23
  94. package/dist/working-recovery.js.map +1 -1
  95. package/package.json +1 -1
  96. package/dist/upgrade-notices.d.ts +0 -17
  97. package/dist/upgrade-notices.js +0 -93
  98. package/dist/upgrade-notices.js.map +0 -1
@@ -52,7 +52,7 @@ import { applyKiroMigration, kiroMigrationPrompt, planKiroMigration } from "./ki
52
52
  import { fleetLabel } from "./fleet-label.js";
53
53
  import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, sameUpdateMarker, setUpdateProgressStage, updateMarkerIdentity, updateProgressOperation, updateReceiptOf, } from "./update-marker.js";
54
54
  import { formatUpdateProgress } from "./update-progress.js";
55
- import { sdNotify, sdNotifyBlocking, setSdNotifyWarn } from "./sd-notify.js";
55
+ import { sdNotify, sdNotifyBlocking, setSdNotifyWarn, startSdWatchdog } from "./sd-notify.js";
56
56
  import { StartupMemoryDeadline, fleetStopConcurrency } from "./startup-memory-deadline.js";
57
57
  import { readFleetMemory } from "./process-memory.js";
58
58
  import { MemoryPressure } from "./memory-pressure.js";
@@ -94,7 +94,7 @@ import { processAttachments } from "./channel/attachment-handler.js";
94
94
  import { routeToolCall } from "./channel/tool-router.js";
95
95
  import { Scheduler } from "./scheduler/index.js";
96
96
  import { boardActivity, boardCardLimit, boardToolAction } from "./task-board-tool.js";
97
- import { TaskBoardWaker } from "./task-board-waker.js";
97
+ import { TaskBoardWaker, namesCard } from "./task-board-waker.js";
98
98
  import { TaskBoardError, waitsForPerson, wrapUpGap } from "./scheduler/task-board.js";
99
99
  import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./scheduler/retry-label.js";
100
100
  import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
@@ -254,7 +254,6 @@ import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
254
254
  import { manualCleanupMessage, reapStaleTunnel } from "./tunnel/lease.js";
255
255
  import { buildToolPermissionsNotice } from "./tool-permissions-notice.js";
256
256
  import { NeedsYouHub } from "./needs-you-hub.js";
257
- import { WEB_CHAT_NOTICE, claimNotice, hasWebChat, releaseNotice, upgradeNoticesPath } from "./upgrade-notices.js";
258
257
  import { GENERAL_NAME, GENERAL_PAUSE_ERROR, adoptGeneralByName, freeGeneralName, isGeneralInstance } from "./general-instance.js";
259
258
  import { buildOrgChart } from "./web-org.js";
260
259
  import { topMemoryHogs } from "./memory-hogs.js";
@@ -612,7 +611,6 @@ export function deriveSpawnConcurrency(freeMemMB, cores) {
612
611
  /** #926: how often reply obligations are checked, the owner-reminder grace, and the default overdue window. */
613
612
  const REPLY_OBLIGATION_SWEEP_MS = 30_000;
614
613
  const REPLY_REMINDER_GRACE_MS = 60_000;
615
- const DEFAULT_REPLY_OVERDUE_MINUTES = 15;
616
614
  /**
617
615
  * The filter `list_emojis` and `list_stickers` take (#1226). Values may come from the agent CLI as strings, so a
618
616
  * limit is read as a number and the flags accept "true". No limit means all of them — never zero.
@@ -676,32 +674,6 @@ export class FleetManager {
676
674
  laneFree: name => !this.isInstanceRestarting(name) && !this.activeDurableTargets.has(name)
677
675
  && outbox.countForTarget(name, ["queued", "retry_wait", "delivering", "submission_started", "reconciliation_pending"]) === 0,
678
676
  admit: (episode, tasks, advisory) => outbox.admitWorkingRecovery(episode, buildWorkingRecoveryPrompt(tasks, this.workingRecoveryCause, advisory), advisory),
679
- summary: (group, rows) => {
680
- const general = this.findGeneralInstance();
681
- if (!general || !this.daemons.has(general))
682
- return;
683
- // Our own informational notice must not supersede General's recovery
684
- // as unrelated pending work before it crosses the input boundary.
685
- const generalEpisode = store.get(general);
686
- if (generalEpisode?.outcome === "admitted" && generalEpisode.deliveryId) {
687
- const state = outbox.get(generalEpisode.deliveryId)?.state;
688
- if (!state || !["delivered", "uncertain", "failed", "cancelled"].includes(state))
689
- return;
690
- }
691
- const deferredAdvisories = rows.filter(row => row.outcome === "deferred"
692
- && (this.daemons.get(row.instance)?.getWorkingRecoveryState?.().background ?? null) === null)
693
- .map(row => `${row.instance} (${row.reason})`);
694
- const posted = store.summary(group, () => outbox.admitSystemNotice(general, `working-recovery-summary:${group}`, `[system:working-recovery-summary] Observed cause: unknown interruption${this.workingRecoveryCause === "unknown_interruption" ? "" : `; prior requested action: ${this.workingRecoveryCause}`}. ${rows.length} interrupted instances; continuation queued ${rows.filter(r => r.outcome === "admitted").length}, excluded ${rows.filter(r => r.outcome === "excluded").length}, still deferred ${rows.filter(r => r.outcome === "deferred").length}. ${rows.filter(r => r.reason === "advisory_background_unknown").length ? `Advisory, background unknown: ${rows.filter(r => r.reason === "advisory_background_unknown").map(r => r.instance).join(", ")}. ` : ""}${deferredAdvisories.length ? `Deferred advisory candidates (background unknown; all other gates still apply): ${deferredAdvisories.join(", ")}. ` : ""}Details are in the Log. Deferred work remains eligible; no action or confirmation is requested.`));
695
- if (posted) {
696
- const counts = { interrupted: rows.length, queued: rows.filter(row => row.outcome === "admitted").length,
697
- excluded: rows.filter(row => row.outcome === "excluded").length, deferred: rows.filter(row => row.outcome === "deferred").length };
698
- const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
699
- this.logger.info({ group, ...counts, observedCause: "unknown_interruption", requestedAction }, "Interrupted work recovery summary queued for General");
700
- this.eventLog?.insert(general, "working_state", { reason: "recovery_summary", group, ...counts,
701
- observedCause: "unknown_interruption", requestedAction,
702
- message: `Interrupted work summary queued for General: ${counts.interrupted} interrupted; ${counts.queued} queued, ${counts.excluded} excluded, ${counts.deferred} deferred.` });
703
- }
704
- },
705
677
  log: (episode, outcome, reason) => {
706
678
  const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
707
679
  this.logger.info({ name: episode.instance, episode: episode.key, outcome, reason,
@@ -2604,27 +2576,8 @@ export class FleetManager {
2604
2576
  const daemon = this.daemons.get(name);
2605
2577
  if (daemon?.bootId === daemonBootId)
2606
2578
  this.listenForWorkingState(name, daemon);
2607
- const outbox = this.deliveryOutbox;
2608
- if (!outbox)
2609
- return;
2610
- for (const prior of outbox.getUnansweredAccepted(name, daemonBootId)) {
2611
- try {
2612
- outbox.admitPostRestartOutcomeNotice(prior, daemonBootId);
2613
- }
2614
- catch (err) {
2615
- this.logger.error({ err, source: name, operationId: prior.operationId }, "Could not persist post-restart delivery outcome notice");
2616
- }
2617
- }
2618
- // #926: a restart kills the turn that was working on a request, and a
2619
- // resumed CLI does not pick it back up. Remind the owner once per restart.
2620
- try {
2621
- const now = new Date();
2622
- this.expireReplyObligations(now);
2623
- outbox.remindReplyObligations(name, { reason: "restart", graceMs: 0, now, since: now, restartId: daemonBootId });
2624
- }
2625
- catch (err) {
2626
- this.logger.error({ err, owner: name }, "Could not persist the post-restart reply reminder");
2627
- }
2579
+ // #2802: no post-restart notices. The outbox still delivers or fails a row accepted before the restart, and an open
2580
+ // request is reminded by the ordinary turn-ended nudge (sweepReplyObligations) once the owner is idle.
2628
2581
  this.scheduleDeliveryOutboxPump();
2629
2582
  }
2630
2583
  workingStateListeners;
@@ -2708,9 +2661,8 @@ export class FleetManager {
2708
2661
  }
2709
2662
  }
2710
2663
  /**
2711
- * #926: the two reply-obligation safety nets. Idle owners with an unanswered
2712
- * request get one reminder (after the grace); requesters get one overdue
2713
- * notice when the owner is not working and nothing came back in time.
2664
+ * #926: idle owners with an unanswered request get one reminder (after the grace). #2802: the only reply notice —
2665
+ * requesters are no longer told about overdue or expired asks (they can check delivery_status / describe_instance).
2714
2666
  */
2715
2667
  sweepReplyObligations(now = new Date()) {
2716
2668
  const outbox = this.deliveryOutbox;
@@ -2721,10 +2673,8 @@ export class FleetManager {
2721
2673
  this.expireReplyObligations(now);
2722
2674
  for (const name of this.daemons.keys()) {
2723
2675
  if (stateOf(name) === "idle")
2724
- outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS, reason: "turn-ended" });
2676
+ outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS });
2725
2677
  }
2726
- const minutes = this.fleetConfig?.defaults?.reply_overdue_minutes ?? DEFAULT_REPLY_OVERDUE_MINUTES;
2727
- outbox.notifyOverdueReplyObligations({ now, overdueMs: minutes * 60_000, ownerIdle: name => stateOf(name) !== "working" });
2728
2678
  }
2729
2679
  catch (err) {
2730
2680
  this.logger.error({ err }, "Reply-obligation sweep failed");
@@ -8170,7 +8120,7 @@ export class FleetManager {
8170
8120
  setSdNotifyWarn((fields, message) => this.logger.warn(fields, message));
8171
8121
  if (this.watchdogTimer)
8172
8122
  clearInterval(this.watchdogTimer);
8173
- this.watchdogTimer = setInterval(() => sdNotify("WATCHDOG=1"), 30_000);
8123
+ this.watchdogTimer = startSdWatchdog();
8174
8124
  this.eventLoopWatch?.stop();
8175
8125
  this.eventLoopWatch = startEventLoopWatch({ logger: this.logger });
8176
8126
  try {
@@ -8734,8 +8684,6 @@ export class FleetManager {
8734
8684
  }
8735
8685
  await this.sendFleetStartCompletionFallback(String(fleet.channel.group_id), text, generalThreadId != null ? String(generalThreadId) : undefined);
8736
8686
  }
8737
- // After "fleet ready", so the notice is not the first thing people see of a restart.
8738
- void this.announceWebChatOnce(agendVersion);
8739
8687
  // #named-link-ttl: a named link with auto_open comes back now — after the web listener and the chat connections.
8740
8688
  this.startPublicLinkAutoOpen();
8741
8689
  }
@@ -10870,34 +10818,6 @@ export class FleetManager {
10870
10818
  * their fleet is organised. Both stay theirs — this only makes sure they are
10871
10819
  * not discovered by an agent failing at three in the morning.
10872
10820
  */
10873
- /**
10874
- * #1366: the first time a fleet runs 2.2 or later, tell each chat platform's General — once — that its agents can
10875
- * now be talked to from a browser. Claimed in upgrade-notices.json before the send, released if the send fails.
10876
- * Platforms with nowhere to post fleet notices (no group, or a Discord fleet with no General channel) are skipped
10877
- * and not recorded, so they are told once they have one.
10878
- */
10879
- async announceWebChatOnce(version) {
10880
- if (!hasWebChat(version))
10881
- return;
10882
- const path = upgradeNoticesPath(this.dataDir);
10883
- for (const [adapterId, world] of this.worlds) {
10884
- if (this.shuttingDown)
10885
- return;
10886
- const target = this.fleetNoticeTarget(adapterId);
10887
- if (!target)
10888
- continue;
10889
- if (!claimNotice(path, WEB_CHAT_NOTICE, adapterId))
10890
- continue;
10891
- try {
10892
- await world.adapter.sendText(target.chatId, t("upgrade.web_chat"), target.opts);
10893
- this.logger.info({ adapterId, notice: WEB_CHAT_NOTICE }, "Announced web chat in General");
10894
- }
10895
- catch (err) {
10896
- const released = releaseNotice(path, WEB_CHAT_NOTICE, adapterId);
10897
- this.logger.warn({ err, adapterId, released }, "Could not announce web chat in General — will try at the next start");
10898
- }
10899
- }
10900
- }
10901
10821
  announceToolPermissionsChange() {
10902
10822
  try {
10903
10823
  const thirtyDaysAgo = new Date(Date.now() - 30 * 864e5).toISOString().slice(0, 19).replace("T", " ");
@@ -18321,8 +18241,8 @@ export class FleetManager {
18321
18241
  * The task board's live side: every committed change goes to the open pages (never to a guest) — the board's new
18322
18242
  * revision and the card numbers it touched; not who touched them (the page does not need it, and a public-link page
18323
18243
  * must not learn a hidden instance's name from it). Its news goes to the instances whose cards someone else changed:
18324
- * one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken
18325
- * (task-board-waker.ts).
18244
+ * one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken, or
18245
+ * withdrawn once its cards changed again (#2801; task-board-waker.ts).
18326
18246
  */
18327
18247
  attachTaskBoard(db) {
18328
18248
  this.taskBoardWaker?.stop();
@@ -18331,6 +18251,19 @@ export class FleetManager {
18331
18251
  const waker = this.taskBoardWaker = new TaskBoardWaker({
18332
18252
  deliver: (instance, message, key) => this.admitTaskBoardNotice(instance, message, key),
18333
18253
  isInstance: (name) => this.fleetConfig?.instances?.[name] != null,
18254
+ // #2785 (c), #2801: the maker's own message already told it; the board's line would repeat it.
18255
+ messagedAbout: (from, to, codes, sinceMs) => {
18256
+ const outbox = this.deliveryOutbox;
18257
+ if (!outbox?.isOpen)
18258
+ return false;
18259
+ return outbox.recentTextsFrom(to, from, new Date(sinceMs).toISOString()).some((text) => codes.some((code) => namesCard(text, code)));
18260
+ },
18261
+ // #2801: a notice still waiting whose cards have all changed again is not delivered.
18262
+ withdraw: (prefix, stale, reason) => {
18263
+ const outbox = this.deliveryOutbox;
18264
+ return outbox?.isOpen ? outbox.withdrawWaitingNotices(prefix, stale, reason).length : 0;
18265
+ },
18266
+ changedAt: (uuid) => db.board.lastChangeAt(uuid),
18334
18267
  holds: (wake) => db.board.wakeHolds(wake),
18335
18268
  card: (uuid) => db.board.findCard(uuid),
18336
18269
  // #2352: the card's Needs you buttons are in this instance's own channel (it is the card's lane).
@@ -18347,12 +18280,13 @@ export class FleetManager {
18347
18280
  },
18348
18281
  log: (level, message, extra) => this.logger[level](extra ?? {}, message),
18349
18282
  });
18350
- db.board.onChange(({ rev, ids, projects, wakes }) => {
18283
+ db.board.onChange(({ rev, ids, projects, wakes, changed }) => {
18351
18284
  // Refresh both old and new owners on reassignment; wakes are not an ownership index.
18352
18285
  if (ids.length)
18353
18286
  for (const name of this.daemons.keys())
18354
18287
  this.observeWorkingState(name, "tasks");
18355
18288
  this.emitSseEvent("tasks", { rev, ids, projects });
18289
+ waker.changed(changed);
18356
18290
  waker.note(wakes);
18357
18291
  // step 3: cards waiting for the person are Needs you items; #2355 review: a project's lead routes them too.
18358
18292
  if (ids.length || projects)
@@ -23506,7 +23440,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23506
23440
  // From the install's own channel tag, this is what /update installs — even a stable the release pointed the
23507
23441
  // tag at (2.2.0 on @alpha / @beta): the plain /update text, never "/update stays on … use --stable" (#1928).
23508
23442
  const key = fromChannel ? "update.available_current" : updateNoticeKey(currentVersion, target);
23509
- this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
23443
+ // #2775: null = a beta install and a stable target; beta users are never told to switch.
23444
+ if (key)
23445
+ this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
23510
23446
  }
23511
23447
  }
23512
23448
  }