@songsid/agend 2.1.6-beta.4 → 2.1.6-beta.5

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 (57) hide show
  1. package/dist/backend/claude-code.js +24 -0
  2. package/dist/backend/claude-code.js.map +1 -1
  3. package/dist/backend/factory.js +4 -1
  4. package/dist/backend/factory.js.map +1 -1
  5. package/dist/backend/muse.d.ts +90 -0
  6. package/dist/backend/muse.js +428 -0
  7. package/dist/backend/muse.js.map +1 -0
  8. package/dist/backend/types.d.ts +2 -0
  9. package/dist/backend/types.js.map +1 -1
  10. package/dist/channel/access-manager.d.ts +20 -0
  11. package/dist/channel/access-manager.js +25 -0
  12. package/dist/channel/access-manager.js.map +1 -1
  13. package/dist/channel/adapters/discord.d.ts +3 -0
  14. package/dist/channel/adapters/discord.js +13 -10
  15. package/dist/channel/adapters/discord.js.map +1 -1
  16. package/dist/channel/markdown-chunk.d.ts +8 -3
  17. package/dist/channel/markdown-chunk.js +12 -3
  18. package/dist/channel/markdown-chunk.js.map +1 -1
  19. package/dist/channel/tool-router.js +12 -2
  20. package/dist/channel/tool-router.js.map +1 -1
  21. package/dist/config-validator.js +14 -3
  22. package/dist/config-validator.js.map +1 -1
  23. package/dist/daemon.js +7 -2
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/fleet-manager.d.ts +23 -0
  26. package/dist/fleet-manager.js +83 -8
  27. package/dist/fleet-manager.js.map +1 -1
  28. package/dist/instance-lifecycle.js +8 -4
  29. package/dist/instance-lifecycle.js.map +1 -1
  30. package/dist/logger.js +42 -25
  31. package/dist/logger.js.map +1 -1
  32. package/dist/outbound-schemas.d.ts +1 -0
  33. package/dist/outbound-schemas.js +1 -1
  34. package/dist/outbound-schemas.js.map +1 -1
  35. package/dist/quickstart-api.d.ts +3 -0
  36. package/dist/quickstart-api.js +1 -0
  37. package/dist/quickstart-api.js.map +1 -1
  38. package/dist/scheduler/db.js +17 -5
  39. package/dist/scheduler/db.js.map +1 -1
  40. package/dist/scheduler/db.test.js +34 -1
  41. package/dist/scheduler/db.test.js.map +1 -1
  42. package/dist/scheduler/types.d.ts +4 -0
  43. package/dist/scheduler/types.js.map +1 -1
  44. package/dist/setup-wizard.js +4 -0
  45. package/dist/setup-wizard.js.map +1 -1
  46. package/dist/steer-capability.js +4 -1
  47. package/dist/steer-capability.js.map +1 -1
  48. package/dist/tmux-manager.js +29 -4
  49. package/dist/tmux-manager.js.map +1 -1
  50. package/dist/topic-commands.js +1 -0
  51. package/dist/topic-commands.js.map +1 -1
  52. package/dist/web-api.js +2 -1
  53. package/dist/web-api.js.map +1 -1
  54. package/dist/web-terminal.d.ts +30 -1
  55. package/dist/web-terminal.js +86 -3
  56. package/dist/web-terminal.js.map +1 -1
  57. package/package.json +2 -1
@@ -757,6 +757,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
757
757
  * Pure: callers rely on being able to ask before claiming the dedup key.
758
758
  */
759
759
  private botMessageDropReason;
760
+ /**
761
+ * Say so when an adapter's access mode is coming from its state file rather
762
+ * than from fleet.yaml. The state file wins by design — a pairing done at
763
+ * runtime has to survive a restart — but that also means an edit to
764
+ * `access.mode` silently does nothing, which is indistinguishable from the
765
+ * fleet not having reloaded. Naming the file is what makes it fixable.
766
+ */
767
+ private warnIfAccessModeOverridden;
760
768
  private handleInboundMessage;
761
769
  /** Handle outbound tool calls from a daemon instance */
