@songsid/agend 2.1.6-beta.3 → 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 (75) hide show
  1. package/dist/agent-endpoint.d.ts +18 -0
  2. package/dist/agent-endpoint.js +53 -2
  3. package/dist/agent-endpoint.js.map +1 -1
  4. package/dist/backend/claude-code.js +24 -0
  5. package/dist/backend/claude-code.js.map +1 -1
  6. package/dist/backend/factory.js +4 -1
  7. package/dist/backend/factory.js.map +1 -1
  8. package/dist/backend/muse.d.ts +90 -0
  9. package/dist/backend/muse.js +428 -0
  10. package/dist/backend/muse.js.map +1 -0
  11. package/dist/backend/types.d.ts +2 -0
  12. package/dist/backend/types.js.map +1 -1
  13. package/dist/channel/access-manager.d.ts +20 -0
  14. package/dist/channel/access-manager.js +25 -0
  15. package/dist/channel/access-manager.js.map +1 -1
  16. package/dist/channel/adapters/discord.d.ts +3 -0
  17. package/dist/channel/adapters/discord.js +13 -10
  18. package/dist/channel/adapters/discord.js.map +1 -1
  19. package/dist/channel/markdown-chunk.d.ts +8 -3
  20. package/dist/channel/markdown-chunk.js +12 -3
  21. package/dist/channel/markdown-chunk.js.map +1 -1
  22. package/dist/channel/mcp-server.js +9 -12
  23. package/dist/channel/mcp-server.js.map +1 -1
  24. package/dist/channel/mcp-tools.d.ts +6 -2
  25. package/dist/channel/mcp-tools.js +6 -36
  26. package/dist/channel/mcp-tools.js.map +1 -1
  27. package/dist/channel/tool-router.js +12 -2
  28. package/dist/channel/tool-router.js.map +1 -1
  29. package/dist/config-validator.js +21 -5
  30. package/dist/config-validator.js.map +1 -1
  31. package/dist/daemon.js +19 -6
  32. package/dist/daemon.js.map +1 -1
  33. package/dist/event-log.d.ts +9 -0
  34. package/dist/event-log.js +21 -0
  35. package/dist/event-log.js.map +1 -1
  36. package/dist/fleet-manager.d.ts +64 -0
  37. package/dist/fleet-manager.js +233 -26
  38. package/dist/fleet-manager.js.map +1 -1
  39. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +11 -1
  40. package/dist/instance-lifecycle.js +8 -4
  41. package/dist/instance-lifecycle.js.map +1 -1
  42. package/dist/logger.js +42 -25
  43. package/dist/logger.js.map +1 -1
  44. package/dist/outbound-schemas.d.ts +1 -0
  45. package/dist/outbound-schemas.js +1 -1
  46. package/dist/outbound-schemas.js.map +1 -1
  47. package/dist/quickstart-api.d.ts +3 -0
  48. package/dist/quickstart-api.js +1 -0
  49. package/dist/quickstart-api.js.map +1 -1
  50. package/dist/scheduler/db.js +17 -5
  51. package/dist/scheduler/db.js.map +1 -1
  52. package/dist/scheduler/db.test.js +34 -1
  53. package/dist/scheduler/db.test.js.map +1 -1
  54. package/dist/scheduler/types.d.ts +4 -0
  55. package/dist/scheduler/types.js.map +1 -1
  56. package/dist/setup-wizard.js +4 -0
  57. package/dist/setup-wizard.js.map +1 -1
  58. package/dist/steer-capability.js +4 -1
  59. package/dist/steer-capability.js.map +1 -1
  60. package/dist/tmux-manager.js +29 -4
  61. package/dist/tmux-manager.js.map +1 -1
  62. package/dist/tool-permissions-notice.d.ts +38 -0
  63. package/dist/tool-permissions-notice.js +92 -0
  64. package/dist/tool-permissions-notice.js.map +1 -0
  65. package/dist/tool-permissions.d.ts +93 -0
  66. package/dist/tool-permissions.js +216 -0
  67. package/dist/tool-permissions.js.map +1 -0
  68. package/dist/topic-commands.js +1 -0
  69. package/dist/topic-commands.js.map +1 -1
  70. package/dist/web-api.js +2 -1
  71. package/dist/web-api.js.map +1 -1
  72. package/dist/web-terminal.d.ts +30 -1
  73. package/dist/web-terminal.js +86 -3
  74. package/dist/web-terminal.js.map +1 -1
  75. package/package.json +2 -1
