@songsid/agend 2.0.11-beta.14 → 2.0.11-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.
- package/dist/classic-channel-manager.d.ts +5 -1
- package/dist/classic-channel-manager.js +11 -6
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +19 -21
- package/dist/cli.js.map +1 -1
- package/dist/fleet-manager.d.ts +8 -1
- package/dist/fleet-manager.js +53 -24
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +1 -1
- package/dist/instance-lifecycle.js +4 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/ui/view.html +21 -14
- package/dist/view-api.js +4 -28
- package/dist/view-api.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -205,6 +205,16 @@ export class FleetManager {
|
|
|
205
205
|
getInstanceDir(name) {
|
|
206
206
|
return join(this.dataDir, "instances", name);
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a slash-command target in a channel. Classic channels are looked up
|
|
210
|
+
* per-bot (same-channel multi-bot); a fleet-topic instance is found via the
|
|
211
|
+
* routing engine. Used by commands that work in BOTH contexts (/ctx, /compact,
|
|
212
|
+
* /cancel). Classic-only commands (/chat, /load) must NOT use this.
|
|
213
|
+
*/
|
|
214
|
+
resolveSlashTarget(channelId, adapterId) {
|
|
215
|
+
return this.classicChannels?.getInstanceByChannel(channelId, adapterId)
|
|
216
|
+
?? this.routing.resolve(channelId)?.name;
|
|
217
|
+
}
|
|
208
218
|
/** Get the adapter bound to an instance, falling back to primary adapter */
|
|
209
219
|
getAdapterForInstance(name) {
|
|
210
220
|
const worldId = this.instanceWorldBinding.get(name);
|
|
@@ -910,7 +920,7 @@ export class FleetManager {
|
|
|
910
920
|
await data.respond(`✅ Sent \`/chat load ${filename}\` to ${name}`);
|
|
911
921
|
}
|
|
912
922
|
else if (data.command === "compact") {
|
|
913
|
-
const name = this.
|
|
923
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
914
924
|
if (!name) {
|
|
915
925
|
await data.respond("No active agent in this channel.");
|
|
916
926
|
return;
|
|
@@ -919,7 +929,7 @@ export class FleetManager {
|
|
|
919
929
|
await data.respond(result);
|
|
920
930
|
}
|
|
921
931
|
else if (data.command === "cancel") {
|
|
922
|
-
const name = this.
|
|
932
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
923
933
|
if (!name) {
|
|
924
934
|
await data.respond("No active agent in this channel.");
|
|
925
935
|
return;
|
|
@@ -928,7 +938,7 @@ export class FleetManager {
|
|
|
928
938
|
await data.respond(ok ? `🛑 Sent cancel to ${name}.` : `❌ ${name} not running.`);
|
|
929
939
|
}
|
|
930
940
|
else if (data.command === "ctx") {
|
|
931
|
-
const name = this.
|
|
941
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
932
942
|
if (!name) {
|
|
933
943
|
await data.respond("No active agent in this channel.");
|
|
934
944
|
return;
|
|
@@ -937,8 +947,10 @@ export class FleetManager {
|
|
|
937
947
|
await data.respond(await this.topicCommands.getCtxText(name));
|
|
938
948
|
}
|
|
939
949
|
else if (data.command === "collab") {
|
|
950
|
+
// Classic no longer lives in the routing engine, so a routing hit here is
|
|
951
|
+
// always a fleet-topic instance.
|
|
940
952
|
const collabTarget = this.routing.resolve(data.channelId);
|
|
941
|
-
if (collabTarget
|
|
953
|
+
if (collabTarget) {
|
|
942
954
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
943
955
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
944
956
|
await data.respond("⛔ Not authorized");
|
|
@@ -1009,7 +1021,7 @@ export class FleetManager {
|
|
|
1009
1021
|
process.kill(process.pid, "SIGUSR2");
|
|
1010
1022
|
}
|
|
1011
1023
|
else if (data.command === "compact") {
|
|
1012
|
-
const name = this.
|
|
1024
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1013
1025
|
if (!name) {
|
|
1014
1026
|
await data.respond("No active agent in this channel.");
|
|
1015
1027
|
return;
|
|
@@ -1173,7 +1185,7 @@ export class FleetManager {
|
|
|
1173
1185
|
await data.respond(`✅ Sent \`/chat load ${filename}\` to ${name}`);
|
|
1174
1186
|
}
|
|
1175
1187
|
else if (data.command === "cancel") {
|
|
1176
|
-
const name = this.
|
|
1188
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1177
1189
|
if (!name) {
|
|
1178
1190
|
await data.respond("No active agent in this channel.");
|
|
1179
1191
|
return;
|
|
@@ -1182,7 +1194,7 @@ export class FleetManager {
|
|
|
1182
1194
|
await data.respond(ok ? `🛑 Sent cancel to ${name}.` : `❌ ${name} not running.`);
|
|
1183
1195
|
}
|
|
1184
1196
|
else if (data.command === "ctx") {
|
|
1185
|
-
const name = this.
|
|
1197
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1186
1198
|
if (!name) {
|
|
1187
1199
|
await data.respond("No active agent in this channel.");
|
|
1188
1200
|
return;
|
|
@@ -1191,8 +1203,10 @@ export class FleetManager {
|
|
|
1191
1203
|
await data.respond(await this.topicCommands.getCtxText(name));
|
|
1192
1204
|
}
|
|
1193
1205
|
else if (data.command === "collab") {
|
|
1206
|
+
// Classic no longer lives in the routing engine, so a routing hit here is
|
|
1207
|
+
// always a fleet-topic instance.
|
|
1194
1208
|
const collabTarget2 = this.routing.resolve(data.channelId);
|
|
1195
|
-
if (collabTarget2
|
|
1209
|
+
if (collabTarget2) {
|
|
1196
1210
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1197
1211
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1198
1212
|
await data.respond("⛔ Not authorized");
|
|
@@ -1263,7 +1277,7 @@ export class FleetManager {
|
|
|
1263
1277
|
process.kill(process.pid, "SIGUSR2");
|
|
1264
1278
|
}
|
|
1265
1279
|
else if (data.command === "compact") {
|
|
1266
|
-
const name = this.
|
|
1280
|
+
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1267
1281
|
if (!name) {
|
|
1268
1282
|
await data.respond("No active agent in this channel.");
|
|
1269
1283
|
return;
|
|
@@ -1762,9 +1776,12 @@ export class FleetManager {
|
|
|
1762
1776
|
if (msg.adapterId)
|
|
1763
1777
|
this.bindInstanceAdapter(generalInstance, msg.adapterId, true);
|
|
1764
1778
|
const inboundAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
1765
|
-
// React immediately — before any other API calls
|
|
1779
|
+
// React immediately — before any other API calls. Use the adapter BOUND to
|
|
1780
|
+
// the instance (not whichever same-guild bot received the event first) so
|
|
1781
|
+
// exactly the owning bot reacts — no duplicate 👀 from a sibling bot.
|
|
1766
1782
|
if (msg.chatId && msg.messageId) {
|
|
1767
|
-
|
|
1783
|
+
const reactAdapter = this.getAdapterForInstance(generalInstance) ?? inboundAdapter;
|
|
1784
|
+
reactAdapter.react(msg.threadId ?? msg.chatId, msg.messageId, "👀")
|
|
1768
1785
|
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
1769
1786
|
}
|
|
1770
1787
|
this.warnIfRateLimited(generalInstance, msg);
|
|
@@ -1843,9 +1860,12 @@ export class FleetManager {
|
|
|
1843
1860
|
if (msg.adapterId)
|
|
1844
1861
|
this.bindInstanceAdapter(instanceName, msg.adapterId, true);
|
|
1845
1862
|
const inboundAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
1846
|
-
// React immediately — before any other Discord API calls
|
|
1863
|
+
// React immediately — before any other Discord API calls. Use the adapter
|
|
1864
|
+
// BOUND to the instance (not whichever same-guild bot received the event
|
|
1865
|
+
// first) so exactly the owning bot reacts — no duplicate 👀 from a sibling.
|
|
1847
1866
|
if (msg.chatId && msg.messageId) {
|
|
1848
|
-
|
|
1867
|
+
const reactAdapter = this.getAdapterForInstance(instanceName) ?? inboundAdapter;
|
|
1868
|
+
reactAdapter.react(this.reactTarget(msg), msg.messageId, "👀")
|
|
1849
1869
|
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
1850
1870
|
}
|
|
1851
1871
|
// These may hit Discord API (topic icon, archive) — do after react
|
|
@@ -2589,15 +2609,17 @@ export class FleetManager {
|
|
|
2589
2609
|
startStatuslineWatcher(name) {
|
|
2590
2610
|
this.statuslineWatcher.watch(name);
|
|
2591
2611
|
}
|
|
2592
|
-
reactMessageStatus(chatId, messageId, emoji) {
|
|
2593
|
-
//
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2612
|
+
reactMessageStatus(instanceName, chatId, messageId, emoji) {
|
|
2613
|
+
// React via the adapter BOUND to this instance — NOT the first discord world.
|
|
2614
|
+
// Otherwise, in a same-channel/same-guild multi-bot setup, the inbound 👀
|
|
2615
|
+
// (bound bot) and the delivery/confirm reactions (some other bot) come from
|
|
2616
|
+
// different bots, leaving a duplicate 👀 that never turns into ✅.
|
|
2617
|
+
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
2618
|
+
// Status reactions are Discord-only (TG/others use the inbound react path).
|
|
2619
|
+
if (!adapter || adapter.type !== "discord")
|
|
2620
|
+
return;
|
|
2621
|
+
adapter.react(chatId, messageId, emoji)
|
|
2622
|
+
.catch(e => this.logger.debug({ err: e.message }, "Message status react failed"));
|
|
2601
2623
|
}
|
|
2602
2624
|
// ── Model failover ──────────────────────────────────────────────────────
|
|
2603
2625
|
static FAILOVER_TRIGGER_PCT = 90;
|
|
@@ -3256,8 +3278,15 @@ When users create specialized instances, suggest these configurations:
|
|
|
3256
3278
|
: "");
|
|
3257
3279
|
ClassicChannelManager.logMessage(instanceName, msg.username, text + collabAttachTag, msg.timestamp, msg.replyToText);
|
|
3258
3280
|
this.logger.info({ instanceName, user: msg.username, textLen: text.length, attachments: msg.attachments?.length ?? 0, source: msg.source }, "Collab mode message");
|
|
3259
|
-
// Check for @mention trigger: must be exact <@BOT_USER_ID>, not @everyone/@here
|
|
3260
|
-
|
|
3281
|
+
// Check for @mention trigger: must be exact <@BOT_USER_ID>, not @everyone/@here.
|
|
3282
|
+
// Each bot matches ONLY its own id. A secondary bot must NOT fall back to the
|
|
3283
|
+
// process-wide botUserId (the primary's) — otherwise, in a same-channel
|
|
3284
|
+
// multi-bot setup, an @mention of the primary would also match the secondary
|
|
3285
|
+
// and BOTH bots would react 👀 and forward. Only the primary adapter may use
|
|
3286
|
+
// the fallback.
|
|
3287
|
+
const mentionWorld = this.worlds.get(msg.adapterId ?? "");
|
|
3288
|
+
const isPrimaryAdapter = !mentionWorld || mentionWorld.adapter === this.adapter;
|
|
3289
|
+
const adapterBotUserId = mentionWorld?.botUserId ?? (isPrimaryAdapter ? this.botUserId : undefined);
|
|
3261
3290
|
const mentionTag = adapterBotUserId ? `<@${adapterBotUserId}>` : null;
|
|
3262
3291
|
const isMentioned = mentionTag && text.includes(mentionTag);
|
|
3263
3292
|
if (!isMentioned) {
|