@songsid/agend 2.1.5-beta.14 → 2.1.5-beta.16

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.
@@ -331,6 +331,26 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
331
331
  getWorldForInstance(name: string): AdapterWorld | undefined;
332
332
  /** Get channel config for a specific adapter (by id), falling back to primary */
333
333
  getChannelConfig(adapterId?: string): import("./types.js").ChannelConfig | undefined;
334
+ /**
335
+ * The configured world that owns `chatId`, when that is provably NOT the
336
+ * world `target` lives in. Returns undefined when they agree, when there is
337
+ * nothing to check, or when no configured channel claims the id.
338
+ *
339
+ * Deliberately one-sided: only a POSITIVE match against another channel's
340
+ * group id counts as foreign. A chat id that matches nothing may still be
341
+ * legitimate for this world (a classic channel, a DM), and treating
342
+ * "unrecognised" as "wrong" would stop seeding for cases that work today.
343
+ *
344
+ * Read from config rather than the live worlds map on purpose: a channel
345
+ * whose adapter failed to start still owns its group id, and the coordinates
346
+ * are just as unusable by the target's adapter either way.
347
+ *
348
+ * This is the other half of what scheduleSourceAdapter fixed. That one stops
349
+ * the trigger NOTICE being sent through the wrong bot; this one stops the
350
+ * same coordinates being planted as the target instance's reply context,
351
+ * which is what made its own replies fail until someone spoke to it (#752).
352
+ */
353
+ private scheduleChatWorldMismatch;
334
354
  /** Get the group_id for an instance's bound adapter */
335
355
  getGroupIdForInstance(name: string): string;
336
356
  /** Configured primary adapter id. Never infer this from Map insertion order. */
@@ -928,6 +928,45 @@ export class FleetManager {
928
928
  }
929
929
  return channels[0];
930
930
  }
931
+ /**
932
+ * The configured world that owns `chatId`, when that is provably NOT the
933
+ * world `target` lives in. Returns undefined when they agree, when there is
934
+ * nothing to check, or when no configured channel claims the id.
935
+ *
936
+ * Deliberately one-sided: only a POSITIVE match against another channel's
937
+ * group id counts as foreign. A chat id that matches nothing may still be
938
+ * legitimate for this world (a classic channel, a DM), and treating
939
+ * "unrecognised" as "wrong" would stop seeding for cases that work today.
940
+ *
941
+ * Read from config rather than the live worlds map on purpose: a channel
942
+ * whose adapter failed to start still owns its group id, and the coordinates
943
+ * are just as unusable by the target's adapter either way.
944
+ *
945
+ * This is the other half of what scheduleSourceAdapter fixed. That one stops
946
+ * the trigger NOTICE being sent through the wrong bot; this one stops the
947
+ * same coordinates being planted as the target instance's reply context,
948
+ * which is what made its own replies fail until someone spoke to it (#752).
949
+ */
950
+ scheduleChatWorldMismatch(target, chatId) {
951
+ if (!chatId)
952
+ return undefined;
953
+ const targetWorld = this.getInstanceAdapterId(target);
954
+ if (!targetWorld)
955
+ return undefined;
956
+ const channels = this.fleetConfig?.channels
957
+ ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []);
958
+ // ALL owners, not the first: a persona bot shares the primary's guild
959
+ // (quickstart writes `group_id: primary.group_id`), so one group id is
960
+ // legitimately claimed by two channels. Taking the first match called a
961
+ // persona instance's own guild "another world" and stopped seeding a
962
+ // context it can address perfectly well.
963
+ const owners = channels.filter(ch => ch.group_id != null && String(ch.group_id) === String(chatId));
964
+ if (owners.length === 0)
965
+ return undefined;
966
+ if (owners.some(ch => (ch.id ?? ch.type) === targetWorld))
967
+ return undefined;
968
+ return owners[0].id ?? owners[0].type;
969
+ }
931
970
  /** Get the group_id for an instance's bound adapter */
932
971
  getGroupIdForInstance(name) {
933
972
  const adapterId = this.getInstanceAdapterId(name);
@@ -4786,7 +4825,9 @@ export class FleetManager {
4786
4825
  // The adapter resolves only after the platform POST returns. Keep
4787
4826
  // outward-facing logs/cancel state on the same confirmation boundary:
4788
4827
  // a routed-but-failed reply is not a delivered reply.
4789
- if (!error && result != null) {
4828
+ // `statusOnly` is daemon-owned envelope metadata, not an MCP argument:
4829
+ // an agent cannot invent it to suppress the normal completion marker.
4830
+ if (!error && result != null && msg.statusOnly !== true) {
4790
4831
  try {
4791
4832
  this.afterReplyRouted(instanceName, args, senderSessionName);
4792
4833
  }
@@ -4892,12 +4933,25 @@ export class FleetManager {
4892
4933
  // the target instance's configured world so replies use its persona
4893
4934
  // after a fresh start instead of falling back to channels[0].
4894
4935
  const adapterId = this.getInstanceAdapterId(target);
4936
+ // ...but the stored reply coordinates belong to the chat the schedule
4937
+ // was CREATED in, which is not always the target's world. Pairing them
4938
+ // with the target's adapter hands one platform's ids to another's API:
4939
+ // a Telegram group + forum topic delivered to a Discord instance made
4940
+ // every reply on that turn fetch `/channels/<telegram topic>` and fail
4941
+ // with Unknown Channel, and because the seeding repeats on each trigger
4942
+ // it stayed broken until a real inbound overwrote the context (#752).
4943
+ const foreignWorld = this.scheduleChatWorldMismatch(target, reply_chat_id);
4944
+ if (foreignWorld) {
4945
+ this.logger.warn({ scheduleId: id, target, foreignWorld, targetWorld: adapterId, chatId: reply_chat_id }, "Schedule reply target belongs to another channel world — not seeding chat context; the instance keeps its own last known chat");
4946
+ }
4895
4947
  await this.deliverToInstance(target, {
4896
4948
  type: "fleet_schedule_trigger",
4897
4949
  payload: { schedule_id: id, message: `[Scheduled] ${message}`, label },
4898
4950
  meta: {
4899
- chat_id: reply_chat_id,
4900
- thread_id: reply_thread_id,
4951
+ // Omitted on a mismatch: the daemon then keeps its persisted
4952
+ // last-chat, which is in the right world, instead of being
4953
+ // overwritten with coordinates the target's adapter cannot address.
4954
+ ...(foreignWorld ? {} : { chat_id: reply_chat_id, thread_id: reply_thread_id }),
4901
4955
  user: "scheduler",
4902
4956
  ...(adapterId ? { adapter_id: adapterId } : {}),
4903
4957
  },