@@ -27,6 +27,15 @@ export declare class EventLog {
27
27
  insert(instance: string, type: string, payload?: Record<string, unknown>): void;
28
28
  query(opts?: QueryOpts): EventRow[];
29
29
  logActivity(event: string, sender: string, summary: string, receiver?: string, detail?: string): void;
30
+ /**
31
+ * Which tools each instance has called lately, from the activity log.
32
+ *
33
+ * `summary` is written as `tool(argument)`, so the name is everything up to
34
+ * the first bracket. Only the outbound path writes these rows — the agent
35
+ * endpoint and the typed IPC handlers do not — so this is a floor on what an
36
+ * instance has used, never a complete account of it.
37
+ */
38
+ toolUseByInstance(sinceIso: string): Map<string, Map<string, number>>;
30
39
  listActivity(opts?: {
31
40
  since?: string;
32
41
  limit?: number;
package/dist/event-log.js CHANGED
@@ -93,6 +93,27 @@ export class EventLog {
93
93
  logActivity(event, sender, summary, receiver, detail) {
94
94
  this.db.prepare("INSERT INTO activity (event, sender, receiver, summary, detail) VALUES (?, ?, ?, ?, ?)").run(event, sender, receiver ?? null, summary, detail ?? null);
95
95
  }
96
+ /**
97
+ * Which tools each instance has called lately, from the activity log.
98
+ *
99
+ * `summary` is written as `tool(argument)`, so the name is everything up to
100
+ * the first bracket. Only the outbound path writes these rows — the agent
101
+ * endpoint and the typed IPC handlers do not — so this is a floor on what an
102
+ * instance has used, never a complete account of it.
103
+ */
104
+ toolUseByInstance(sinceIso) {
105
+ const rows = this.db.prepare("SELECT sender, summary FROM activity WHERE event = 'tool_call' AND timestamp >= ?").all(sinceIso);
106
+ const out = new Map();
107
+ for (const row of rows) {
108
+ const tool = /^([a-z_]+)/.exec(row.summary ?? "")?.[1];
109
+ if (!tool || !row.sender)
110
+ continue;
111
+ const tools = out.get(row.sender) ?? new Map();
112
+ tools.set(tool, (tools.get(tool) ?? 0) + 1);
113
+ out.set(row.sender, tools);
114
+ }
115
+ return out;
116
+ }
96
117
  listActivity(opts) {
97
118
  let sql = "SELECT * FROM activity";
98
119
  const params = [];
@@ -1 +1 @@
1
- {"version":3,"file":"event-log.js","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-log.js","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"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"}
@@ -757,12 +757,43 @@ 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. */
763
771
  private rateLimitWarnedAt;
764
772
  private warnIfRateLimited;
765
773
  /** Handle outbound tool calls from a daemon instance */
774
+ /**
775
+ * Would this instance be allowed to use this tool?
776
+ *
777
+ * Stage 1 asks and records; nothing is refused yet. The recording is not only
778
+ * an observation window: `logActivity("tool_call")` sits on the outbound path
779
+ * only, so today the fleet has no idea what the agent endpoint or the typed
780
+ * IPC handlers are being asked to do — the one face with no authorization is
781
+ * also the one face with no telemetry.
782
+ *
783
+ * The profile comes from the instance the socket belongs to. Deliberately not
784
+ * `senderSessionName`, which arrives inside the message: a caller that fills
785
+ * in its own identity has not been identified.
786
+ */
787
+ /**
788
+ * Say once, at startup, what the new default means for this fleet.
789
+ *
790
+ * Nothing is rewritten: an explicit `tool_set: full` is a choice somebody
791
+ * made, and marking an instance as a coordinator is a judgement about how
792
+ * their fleet is organised. Both stay theirs — this only makes sure they are
793
+ * not discovered by an agent failing at three in the morning.
794
+ */
795
+ private announceToolPermissionsChange;
796
+ private checkToolPermission;
766
797
  private handleOutboundFromInstance;
767
798
  /** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
768
799
  private afterReplyRouted;
@@ -798,6 +829,24 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
798
829
  private scheduleSourceAdapter;
799
830
  private notifySourceTopic;
800
831
  private notifyScheduleFailure;
832
+ /**
833
+ * The typed IPC messages, all through one door.
834
+ *
835
+ * They used to be five sibling branches on the dispatch, which is why the
836
+ * permission question had five places to be forgotten. Routing them together
837
+ * means the check above happens once and cannot be skipped by adding a
838
+ * sixth — a new type has to appear in `IPC_TYPE_TOOLS` to be dispatched at
839
+ * all.
840
+ */
841
+ /**
842
+ * Answer a refused typed message the way its handler would have.
843
+ *
844
+ * These are request/response over IPC: dropping the message silently leaves
845
+ * the caller waiting for a reply that never comes, and a hung agent is a
846
+ * worse failure than a refused one.
847
+ */
848
+ private refuseTypedIpc;
849
+ private dispatchTypedIpc;
801
850
  private handleScheduleCrud;
802
851
  private handleDecisionCrud;
803
852
  /** Resolve display name for an instance, fallback to instance name. */
@@ -1043,6 +1092,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1043
1092
  * (or an assist for one they stopped) must not stay clickable for the rest
1044
1093
  * of its 15 minutes.
1045
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;
1046
1110
  clearNoncePromptsForInstance(instanceName: string): void;
1047
1111
  /** A clean exit is intentional from the CLI's perspective, but often not from
1048
1112
  * the operator's. Keep the instance notice passive and put the action in the
@@ -61,7 +61,9 @@ import { clearPausedMarker } from "./pause-marker.js";
61
61
  import { isFleetStartCommandLine, readProcessCommandLine, releaseProcessFleetLock } from "./fleet-lock.js";
62
62
  import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
63
63
  import { manualCleanupMessage, reapStaleTunnel } from "./tunnel/lease.js";
64
+ import { buildToolPermissionsNotice } from "./tool-permissions-notice.js";
64
65
  import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
66
+ import { mayUseTool, resolveToolSet, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
65
67
  import { decideWebGate, loadOrCreateWebToken, readWebToken } from "./web-auth.js";
66
68
  import { fleetLevelDifferences, fleetLevelSignature } from "./fleet-level-config.js";
67
69
  import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
@@ -215,6 +217,8 @@ const CLEAR_CONFIRM_TIMEOUT_MS = 15_000;
215
217
  /** Default lifetime for long-lived nonce prompts (clear overrides this to 15s). */
216
218
  const NONCE_BUTTON_TIMEOUT_MS = 15 * 60_000;
217
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;
218
222
  const CLI_ENV_TTL_MS = 24 * 60 * 60 * 1000; // hard validity bound for the cached CLI env
219
223
  /**
220
224
  * How old the cached CLI env may be before `/model` re-probes it live.
@@ -615,6 +619,7 @@ export class FleetManager {
615
619
  // watching, and the fleet starting is the moment there is finally a process
616
620
  // around to notice. Never throws: a lease that cannot be resolved blocks
617
621
  // the next tunnel and says so, it does not block the fleet.
622
+ this.announceToolPermissionsChange();
618
623
  void reapStaleTunnel(this.dataDir)
619
624
  .then(outcome => {
620
625
  if (outcome.kind === "manual")
@@ -3164,7 +3169,9 @@ export class FleetManager {
3164
3169
  }
3165
3170
  const accessDir = join(this.dataDir, "access");
3166
3171
  mkdirSync(accessDir, { recursive: true });
3167
- 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);
3168
3175
  this.accessManager = accessManager;
3169
3176
  const inboxDir = join(this.dataDir, "inbox");
3170
3177
  mkdirSync(inboxDir, { recursive: true });
@@ -3556,7 +3563,9 @@ export class FleetManager {
3556
3563
  }
3557
3564
  const accessDir = join(this.dataDir, "access");
3558
3565
  mkdirSync(accessDir, { recursive: true });
3559
- 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);
3560
3569
  const inboxDir = join(this.dataDir, "inbox");
3561
3570
  mkdirSync(inboxDir, { recursive: true });
3562
3571
  const adapter = await createAdapter(channelConfig, {
@@ -3934,22 +3943,15 @@ export class FleetManager {
3934
3943
  }
3935
3944
  await this.handleOutboundFromInstance(name, msg);
3936
3945
  }
3937
- else if (msg.type === "fleet_schedule_create" || msg.type === "fleet_schedule_list" ||
3938
- msg.type === "fleet_schedule_update" || msg.type === "fleet_schedule_delete") {
3939
- this.handleScheduleCrud(name, msg);
3940
- }
3941
- else if (msg.type === "fleet_decision_create" || msg.type === "fleet_decision_list" ||
3942
- msg.type === "fleet_decision_update") {
3943
- this.handleDecisionCrud(name, msg);
3944
- }
3945
- else if (msg.type === "fleet_task") {
3946
- this.handleTaskCrud(name, msg);
3947
- }
3948
- else if (msg.type === "fleet_set_display_name") {
3949
- this.handleSetDisplayName(name, msg);
3950
- }
3951
- else if (msg.type === "fleet_set_description") {
3952
- this.handleSetDescription(name, msg);
3946
+ else if (toolForIpcType(msg.type) !== null) {
3947
+ // Sink 2 of 3. Each of these types IS a tool and reaches its own
3948
+ // handler without passing through the outbound path — which is how
3949
+ // `update_decision`, a coordinator-only tool, kept a way through.
3950
+ const typed = this.checkToolPermission("ipc-typed", name, toolForIpcType(msg.type));
3951
+ if (typed.allowed)
3952
+ this.dispatchTypedIpc(name, msg);
3953
+ else
3954
+ this.refuseTypedIpc(name, msg, typed.message);
3953
3955
  }
3954
3956
  else if (msg.type === "instance_process_state") {
3955
3957
  this.cacheInstanceProcessStatus(name, msg.status);
@@ -4318,13 +4320,36 @@ export class FleetManager {
4318
4320
  const target = this.routing.resolve(threadId);
4319
4321
  if (!target)
4320
4322
  return "fleet topic: no instance routed for this thread";
4321
- // 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.
4322
4334
  const isOpen = this.getChannelConfig(msg.adapterId)?.access?.mode === "open";
4323
4335
  if (!isOpen && !this.collabInstances.has(target.name)) {
4324
4336
  return `fleet topic: adapter not open and collab off for ${target.name}`;
4325
4337
  }
4326
4338
  return null;
4327
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
+ }
4328
4353
  async handleInboundMessage(msg) {
4329
4354
  const threadId = msg.threadId || undefined;
4330
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");
@@ -4891,6 +4916,55 @@ export class FleetManager {
4891
4916
  }
4892
4917
  }
4893
4918
  /** Handle outbound tool calls from a daemon instance */
4919
+ /**
4920
+ * Would this instance be allowed to use this tool?
4921
+ *
4922
+ * Stage 1 asks and records; nothing is refused yet. The recording is not only
4923
+ * an observation window: `logActivity("tool_call")` sits on the outbound path
4924
+ * only, so today the fleet has no idea what the agent endpoint or the typed
4925
+ * IPC handlers are being asked to do — the one face with no authorization is
4926
+ * also the one face with no telemetry.
4927
+ *
4928
+ * The profile comes from the instance the socket belongs to. Deliberately not
4929
+ * `senderSessionName`, which arrives inside the message: a caller that fills
4930
+ * in its own identity has not been identified.
4931
+ */
4932
+ /**
4933
+ * Say once, at startup, what the new default means for this fleet.
4934
+ *
4935
+ * Nothing is rewritten: an explicit `tool_set: full` is a choice somebody
4936
+ * made, and marking an instance as a coordinator is a judgement about how
4937
+ * their fleet is organised. Both stay theirs — this only makes sure they are
4938
+ * not discovered by an agent failing at three in the morning.
4939
+ */
4940
+ announceToolPermissionsChange() {
4941
+ try {
4942
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 864e5).toISOString().slice(0, 19).replace("T", " ");
4943
+ const recent = [...(this.eventLog?.toolUseByInstance(thirtyDaysAgo) ?? new Map())]
4944
+ .map(([instance, tools]) => ({ instance, tools }));
4945
+ const notice = buildToolPermissionsNotice({
4946
+ defaultsToolSet: this.fleetConfig?.defaults?.tool_set,
4947
+ instances: (this.fleetConfig?.instances ?? {}),
4948
+ recent,
4949
+ });
4950
+ if (notice)
4951
+ this.logger.warn({ notice }, notice);
4952
+ }
4953
+ catch (err) {
4954
+ // Advice is not worth failing a startup over.
4955
+ this.logger.debug({ err }, "tool-permissions: could not build the migration notice");
4956
+ }
4957
+ }
4958
+ checkToolPermission(sink, instanceName, tool) {
4959
+ const profile = resolveToolSet(this.fleetConfig?.instances[instanceName], instanceName);
4960
+ const allowed = mayUseTool(profile, tool);
4961
+ if (!allowed) {
4962
+ this.logger.warn({ sink, instance: instanceName, profile, tool, enforced: true }, "tool-permissions: refused");
4963
+ return { allowed: false, message: toolRefusedMessage(profile, tool) };
4964
+ }
4965
+ this.logger.debug({ sink, instance: instanceName, profile, tool, enforced: true }, "tool-permissions: allowed");
4966
+ return { allowed: true, message: "" };
4967
+ }
4894
4968
  async handleOutboundFromInstance(instanceName, msg) {
4895
4969
  this.touchActivity(instanceName);
4896
4970
  this.setTopicIcon(instanceName, "green");
@@ -4912,6 +4986,19 @@ export class FleetManager {
4912
4986
  this.logger.warn({ instanceName, tool, requestId, fleetRequestId, error }, "Fleet outbound result could not be returned — instance IPC is disconnected");
4913
4987
  }
4914
4988
  };
4989
+ // Sink 1 of 3, and the first thing decided. Every MCP call and every direct
4990
+ // write to channel.sock lands here, whether or not mcp-server was ever
4991
+ // involved — which is why this is the boundary and the tool list the model
4992
+ // was shown is not.
4993
+ //
4994
+ // Above the adapter check on purpose: "retry shortly" is the wrong answer
4995
+ // to a call that will never be allowed, and an agent that believes it is a
4996
+ // timing problem will keep trying.
4997
+ const permitted = this.checkToolPermission("ipc-outbound", instanceName, tool);
4998
+ if (!permitted.allowed) {
4999
+ respond(null, permitted.message);
5000
+ return;
5001
+ }
4915
5002
  if (this.worlds.size === 0) {
4916
5003
  respond(null, "Channel adapters are not ready — retry shortly");
4917
5004
  return;
@@ -5169,6 +5256,15 @@ export class FleetManager {
5169
5256
  */
5170
5257
  scheduleSourceAdapter(schedule) {
5171
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
+ }
5172
5268
  const sourceAdapterId = schedule.source ? this.getInstanceAdapterId(schedule.source) : undefined;
5173
5269
  const sourceWorld = sourceAdapterId ? this.worlds.get(sourceAdapterId) : undefined;
5174
5270
  if (sourceWorld && String(sourceWorld.groupId) === chatId)
@@ -5200,6 +5296,58 @@ export class FleetManager {
5200
5296
  threadId: schedule.reply_thread_id ?? undefined,
5201
5297
  }).catch((err) => this.logger.error({ err }, "Failed to send schedule failure notification"));
5202
5298
  }
5299
+ /**
5300
+ * The typed IPC messages, all through one door.
5301
+ *
5302
+ * They used to be five sibling branches on the dispatch, which is why the
5303
+ * permission question had five places to be forgotten. Routing them together
5304
+ * means the check above happens once and cannot be skipped by adding a
5305
+ * sixth — a new type has to appear in `IPC_TYPE_TOOLS` to be dispatched at
5306
+ * all.
5307
+ */
5308
+ /**
5309
+ * Answer a refused typed message the way its handler would have.
5310
+ *
5311
+ * These are request/response over IPC: dropping the message silently leaves
5312
+ * the caller waiting for a reply that never comes, and a hung agent is a
5313
+ * worse failure than a refused one.
5314
+ */
5315
+ refuseTypedIpc(name, msg, message) {
5316
+ const ipc = this.instanceIpcClients.get(name);
5317
+ const fleetRequestId = msg.fleetRequestId;
5318
+ if (!ipc || !fleetRequestId)
5319
+ return;
5320
+ const type = String(msg.type);
5321
+ const responseType = type.startsWith("fleet_schedule_") ? "fleet_schedule_response"
5322
+ : type.startsWith("fleet_decision_") ? "fleet_decision_response"
5323
+ : type === "fleet_task" ? "fleet_task_response"
5324
+ : type === "fleet_set_display_name" ? "fleet_display_name_response"
5325
+ : "fleet_description_response";
5326
+ ipc.send({ type: responseType, fleetRequestId, error: message });
5327
+ }
5328
+ dispatchTypedIpc(name, msg) {
5329
+ const type = String(msg.type);
5330
+ if (type.startsWith("fleet_schedule_")) {
5331
+ this.handleScheduleCrud(name, msg);
5332
+ return;
5333
+ }
5334
+ if (type.startsWith("fleet_decision_")) {
5335
+ this.handleDecisionCrud(name, msg);
5336
+ return;
5337
+ }
5338
+ if (type === "fleet_task") {
5339
+ this.handleTaskCrud(name, msg);
5340
+ return;
5341
+ }
5342
+ if (type === "fleet_set_display_name") {
5343
+ this.handleSetDisplayName(name, msg);
5344
+ return;
5345
+ }
5346
+ if (type === "fleet_set_description") {
5347
+ this.handleSetDescription(name, msg);
5348
+ return;
5349
+ }
5350
+ }
5203
5351
  handleScheduleCrud(instanceName, msg) {
5204
5352
  const fleetRequestId = msg.fleetRequestId;
5205
5353
  const payload = (msg.payload ?? {});
@@ -5207,6 +5355,14 @@ export class FleetManager {
5207
5355
  const ipc = this.instanceIpcClients.get(instanceName);
5208
5356
  if (!ipc)
5209
5357
  return;
5358
+ if (!this.scheduler) {
5359
+ // It did answer before, with whatever TypeError fell out of the try
5360
+ // block — "Cannot read properties of null (reading 'list')" is a stack
5361
+ // trace wearing an error message, and the agent reading it cannot tell
5362
+ // that the fleet simply has no scheduler.
5363
+ ipc.send({ type: "fleet_schedule_response", fleetRequestId, error: "Schedules are unavailable — the fleet scheduler is not running" });
5364
+ return;
5365
+ }
5210
5366
  try {
5211
5367
  let result;
5212
5368
  switch (msg.type) {
@@ -5219,6 +5375,7 @@ export class FleetManager {
5219
5375
  target: payload.target || instanceName,
5220
5376
  reply_chat_id: meta.chat_id,
5221
5377
  reply_thread_id: meta.thread_id || null,
5378
+ reply_adapter_id: meta.adapter_id || null,
5222
5379
  label: payload.label,
5223
5380
  timezone: payload.timezone,
5224
5381
  silent: !!(payload.silent),
@@ -5248,8 +5405,15 @@ export class FleetManager {
5248
5405
  const payload = (msg.payload ?? {});
5249
5406
  const meta = (msg.meta ?? {});
5250
5407
  const ipc = this.instanceIpcClients.get(instanceName);
5251
- if (!ipc || !this.scheduler)
5408
+ if (!ipc)
5252
5409
  return;
5410
+ if (!this.scheduler) {
5411
+ // Returning silently left the caller waiting for a response that was
5412
+ // never coming. Over IPC that is a hang, and a hang is a worse answer
5413
+ // than an error.
5414
+ ipc.send({ type: "fleet_decision_response", fleetRequestId, error: "Decisions are unavailable — the fleet scheduler is not running" });
5415
+ return;
5416
+ }
5253
5417
  const db = this.scheduler.db;
5254
5418
  const projectRoot = meta.working_directory || this.fleetConfig?.instances[instanceName]?.working_directory || "";
5255
5419
  try {
@@ -5523,8 +5687,15 @@ export class FleetManager {
5523
5687
  const payload = (msg.payload ?? {});
5524
5688
  const meta = (msg.meta ?? {});
5525
5689
  const ipc = this.instanceIpcClients.get(instanceName);
5526
- if (!ipc || !this.scheduler)
5690
+ if (!ipc)
5527
5691
  return;
5692
+ if (!this.scheduler) {
5693
+ // Returning silently left the caller waiting for a response that was
5694
+ // never coming. Over IPC that is a hang, and a hang is a worse answer
5695
+ // than an error.
5696
+ ipc.send({ type: "fleet_task_response", fleetRequestId, error: "The task board is unavailable — the fleet scheduler is not running" });
5697
+ return;
5698
+ }
5528
5699
  const db = this.scheduler.db;
5529
5700
  const action = payload.action;
5530
5701
  try {
@@ -6872,6 +7043,39 @@ export class FleetManager {
6872
7043
  * (or an assist for one they stopped) must not stay clickable for the rest
6873
7044
  * of its 15 minutes.
6874
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
+ }
6875
7079
  clearNoncePromptsForInstance(instanceName) {
6876
7080
  for (const [nonce, entry] of this.pendingNonceButtons) {
6877
7081
  if (entry.instanceName !== instanceName)
@@ -8656,6 +8860,8 @@ export class FleetManager {
8656
8860
  // Grok reads AGENTS.md project docs; agy reads .agents/agents.md — the
8657
8861
  // same files their writeConfig() appends fleet instructions to.
8658
8862
  "grok": "AGENTS.md",
8863
+ // muse init scaffolds AGENTS.md and the binary reads it as project rules.
8864
+ "muse": "AGENTS.md",
8659
8865
  "antigravity": ".agents/agents.md",
8660
8866
  "mock": "CLAUDE.md",
8661
8867
  };
@@ -8731,6 +8937,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
8731
8937
  // vendor-canonical location is .grok/skills.
8732
8938
  "opencode": [".agents", "skills"],
8733
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"],
8734
8943
  "antigravity": [".agents", "skills"],
8735
8944
  };
8736
8945
  /** Copy general-knowledge steering + all role-eligible skills to General. */
@@ -10470,11 +10679,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10470
10679
  for (const pending of this.pendingClassicStarts.values())
10471
10680
  clearTimeout(pending.timer);
10472
10681
  this.pendingClassicStarts.clear();
10473
- for (const pending of this.pendingNonceButtons.values()) {
10474
- if (pending.timer)
10475
- clearTimeout(pending.timer);
10476
- }
10477
- 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();
10478
10685
  this.topicArchiver.stop();
10479
10686
  this.scheduler?.shutdown();
10480
10687
  // Stop instances in parallel batches to avoid long sequential waits.