762
770
  /** Warn (but don't block) when rate limits are high. 30-min debounce per instance. */
@@ -1084,6 +1092,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1084
1092
  * (or an assist for one they stopped) must not stay clickable for the rest
1085
1093
  * of its 15 minutes.
1086
1094
  */
1095
+ /**
1096
+ * Collapse every still-armed button prompt, for a fleet that is going away.
1097
+ *
1098
+ * The map is in memory; the buttons are in the chat for up to 24 hours. A
1099
+ * restart therefore leaves them looking live — pressing one takes the stale
1100
+ * branch, which acknowledges the click and drops the dismissal without
1101
+ * saying so. Retiring them here means the user meets a spent prompt instead
1102
+ * of a live-looking dead one.
1103
+ *
1104
+ * Best effort and bounded. Each collapse is a platform call, a day of tips
1105
+ * can be many of them, and shutdown still has instances to stop. Whatever
1106
+ * has not finished by the deadline is abandoned — that is exactly today's
1107
+ * behaviour, so the budget can only leave things no worse than before.
1108
+ */
1109
+ private retirePendingNoncePrompts;
1087
1110
  clearNoncePromptsForInstance(instanceName: string): void;
1088
1111
  /** A clean exit is intentional from the CLI's perspective, but often not from
1089
1112
  * the operator's. Keep the instance notice passive and put the action in the
@@ -217,6 +217,8 @@ const CLEAR_CONFIRM_TIMEOUT_MS = 15_000;
217
217
  /** Default lifetime for long-lived nonce prompts (clear overrides this to 15s). */
218
218
  const NONCE_BUTTON_TIMEOUT_MS = 15 * 60_000;
219
219
  const TIP_BUTTON_TIMEOUT_MS = 24 * 60 * 60_000;
220
+ /** How long shutdown will spend retiring still-armed button prompts. */
221
+ const NONCE_RETIRE_BUDGET_MS = 5_000;
220
222
  const CLI_ENV_TTL_MS = 24 * 60 * 60 * 1000; // hard validity bound for the cached CLI env
