@songsid/agend 2.1.6 → 2.1.7-beta.2
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/backend/codex.d.ts +8 -3
- package/dist/backend/codex.js +118 -9
- package/dist/backend/codex.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +1 -0
- package/dist/channel/adapters/discord.js +25 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +13 -0
- package/dist/channel/adapters/telegram.js +44 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +4 -0
- package/dist/daemon.d.ts +6 -1
- package/dist/daemon.js +70 -27
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +23 -8
- package/dist/fleet-manager.js +101 -28
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -1
- package/dist/instance-lifecycle.js +53 -9
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +5 -1
- package/dist/turn-reply-guard.js +6 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -1015,7 +1015,29 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1015
1015
|
removeInstance(name: string, authorization: ExplicitInstanceRemoval): Promise<void>;
|
|
1016
1016
|
startStatuslineWatcher(name: string): void;
|
|
1017
1017
|
stopStatuslineWatcher(name: string): void;
|
|
1018
|
-
|
|
1018
|
+
/**
|
|
1019
|
+
* Last delivery status applied per bot+message. Unbounded by design: any cap
|
|
1020
|
+
* reintroduces the hole where a forgotten ❌ can never be cleared, and an
|
|
1021
|
+
* entry is two short strings (id pair plus one emoji) — thousands of
|
|
1022
|
+
* status-reacted messages cost kilobytes. A bot restart loses it, degrading
|
|
1023
|
+
* to a plain add (never worse than the old behaviour).
|
|
1024
|
+
*/
|
|
1025
|
+
private lastStatusEmoji;
|
|
1026
|
+
/**
|
|
1027
|
+
* One in-flight status update per bot+message: updates run strictly in call
|
|
1028
|
+
* order, so a delayed ❌ add can never land after a newer ✅'s removal —
|
|
1029
|
+
* the final state is always the latest call. Entries delete themselves when
|
|
1030
|
+
* the chain drains, so idle messages hold no memory.
|
|
1031
|
+
*/
|
|
1032
|
+
private deliveryStatusChains;
|
|
1033
|
+
reactMessageStatus(instanceName: string, chatId: string, messageId: string, emoji: string, threadId?: string): void;
|
|
1034
|
+
/**
|
|
1035
|
+
* Apply a terminal delivery verdict. Telegram maps ✅ to 👀 and ❌ to 👎 in
|
|
1036
|
+
* reactMessageStatus; Discord continues to show the verdict emoji directly.
|
|
1037
|
+
*/
|
|
1038
|
+
finishDeliveryStatus(instanceName: string, chatId: string, messageId: string, emoji: string, threadId?: string): void;
|
|
1039
|
+
private queueDeliveryStatusReaction;
|
|
1040
|
+
private applyDeliveryStatusReaction;
|
|
1019
1041
|
private static FAILOVER_TRIGGER_PCT;
|
|
1020
1042
|
private static FAILOVER_RECOVER_PCT;
|
|
1021
1043
|
checkModelFailover(name: string, fiveHourPct: number): void;
|
|
@@ -1356,13 +1378,6 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1356
1378
|
* Used by report_result, where the sender's self-derived name may not match
|
|
1357
1379
|
* the target-address name the button was registered under. */
|
|
1358
1380
|
clearCancelButtonByCorrelation(correlationId: string): void;
|
|
1359
|
-
/**
|
|
1360
|
-
* Reaction target chat id. Telegram reactions key on the supergroup chat_id
|
|
1361
|
-
* (the topic thread is NOT a chat_id), so a forum-topic message must react on
|
|
1362
|
-
* msg.chatId — reacting on threadId silently fails. Discord reactions key on
|
|
1363
|
-
* the channel/thread id.
|
|
1364
|
-
*/
|
|
1365
|
-
private reactTarget;
|
|
1366
1381
|
/** Remember the user message just delivered, so we can react ✅ when done. */
|
|
1367
1382
|
private trackInboundMsg;
|
|
1368
1383
|
/** Clear the tracked last-inbound message after the agent replies. The ✅
|
package/dist/fleet-manager.js
CHANGED
|
@@ -16,6 +16,17 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
17
|
/** Fallback access policy for a channel with no `access:` block — open (no gate). */
|
|
18
18
|
const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
|
|
19
|
+
/**
|
|
20
|
+
* Telegram bot reactions replace a single slot per message. Keep delivery state
|
|
21
|
+
* presentation here so supported emoji choices can change without altering the
|
|
22
|
+
* status reconciliation logic.
|
|
23
|
+
*/
|
|
24
|
+
const TELEGRAM_DELIVERY_STATUS_REACTIONS = new Map([
|
|
25
|
+
["👀", "👀"], // queued / processing
|
|
26
|
+
["⏳", "👀"],
|
|
27
|
+
["✅", "👀"], // confirmed
|
|
28
|
+
["❌", "👎"], // failed
|
|
29
|
+
]);
|
|
19
30
|
import { isProbeableRouteTarget } from "./fleet-context.js";
|
|
20
31
|
import { loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG } from "./config.js";
|
|
21
32
|
import { EventLog } from "./event-log.js";
|
|
@@ -25,6 +36,7 @@ import { TmuxManager } from "./tmux-manager.js";
|
|
|
25
36
|
import { AccessManager } from "./channel/access-manager.js";
|
|
26
37
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
27
38
|
import { createAdapter } from "./channel/factory.js";
|
|
39
|
+
import { TelegramAdapter } from "./channel/adapters/telegram.js";
|
|
28
40
|
import { createBackend } from "./backend/factory.js";
|
|
29
41
|
import { isModelCompatible } from "./backend/types.js";
|
|
30
42
|
import { createLogger, rotateLogIfNeeded } from "./logger.js";
|
|
@@ -4950,13 +4962,11 @@ export class FleetManager {
|
|
|
4950
4962
|
if (msg.adapterId)
|
|
4951
4963
|
this.bindInstanceAdapter(generalInstance, msg.adapterId, true);
|
|
4952
4964
|
const inboundAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
4953
|
-
// React immediately — before any other API calls
|
|
4954
|
-
//
|
|
4955
|
-
//
|
|
4965
|
+
// React immediately — before any other API calls — through the status
|
|
4966
|
+
// path, so a later delivery status replaces (not stacks onto) this
|
|
4967
|
+
// acknowledgement.
|
|
4956
4968
|
if (msg.chatId && msg.messageId) {
|
|
4957
|
-
|
|
4958
|
-
reactAdapter.react(msg.threadId ?? msg.chatId, msg.messageId, "👀")
|
|
4959
|
-
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
4969
|
+
this.reactMessageStatus(generalInstance, msg.chatId, msg.messageId, "👀", msg.threadId || undefined);
|
|
4960
4970
|
}
|
|
4961
4971
|
this.warnIfRateLimited(generalInstance, msg);
|
|
4962
4972
|
const { text, extraMeta } = await processAttachments(msg, inboundAdapter, this.logger, generalInstance);
|
|
@@ -5044,13 +5054,11 @@ export class FleetManager {
|
|
|
5044
5054
|
if (msg.adapterId)
|
|
5045
5055
|
this.bindInstanceAdapter(instanceName, msg.adapterId, true);
|
|
5046
5056
|
const inboundAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
5047
|
-
// React immediately — before any other Discord API calls
|
|
5048
|
-
//
|
|
5049
|
-
//
|
|
5057
|
+
// React immediately — before any other Discord API calls — through the
|
|
5058
|
+
// status path, so a later delivery status replaces (not stacks onto) this
|
|
5059
|
+
// acknowledgement. Same bound-adapter routing as the status path itself.
|
|
5050
5060
|
if (msg.chatId && msg.messageId) {
|
|
5051
|
-
|
|
5052
|
-
reactAdapter.react(this.reactTarget(msg), msg.messageId, "👀")
|
|
5053
|
-
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
5061
|
+
this.reactMessageStatus(instanceName, msg.chatId, msg.messageId, "👀", msg.threadId || undefined);
|
|
5054
5062
|
}
|
|
5055
5063
|
// These may hit Discord API (topic icon, archive) — do after react
|
|
5056
5064
|
if (this.topicArchiver.isArchived(threadId)) {
|
|
@@ -5089,8 +5097,10 @@ export class FleetManager {
|
|
|
5089
5097
|
catch (err) {
|
|
5090
5098
|
this.logger.warn({ err: err.message, instanceName }, "Wake/delivery failed");
|
|
5091
5099
|
if (msg.chatId && msg.messageId) {
|
|
5092
|
-
|
|
5093
|
-
|
|
5100
|
+
// Reconciled status path (not a bare add): a retry that later
|
|
5101
|
+
// succeeds replaces this ❌ with ✅ instead of leaving both. Chat and
|
|
5102
|
+
// thread travel separately so Telegram addresses the supergroup.
|
|
5103
|
+
this.finishDeliveryStatus(instanceName, msg.chatId, msg.messageId, "❌", msg.threadId || undefined);
|
|
5094
5104
|
}
|
|
5095
5105
|
return;
|
|
5096
5106
|
}
|
|
@@ -6503,17 +6513,89 @@ export class FleetManager {
|
|
|
6503
6513
|
// Pausing stops I/O but retains the last observed limits for status views.
|
|
6504
6514
|
this.statuslineWatcher.unwatch(name, true);
|
|
6505
6515
|
}
|
|
6506
|
-
|
|
6516
|
+
/**
|
|
6517
|
+
* Last delivery status applied per bot+message. Unbounded by design: any cap
|
|
6518
|
+
* reintroduces the hole where a forgotten ❌ can never be cleared, and an
|
|
6519
|
+
* entry is two short strings (id pair plus one emoji) — thousands of
|
|
6520
|
+
* status-reacted messages cost kilobytes. A bot restart loses it, degrading
|
|
6521
|
+
* to a plain add (never worse than the old behaviour).
|
|
6522
|
+
*/
|
|
6523
|
+
lastStatusEmoji = new Map();
|
|
6524
|
+
/**
|
|
6525
|
+
* One in-flight status update per bot+message: updates run strictly in call
|
|
6526
|
+
* order, so a delayed ❌ add can never land after a newer ✅'s removal —
|
|
6527
|
+
* the final state is always the latest call. Entries delete themselves when
|
|
6528
|
+
* the chain drains, so idle messages hold no memory.
|
|
6529
|
+
*/
|
|
6530
|
+
deliveryStatusChains = new Map();
|
|
6531
|
+
reactMessageStatus(instanceName, chatId, messageId, emoji, threadId) {
|
|
6507
6532
|
// React via the adapter BOUND to this instance — NOT the first discord world.
|
|
6508
6533
|
// Otherwise, in a same-channel/same-guild multi-bot setup, the inbound 👀
|
|
6509
6534
|
// (bound bot) and the delivery/confirm reactions (some other bot) come from
|
|
6510
6535
|
// different bots, leaving a duplicate 👀 that never turns into ✅.
|
|
6511
6536
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
6512
|
-
|
|
6513
|
-
if (!adapter || adapter.type !== "discord")
|
|
6537
|
+
if (!adapter)
|
|
6514
6538
|
return;
|
|
6515
|
-
adapter
|
|
6516
|
-
|
|
6539
|
+
const statusEmoji = adapter instanceof TelegramAdapter
|
|
6540
|
+
? TELEGRAM_DELIVERY_STATUS_REACTIONS.get(emoji)
|
|
6541
|
+
: emoji;
|
|
6542
|
+
// Telegram has a deliberately smaller status vocabulary, and every mapped
|
|
6543
|
+
// value is accepted by ReactionTypeEmoji. Unknown statuses stay a no-op.
|
|
6544
|
+
if (!statusEmoji)
|
|
6545
|
+
return;
|
|
6546
|
+
// Bot-scoped: sibling bots reacting on the same message must not clear
|
|
6547
|
+
// each other's state — every removal below targets this adapter's own
|
|
6548
|
+
// reactions (@me on Discord, the bot's list on Telegram).
|
|
6549
|
+
const adapterId = typeof adapter.id === "string"
|
|
6550
|
+
? adapter.id : "?";
|
|
6551
|
+
const key = `${adapterId}:${chatId}:${threadId ?? ""}:${messageId}`;
|
|
6552
|
+
this.queueDeliveryStatusReaction(adapter, key, chatId, messageId, statusEmoji, threadId);
|
|
6553
|
+
}
|
|
6554
|
+
/**
|
|
6555
|
+
* Apply a terminal delivery verdict. Telegram maps ✅ to 👀 and ❌ to 👎 in
|
|
6556
|
+
* reactMessageStatus; Discord continues to show the verdict emoji directly.
|
|
6557
|
+
*/
|
|
6558
|
+
finishDeliveryStatus(instanceName, chatId, messageId, emoji, threadId) {
|
|
6559
|
+
this.reactMessageStatus(instanceName, chatId, messageId, emoji, threadId);
|
|
6560
|
+
}
|
|
6561
|
+
queueDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId) {
|
|
6562
|
+
const prev = this.deliveryStatusChains.get(key) ?? Promise.resolve();
|
|
6563
|
+
const run = prev.then(() => this.applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId));
|
|
6564
|
+
this.deliveryStatusChains.set(key, run);
|
|
6565
|
+
void run.then(() => { if (this.deliveryStatusChains.get(key) === run)
|
|
6566
|
+
this.deliveryStatusChains.delete(key); }, () => { if (this.deliveryStatusChains.get(key) === run)
|
|
6567
|
+
this.deliveryStatusChains.delete(key); });
|
|
6568
|
+
}
|
|
6569
|
+
async applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId) {
|
|
6570
|
+
try {
|
|
6571
|
+
const target = adapter instanceof TelegramAdapter ? chatId : (threadId ?? chatId);
|
|
6572
|
+
const prev = this.lastStatusEmoji.get(key);
|
|
6573
|
+
if (emoji == null) {
|
|
6574
|
+
if (prev && adapter.unreact) {
|
|
6575
|
+
await adapter.unreact(target, messageId, prev, threadId);
|
|
6576
|
+
this.lastStatusEmoji.delete(key);
|
|
6577
|
+
}
|
|
6578
|
+
return;
|
|
6579
|
+
}
|
|
6580
|
+
// A Telegram status may map multiple lifecycle states to the same
|
|
6581
|
+
// reaction. Avoid resending it: an agent may have replaced the one
|
|
6582
|
+
// reaction slot with its own 👍 between delivered and confirmed.
|
|
6583
|
+
if (prev === emoji)
|
|
6584
|
+
return;
|
|
6585
|
+
// Thread-aware adapters (Discord) react where the thread is; Telegram
|
|
6586
|
+
// addresses the supergroup chat and ignores the thread part.
|
|
6587
|
+
// Discord needs the old reaction removed before the new one is added.
|
|
6588
|
+
// Telegram's setMessageReaction replaces the bot's one current reaction.
|
|
6589
|
+
if (prev && prev !== emoji && adapter.unreact && !(adapter instanceof TelegramAdapter)) {
|
|
6590
|
+
// Best effort: a failed removal must not block the new status.
|
|
6591
|
+
await adapter.unreact(target, messageId, prev, threadId).catch(e => this.logger.debug({ err: e.message }, "Delivery status reaction removal failed"));
|
|
6592
|
+
}
|
|
6593
|
+
await adapter.react(target, messageId, emoji, threadId);
|
|
6594
|
+
this.lastStatusEmoji.set(key, emoji);
|
|
6595
|
+
}
|
|
6596
|
+
catch (e) {
|
|
6597
|
+
this.logger.debug({ err: e.message }, "Message status react failed");
|
|
6598
|
+
}
|
|
6517
6599
|
}
|
|
6518
6600
|
// ── Model failover ──────────────────────────────────────────────────────
|
|
6519
6601
|
static FAILOVER_TRIGGER_PCT = 90;
|
|
@@ -8186,15 +8268,6 @@ export class FleetManager {
|
|
|
8186
8268
|
this.retireButton(e);
|
|
8187
8269
|
}
|
|
8188
8270
|
}
|
|
8189
|
-
/**
|
|
8190
|
-
* Reaction target chat id. Telegram reactions key on the supergroup chat_id
|
|
8191
|
-
* (the topic thread is NOT a chat_id), so a forum-topic message must react on
|
|
8192
|
-
* msg.chatId — reacting on threadId silently fails. Discord reactions key on
|
|
8193
|
-
* the channel/thread id.
|
|
8194
|
-
*/
|
|
8195
|
-
reactTarget(msg) {
|
|
8196
|
-
return msg.source === "telegram" ? msg.chatId : (msg.threadId ?? msg.chatId);
|
|
8197
|
-
}
|
|
8198
8271
|
/** Remember the user message just delivered, so we can react ✅ when done. */
|
|
8199
8272
|
trackInboundMsg(instanceName, msg) {
|
|
8200
8273
|
if (!msg.chatId || !msg.messageId)
|