@songsid/agend 2.0.5-beta.2 → 2.0.5-beta.21

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.
@@ -539,9 +539,9 @@ export class FleetManager {
539
539
  // Snapshotted at startup — new decisions via post_decision are available
540
540
  // through list_decisions tool but not auto-injected until restart.
541
541
  try {
542
- const decisions = this.scheduler.db.listDecisions("", { includeArchived: false });
542
+ const decisions = this.scheduler.db.listAllActiveDecisions();
543
543
  if (decisions.length > 0) {
544
- const capped = decisions.slice(0, 20).map(d => ({ title: d.title, content: (d.content ?? "").slice(0, 200) }));
544
+ const capped = decisions.slice(0, 20).map(d => ({ title: d.title, content: (d.content ?? "").slice(0, 200), scope: d.scope, project_root: d.project_root }));
545
545
  process.env.AGEND_DECISIONS = JSON.stringify(capped);
546
546
  this.logger.info({ count: decisions.length, injected: capped.length }, "Injected active decisions into env");
547
547
  }
@@ -780,7 +780,7 @@ export class FleetManager {
780
780
  timestamp: new Date(),
781
781
  });
782
782
  }
783
- else if (data.command === "compact" || data.command === "save" || data.command === "load") {
783
+ else if (data.command === "save" || data.command === "load") {
784
784
  if (!this.classicChannels?.isAdmin(data.userId)) {
785
785
  await data.respond("⛔ This command requires admin access.");
786
786
  return;
@@ -791,10 +791,7 @@ export class FleetManager {
791
791
  return;
792
792
  }
793
793
  let rawCmd;
794
- if (data.command === "compact") {
795
- rawCmd = "/compact";
796
- }
797
- else if (data.command === "save") {
794
+ if (data.command === "save") {
798
795
  const filename = data.options?.filename;
799
796
  if (!/^[\w.-]+$/.test(filename)) {
800
797
  await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
@@ -813,6 +810,15 @@ export class FleetManager {
813
810
  this.pasteRawToClassicInstance(target.name, rawCmd);
814
811
  await data.respond(`✅ Sent \`${rawCmd}\` to ${target.name}`);
815
812
  }
813
+ else if (data.command === "compact") {
814
+ const target = this.routing.resolve(data.channelId);
815
+ if (!target) {
816
+ await data.respond("No active agent in this channel.");
817
+ return;
818
+ }
819
+ const result = await this.topicCommands.sendCompact(target.name);
820
+ await data.respond(result);
821
+ }
816
822
  else if (data.command === "ctx") {
817
823
  const target = this.routing.resolve(data.channelId);
818
824
  if (!target) {
@@ -1059,7 +1065,7 @@ export class FleetManager {
1059
1065
  timestamp: new Date(),
1060
1066
  });
1061
1067
  }
1062
- else if (data.command === "compact" || data.command === "save" || data.command === "load") {
1068
+ else if (data.command === "save" || data.command === "load") {
1063
1069
  if (!this.classicChannels?.isAdmin(data.userId)) {
1064
1070
  await data.respond("⛔ This command requires admin access.");
1065
1071
  return;
@@ -1070,10 +1076,7 @@ export class FleetManager {
1070
1076
  return;
1071
1077
  }
1072
1078
  let rawCmd;
1073
- if (data.command === "compact") {
1074
- rawCmd = "/compact";
1075
- }
1076
- else if (data.command === "save") {
1079
+ if (data.command === "save") {
1077
1080
  const filename = data.options?.filename;
1078
1081
  if (!/^[\w.-]+$/.test(filename)) {
1079
1082
  await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
@@ -1092,6 +1095,15 @@ export class FleetManager {
1092
1095
  this.pasteRawToClassicInstance(target.name, rawCmd);
1093
1096
  await data.respond(`✅ Sent \`${rawCmd}\` to ${target.name}`);
1094
1097
  }
1098
+ else if (data.command === "compact") {
1099
+ const target = this.routing.resolve(data.channelId);
1100
+ if (!target) {
1101
+ await data.respond("No active agent in this channel.");
1102
+ return;
1103
+ }
1104
+ const result = await this.topicCommands.sendCompact(target.name);
1105
+ await data.respond(result);
1106
+ }
1095
1107
  else if (data.command === "ctx") {
1096
1108
  const target = this.routing.resolve(data.channelId);
1097
1109
  if (!target) {
@@ -1486,6 +1498,10 @@ export class FleetManager {
1486
1498
  if (text === "/start" || text.startsWith("/start ")) {
1487
1499
  if (isPrivateChat) {
1488
1500
  if (!this.classicChannels.isUserAllowed(msg.userId)) {
1501
+ const generalId = this.findGeneralInstance(msg.adapterId);
1502
+ if (generalId) {
1503
+ this.notifyInstanceTopic(generalId, `🆕 Unauthorized user tried /start in private chat:\n• Name: ${msg.username}\n• ID: ${msg.userId}\n• Platform: ${msg.source}\n\nTo allow: add \`${msg.userId}\` to classicBot.yaml \`allowed_users\``);
1504
+ }
1489
1505
  await msgAdapter?.sendText(chatId, "⛔ You are not in the allowed users list.");
1490
1506
  return;
1491
1507
  }
@@ -1547,14 +1563,24 @@ export class FleetManager {
1547
1563
  await msgAdapter?.sendText(chatId, result);
1548
1564
  return;
1549
1565
  }
1566
+ // Handle /ctx command
1567
+ if (text === "/ctx" || text.startsWith("/ctx@")) {
1568
+ const ctxTarget = this.routing.resolve(chatId);
1569
+ if (!ctxTarget || ctxTarget.kind !== "classic") {
1570
+ await msgAdapter?.sendText(chatId, "No active agent. Use /start first.");
1571
+ return;
1572
+ }
1573
+ const reply = await this.topicCommands.getCtxText(ctxTarget.name);
1574
+ await msgAdapter?.sendText(chatId, reply);
1575
+ return;
1576
+ }
1550
1577
  // Route to classic channel if registered
1551
1578
  const target = this.routing.resolve(chatId);
1552
1579
  if (target?.kind === "classic") {
1553
1580
  if (msg.adapterId)
1554
1581
  this.bindInstanceAdapter(target.name, msg.adapterId, true);
1555
- // TG ClassicBot: only @mention triggers agent (both private and group).
1556
- // /chat command is NOT supported for TG classic — use @bot instead.
1557
- if (!isBotMentioned) {
1582
+ // TG ClassicBot: group requires @mention, private chat forwards directly.
1583
+ if (!isPrivateChat && !isBotMentioned) {
1558
1584
  // No trigger: save attachments + react, log, but don't forward to agent
1559
1585
  const syntheticMsg = { ...msg, threadId: chatId, text: rawText.startsWith("/") ? "" : rawText };
1560
1586
  await this.handleClassicChannelMessage(target.name, syntheticMsg);
@@ -1754,7 +1780,7 @@ export class FleetManager {
1754
1780
  const routingConfig = senderInstanceName
1755
1781
  ? this.fleetConfig?.instances[senderInstanceName]
1756
1782
  : (senderSessionName ? undefined : this.fleetConfig?.instances[instanceName]);
1757
- const threadId = resolveReplyThreadId(args.thread_id, routingConfig)
1783
+ let threadId = resolveReplyThreadId(args.thread_id, routingConfig)
1758
1784
  ?? this.classicChannels?.getChannelIdByInstance(senderInstanceName ?? instanceName);
1759
1785
  // Select adapter: use instance binding, or resolve from chatId in args
1760
1786
  const outAdapter = this.getAdapterForInstance(senderInstanceName ?? instanceName) ?? this.adapter;
@@ -1762,6 +1788,15 @@ export class FleetManager {
1762
1788
  respond(null, "No adapter available");
1763
1789
  return;
1764
1790
  }
1791
+ // For classic instances: clear thread_id to prevent TG interpreting it as message_thread_id
1792
+ // (classic chats — private or group — don't use forum threads)
1793
+ const classicChannelId = this.classicChannels?.getChannelIdByInstance(senderInstanceName ?? instanceName);
1794
+ if (classicChannelId) {
1795
+ if (!args.chat_id)
1796
+ args.chat_id = classicChannelId;
1797
+ delete args.thread_id;
1798
+ threadId = undefined;
1799
+ }
1765
1800
  // Route standard channel tools (reply, react, edit_message, download_attachment)
1766
1801
  if (routeToolCall(outAdapter, tool, args, threadId, respond)) {
1767
1802
  if (tool === "reply") {
@@ -2799,6 +2834,14 @@ When users create specialized instances, suggest these configurations:
2799
2834
  const text = msg.text ?? "";
2800
2835
  const channelId = msg.threadId ?? msg.chatId;
2801
2836
  const isCollabMode = this.classicChannels?.isCollab(channelId) ?? false;
2837
+ // Handle /ctx in classic mode — always, regardless of collab mode
2838
+ if (text === "/ctx" || text.startsWith("/ctx@")) {
2839
+ const reply = await this.topicCommands.getCtxText(instanceName);
2840
+ const classicAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
2841
+ if (classicAdapter)
2842
+ await classicAdapter.sendText(msg.threadId ?? msg.chatId, reply, { threadId: msg.threadId });
2843
+ return;
2844
+ }
2802
2845
  // Collab mode: trigger on @mention of our bot, log all messages
2803
2846
  if (isCollabMode) {
2804
2847
  // Skip empty bot messages (e.g., reactions) — don't pollute chat log
@@ -2883,14 +2926,6 @@ When users create specialized instances, suggest these configurations:
2883
2926
  await this.forwardToClassicInstance(instanceName, finalText, msg, extraMeta);
2884
2927
  return;
2885
2928
  }
2886
- // Handle /ctx in classic mode
2887
- if (text === "/ctx" || text.startsWith("/ctx@")) {
2888
- const reply = await this.topicCommands.getCtxText(instanceName);
2889
- const classicAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
2890
- if (classicAdapter)
2891
- await classicAdapter.sendText(msg.threadId ?? msg.chatId, reply, { threadId: msg.threadId });
2892
- return;
2893
- }
2894
2929
  // Normal mode: /chat trigger
2895
2930
  const isChat = text.startsWith("/chat ") || text === "/chat";
2896
2931
  this.logger.info({ instanceName, user: msg.username, textLen: text.length, hasChat: isChat }, "classic channel message received");
@@ -3077,8 +3112,13 @@ When users create specialized instances, suggest these configurations:
3077
3112
  async handleClassicStart(channelId, channelName, userId, guildId) {
3078
3113
  if (!this.classicChannels)
3079
3114
  return "Classic channel manager not initialized.";
3080
- if (guildId && !this.classicChannels.isGuildAllowed(guildId))
3115
+ if (guildId && !this.classicChannels.isGuildAllowed(guildId)) {
3116
+ const generalId = this.findGeneralInstance();
3117
+ if (generalId) {
3118
+ this.notifyInstanceTopic(generalId, `🆕 Unauthorized guild tried /start:\n• Guild ID: ${guildId}\n• User: ${userId}\n• Platform: discord\n\nTo allow: add \`${guildId}\` to classicBot.yaml \`allowed_guilds\``);
3119
+ }
3081
3120
  return "⛔ This server is not in the allowed guilds list.";
3121
+ }
3082
3122
  if (this.classicChannels.isClassicChannel(channelId))
3083
3123
  return "This channel already has an active agent. Use /chat to talk.";
3084
3124
  if (this.routing.resolve(channelId))
@@ -3088,8 +3128,8 @@ When users create specialized instances, suggest these configurations:
3088
3128
  this.routing.register(channelId, { kind: "classic", name: instanceName });
3089
3129
  await this.startClassicInstance(instanceName, this.classicChannels.getBackend(channelId, this.fleetConfig?.defaults?.backend), this.classicChannels.getPreTaskCommand(channelId), this.classicChannels.getModel(channelId, this.fleetConfig?.defaults?.model));
3090
3130
  this.reregisterClassicChannels();
3091
- // Auto-enable collab for Discord classic channels
3092
- if (!this.classicChannels.isCollab(channelId)) {
3131
+ // Auto-enable collab for Discord classic channels (TG uses @mention directly without collab mode)
3132
+ if (guildId && !this.classicChannels.isCollab(channelId)) {
3093
3133
  this.classicChannels.toggleCollab(channelId);
3094
3134
  }
3095
3135
  this.logger.info({ channelId, instanceName, userId }, "Classic channel started");