221
223
  /**
222
224
  * How old the cached CLI env may be before `/model` re-probes it live.
@@ -3167,7 +3169,9 @@ export class FleetManager {
3167
3169
  }
3168
3170
  const accessDir = join(this.dataDir, "access");
3169
3171
  mkdirSync(accessDir, { recursive: true });
3170
- const accessManager = new AccessManager(channelConfig.access ?? DEFAULT_OPEN_ACCESS, join(accessDir, "access.json"));
3172
+ const accessStatePath = join(accessDir, "access.json");
3173
+ const accessManager = new AccessManager(channelConfig.access ?? DEFAULT_OPEN_ACCESS, accessStatePath);
3174
+ this.warnIfAccessModeOverridden(accessManager, accessStatePath);
3171
3175
  this.accessManager = accessManager;
3172
3176
  const inboxDir = join(this.dataDir, "inbox");
3173
3177
  mkdirSync(inboxDir, { recursive: true });
@@ -3559,7 +3563,9 @@ export class FleetManager {
3559
3563
  }
3560
3564
  const accessDir = join(this.dataDir, "access");
3561
3565
  mkdirSync(accessDir, { recursive: true });
3562
- const accessManager = new AccessManager(channelConfig.access ?? DEFAULT_OPEN_ACCESS, join(accessDir, `access-${adapterId}.json`));
3566
+ const accessStatePath = join(accessDir, `access-${adapterId}.json`);
3567
+ const accessManager = new AccessManager(channelConfig.access ?? DEFAULT_OPEN_ACCESS, accessStatePath);
3568
+ this.warnIfAccessModeOverridden(accessManager, accessStatePath);
3563
3569
  const inboxDir = join(this.dataDir, "inbox");
3564
3570
  mkdirSync(inboxDir, { recursive: true });
3565
3571
  const adapter = await createAdapter(channelConfig, {
@@ -4314,13 +4320,36 @@ export class FleetManager {
4314
4320
  const target = this.routing.resolve(threadId);
4315
4321
  if (!target)
4316
4322
  return "fleet topic: no instance routed for this thread";
4317
- // Fleet topic: allow if collab enabled OR access mode is open
4323
+ // Fleet topic: this gate lets the copy through when the adapter is open OR
4324
+ // collab is on for the instance. "Through" is not "delivered" — access
4325
+ // control runs next, and the two arms are not symmetric there:
4326
+ //
4327
+ // open adapter → isAllowed() returns true outright: really admitted.
4328
+ // locked + collab → the bot's user id still has to be on the allowlist,
4329
+ // so the message is normally refused a few lines later
4330
+ // under "Access DENIED for non-allowed user".
4331
+ //
4332
+ // Collab alone therefore does not admit a bot on a locked adapter; it only
4333
+ // declines to drop the copy here and leaves the decision to access control.
4318
4334
  const isOpen = this.getChannelConfig(msg.adapterId)?.access?.mode === "open";
4319
4335
  if (!isOpen && !this.collabInstances.has(target.name)) {
4320
4336
  return `fleet topic: adapter not open and collab off for ${target.name}`;
4321
4337
  }
4322
4338
  return null;
4323
4339
  }
4340
+ /**
4341
+ * Say so when an adapter's access mode is coming from its state file rather
4342
+ * than from fleet.yaml. The state file wins by design — a pairing done at
4343
+ * runtime has to survive a restart — but that also means an edit to
4344
+ * `access.mode` silently does nothing, which is indistinguishable from the
4345
+ * fleet not having reloaded. Naming the file is what makes it fixable.
4346
+ */
4347
+ warnIfAccessModeOverridden(accessManager, statePath) {
4348
+ const configured = accessManager.overriddenConfigMode();
4349
+ if (!configured)
4350
+ return;
4351
+ this.logger.warn({ statePath, configured, inEffect: accessManager.getMode() }, `access mode "${accessManager.getMode()}" comes from the state file and overrides "${configured}" in fleet.yaml; delete ${statePath} to go back to the configured value`);
4352
+ }
4324
4353
  async handleInboundMessage(msg) {
4325
4354
  const threadId = msg.threadId || undefined;
4326
4355
  this.logger.debug({ source: msg.source, chatId: msg.chatId, threadId, userId: msg.userId, isBotMessage: msg.isBotMessage, textLen: (msg.text ?? "").length, text: (msg.text ?? "").slice(0, 80) }, "handleInboundMessage entry");
@@ -5227,6 +5256,15 @@ export class FleetManager {
5227
5256
  */
5228
5257
  scheduleSourceAdapter(schedule) {
5229
5258
  const chatId = String(schedule.reply_chat_id);
5259
+ // New schedules carry the adapter that owned the source chat at creation.
5260
+ // Keep that persona across source-instance rebinding, but never trust a
5261
+ // stale adapter after the chat has moved to another world.
5262
+ const persistedWorld = schedule.reply_adapter_id
5263
+ ? this.worlds.get(schedule.reply_adapter_id)
5264
+ : undefined;
5265
+ if (persistedWorld && String(persistedWorld.groupId) === chatId) {
5266
+ return persistedWorld.adapter;
5267
+ }
5230
5268
  const sourceAdapterId = schedule.source ? this.getInstanceAdapterId(schedule.source) : undefined;
5231
5269
  const sourceWorld = sourceAdapterId ? this.worlds.get(sourceAdapterId) : undefined;
5232
5270
  if (sourceWorld && String(sourceWorld.groupId) === chatId)
@@ -5337,6 +5375,7 @@ export class FleetManager {
5337
5375
  target: payload.target || instanceName,
5338
5376
  reply_chat_id: meta.chat_id,
5339
5377
  reply_thread_id: meta.thread_id || null,
5378
+ reply_adapter_id: meta.adapter_id || null,
5340
5379
  label: payload.label,
5341
5380
  timezone: payload.timezone,
5342
5381
  silent: !!(payload.silent),
@@ -7004,6 +7043,39 @@ export class FleetManager {
7004
7043
  * (or an assist for one they stopped) must not stay clickable for the rest
7005
7044
  * of its 15 minutes.
7006
7045
  */
7046
+ /**
7047
+ * Collapse every still-armed button prompt, for a fleet that is going away.
7048
+ *
7049
+ * The map is in memory; the buttons are in the chat for up to 24 hours. A
7050
+ * restart therefore leaves them looking live — pressing one takes the stale
7051
+ * branch, which acknowledges the click and drops the dismissal without
7052
+ * saying so. Retiring them here means the user meets a spent prompt instead
7053
+ * of a live-looking dead one.
7054
+ *
7055
+ * Best effort and bounded. Each collapse is a platform call, a day of tips
7056
+ * can be many of them, and shutdown still has instances to stop. Whatever
7057
+ * has not finished by the deadline is abandoned — that is exactly today's
7058
+ * behaviour, so the budget can only leave things no worse than before.
7059
+ */
7060
+ async retirePendingNoncePrompts(budgetMs = NONCE_RETIRE_BUDGET_MS) {
7061
+ const entries = [...this.pendingNonceButtons.values()];
7062
+ this.pendingNonceButtons.clear();
7063
+ for (const entry of entries)
7064
+ if (entry.timer)
7065
+ clearTimeout(entry.timer);
7066
+ const collapses = entries
7067
+ .filter(entry => entry.messageId && entry.adapter.editMessageRemoveButtons)
7068
+ .map(entry => entry.adapter.editMessageRemoveButtons(entry.chatId, entry.messageId, entry.expiredText, entry.threadId).catch(err => this.logger.debug({ err, instanceName: entry.instanceName, prefix: entry.prefix }, "Failed to retire button prompt during shutdown")));
7069
+ if (!collapses.length)
7070
+ return;
7071
+ await Promise.race([
7072
+ Promise.allSettled(collapses),
7073
+ new Promise(resolve => {
7074
+ const timer = setTimeout(resolve, budgetMs);
7075
+ timer.unref?.();
7076
+ }),
7077
+ ]);
7078
+ }
7007
7079
  clearNoncePromptsForInstance(instanceName) {
7008
7080
  for (const [nonce, entry] of this.pendingNonceButtons) {
7009
7081
  if (entry.instanceName !== instanceName)
@@ -8788,6 +8860,8 @@ export class FleetManager {
8788
8860
  // Grok reads AGENTS.md project docs; agy reads .agents/agents.md — the
8789
8861
  // same files their writeConfig() appends fleet instructions to.
8790
8862
  "grok": "AGENTS.md",
8863
+ // muse init scaffolds AGENTS.md and the binary reads it as project rules.
8864
+ "muse": "AGENTS.md",
8791
8865
  "antigravity": ".agents/agents.md",
8792
8866
  "mock": "CLAUDE.md",
8793
8867
  };
@@ -8863,6 +8937,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
8863
8937
  // vendor-canonical location is .grok/skills.
8864
8938
  "opencode": [".agents", "skills"],
8865
8939
  "grok": [".grok", "skills"],
8940
+ // Live-verified on muse 1.3.0: `muse skills list --source project` sees
8941
+ // .agents/skills and ignores .muse/skills.
8942
+ "muse": [".agents", "skills"],
8866
8943
  "antigravity": [".agents", "skills"],
8867
8944
  };
8868
8945
  /** Copy general-knowledge steering + all role-eligible skills to General. */
@@ -10602,11 +10679,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10602
10679
  for (const pending of this.pendingClassicStarts.values())
10603
10680
  clearTimeout(pending.timer);
10604
10681
  this.pendingClassicStarts.clear();
10605
- for (const pending of this.pendingNonceButtons.values()) {
10606
- if (pending.timer)
10607
- clearTimeout(pending.timer);
10608
- }
10609
- this.pendingNonceButtons.clear();
10682
+ // Adapters are still connected here — they are stopped further down — so
10683
+ // this is the last moment the prompts can be collapsed.
10684
+ await this.retirePendingNoncePrompts();
10610
10685
  this.topicArchiver.stop();
10611
10686
  this.scheduler?.shutdown();
10612
10687
  // Stop instances in parallel batches to avoid long sequential waits.