@songsid/agend 2.1.7-beta.6 β 2.1.7-beta.7
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 -0
- package/dist/backend/codex.js +106 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +16 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/config-validator.js +41 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +8 -0
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +59 -7
- package/dist/fleet-manager.js +167 -59
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-config-impact.js +5 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -2
- package/dist/instance-lifecycle.js +30 -21
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +11 -0
- package/dist/instructions.js +16 -1
- package/dist/instructions.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -1
- package/dist/settings-api.d.ts +17 -0
- package/dist/settings-api.js +62 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/status-emojis.d.ts +118 -0
- package/dist/status-emojis.js +206 -0
- package/dist/status-emojis.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/ui/settings.html +168 -6
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type FleetMemory } from "./process-memory.js";
|
|
2
2
|
import { ReplyDeduper } from "./reply-dedup.js";
|
|
3
3
|
import type { FleetConfig, RawFleetConfig, InstanceConfig } from "./types.js";
|
|
4
|
+
import { type DeliveryStatus, type ResolvedStatusEmojis } from "./status-emojis.js";
|
|
4
5
|
import { type RouteTarget } from "./fleet-context.js";
|
|
5
6
|
import { EventLog } from "./event-log.js";
|
|
6
7
|
import { AdapterWorld } from "./adapter-world.js";
|
|
7
8
|
import { CostGuard } from "./cost-guard.js";
|
|
8
9
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
9
|
-
import type { AdapterHealthSnapshot, ChannelAdapter } from "./channel/types.js";
|
|
10
|
+
import type { AdapterHealthSnapshot, ChannelAdapter, InboundReaction } from "./channel/types.js";
|
|
10
11
|
import { type Logger } from "./logger.js";
|
|
11
12
|
import { Scheduler } from "./scheduler/index.js";
|
|
12
13
|
import type { FleetContext } from "./fleet-context.js";
|
|
@@ -1076,12 +1077,63 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1076
1077
|
private deliveryStatusChains;
|
|
1077
1078
|
/** #725: in-flight CLI env probes, keyed by backend name. Coalesces concurrent /model requests. */
|
|
1078
1079
|
private pendingCliEnvProbes;
|
|
1079
|
-
reactMessageStatus(instanceName: string, chatId: string, messageId: string, emoji: string, threadId?: string): void;
|
|
1080
1080
|
/**
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1081
|
+
* The status emojis for an instance (#1005): its own `status_emojis`, then
|
|
1082
|
+
* its channel's `options.status_emojis`, then the platform built-in. An
|
|
1083
|
+
* unusable value warns once and falls through, never failing the reaction.
|
|
1084
|
+
* `adapterId` pins the channel for paths that already know the bot (classic).
|
|
1083
1085
|
*/
|
|
1084
|
-
|
|
1086
|
+
resolveStatusEmojisFor(instanceName: string, adapterId?: string, adapter?: ChannelAdapter | null): ResolvedStatusEmojis & {
|
|
1087
|
+
platform: string | undefined;
|
|
1088
|
+
};
|
|
1089
|
+
private warnedStatusEmojis;
|
|
1090
|
+
/**
|
|
1091
|
+
* A Discord connection's server emojis for the Settings picker (#1005).
|
|
1092
|
+
* Cached per connection β a server's emoji set changes rarely β and one
|
|
1093
|
+
* fetch at a time; `refresh` forces a new one. Uses the running adapter, so
|
|
1094
|
+
* the bot token never leaves the server.
|
|
1095
|
+
*/
|
|
1096
|
+
listGuildEmojis(channelId: string, refresh?: boolean): Promise<{
|
|
1097
|
+
ok: true;
|
|
1098
|
+
fetched_at: number;
|
|
1099
|
+
emojis: Array<{
|
|
1100
|
+
id: string;
|
|
1101
|
+
name: string;
|
|
1102
|
+
animated: boolean;
|
|
1103
|
+
available: boolean;
|
|
1104
|
+
}>;
|
|
1105
|
+
} | {
|
|
1106
|
+
ok: false;
|
|
1107
|
+
error: string;
|
|
1108
|
+
}>;
|
|
1109
|
+
private static GUILD_EMOJI_TTL_MS;
|
|
1110
|
+
private guildEmojiCache;
|
|
1111
|
+
private guildEmojiFetches;
|
|
1112
|
+
/** What the instructions tell `instanceName` not to react with (its own status set). */
|
|
1113
|
+
statusEmojiAvoidList(instanceName: string): string[];
|
|
1114
|
+
/** The progress bubble's leading emoji, in the form the channel renders. */
|
|
1115
|
+
private progressPrefixFor;
|
|
1116
|
+
/**
|
|
1117
|
+
* Is this inbound reaction one of the fleet's own delivery-status stamps?
|
|
1118
|
+
* Those are machine indicators and must not reach any instance as a user
|
|
1119
|
+
* reaction (#1005 point 3). When every bot's user id is known, the check
|
|
1120
|
+
* keys on WHO reacted: a fleet bot's reaction is a status stamp only if it
|
|
1121
|
+
* is in that bot's instances' status set (other bot reactions are agent
|
|
1122
|
+
* signals and pass), and a human's reaction always passes β whatever emoji
|
|
1123
|
+
* a persona picked. Without every id, fall back to the emoji alone.
|
|
1124
|
+
*/
|
|
1125
|
+
isOwnStatusReaction(r: InboundReaction): boolean;
|
|
1126
|
+
/**
|
|
1127
|
+
* The "received" reaction for paths that react with the inbound message's
|
|
1128
|
+
* own adapter rather than through reactMessageStatus (classic bots).
|
|
1129
|
+
*/
|
|
1130
|
+
private receivedReactionFor;
|
|
1131
|
+
reactMessageStatus(instanceName: string, chatId: string, messageId: string, status: DeliveryStatus, threadId?: string): void;
|
|
1132
|
+
/**
|
|
1133
|
+
* Apply a terminal delivery verdict. On Telegram the built-in delivered is
|
|
1134
|
+
* π and failed is π; Discord shows β
/β β both configurable (#1005).
|
|
1135
|
+
*/
|
|
1136
|
+
finishDeliveryStatus(instanceName: string, chatId: string, messageId: string, status: DeliveryStatus, threadId?: string): void;
|
|
1085
1137
|
private queueDeliveryStatusReaction;
|
|
1086
1138
|
private applyDeliveryStatusReaction;
|
|
1087
1139
|
private static FAILOVER_TRIGGER_PCT;
|
|
@@ -1324,7 +1376,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1324
1376
|
* progress indicator (#409) β the channel showed nothing at all during long work,
|
|
1325
1377
|
* and once the agent had replied once there was no sign it was still going.
|
|
1326
1378
|
*/
|
|
1327
|
-
static progressText(elapsedMs: number, activity?: string | null, minElapsedMs?: number): string;
|
|
1379
|
+
static progressText(elapsedMs: number, activity?: string | null, minElapsedMs?: number, prefix?: string): string;
|
|
1328
1380
|
/** Render elapsed time consistently in live and retained progress bubbles. */
|
|
1329
1381
|
private static formatProgressElapsed;
|
|
1330
1382
|
/** Final read-only form of a bubble that contains semantic tool history.
|
|
@@ -1366,7 +1418,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1366
1418
|
*/
|
|
1367
1419
|
private composeBubbleText;
|
|
1368
1420
|
/** Pure composition of header + tool list, exposed for tests. */
|
|
1369
|
-
static bubbleText(elapsedMs: number, activity: string | undefined, minElapsedMs: number, progress: string | undefined): string;
|
|
1421
|
+
static bubbleText(elapsedMs: number, activity: string | undefined, minElapsedMs: number, progress: string | undefined, prefix?: string): string;
|
|
1370
1422
|
/** Recompose and edit the bubble in place; skips when nothing changed. */
|
|
1371
1423
|
private refreshBubble;
|
|
1372
1424
|
/**
|
package/dist/fleet-manager.js
CHANGED
|
@@ -16,17 +16,7 @@ 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
|
+
import { STATUS_EMOJI_KEYS, builtinStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
|
|
30
20
|
import { isProbeableRouteTarget } from "./fleet-context.js";
|
|
31
21
|
import { loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG } from "./config.js";
|
|
32
22
|
import { EventLog } from "./event-log.js";
|
|
@@ -196,19 +186,10 @@ const TOOL_PROGRESS_EDIT_MIN_MS = 4_000;
|
|
|
196
186
|
const PROGRESS_MIN_ELAPSED_MS = 30_000;
|
|
197
187
|
/** How much of a tool summary the progress line will show before eliding. */
|
|
198
188
|
const PROGRESS_ACTIVITY_MAX_CHARS = 48;
|
|
199
|
-
/**
|
|
200
|
-
* Emoji AgEnD itself stamps on messages as the delivery-status ladder
|
|
201
|
-
* (β³ queued, π delivered, β
confirmed, β failed). These are machine
|
|
202
|
-
* indicators, not opinions, so they never enter the reactions queue β from
|
|
203
|
-
* anyone. This exact-emoji filter is the ONLY bot filtering left: bot-to-bot
|
|
204
|
-
* reactions are otherwise delivered on purpose (agents signal each other), and
|
|
205
|
-
* π«‘ passes too β it reads as a deliberate acknowledgement, not plumbing.
|
|
206
|
-
*/
|
|
207
|
-
const DELIVERY_STATUS_EMOJIS = new Set(["π", "β³", "β
", "β"]);
|
|
208
189
|
/**
|
|
209
190
|
* Reactions that are neither delivery plumbing nor meaningful conversational
|
|
210
|
-
* feedback.
|
|
211
|
-
*
|
|
191
|
+
* feedback. Delivery-status emojis are configurable (#1005) and filtered by
|
|
192
|
+
* FleetManager.isOwnStatusReaction, not by a fixed set.
|
|
212
193
|
*/
|
|
213
194
|
const IGNORED_REACTION_EMOJIS = new Set(["π·"]);
|
|
214
195
|
/**
|
|
@@ -4732,7 +4713,7 @@ export class FleetManager {
|
|
|
4732
4713
|
this.logger.debug({ emoji: r.emoji, chatId: r.chatId }, "Reaction in an unrouted channel β ignoring");
|
|
4733
4714
|
return;
|
|
4734
4715
|
}
|
|
4735
|
-
if (
|
|
4716
|
+
if (this.isOwnStatusReaction(r)) {
|
|
4736
4717
|
this.logger.debug({ emoji: r.emoji, user: r.username }, "Ignoring delivery-status emoji as a reaction");
|
|
4737
4718
|
return;
|
|
4738
4719
|
}
|
|
@@ -5273,7 +5254,7 @@ export class FleetManager {
|
|
|
5273
5254
|
// path, so a later delivery status replaces (not stacks onto) this
|
|
5274
5255
|
// acknowledgement.
|
|
5275
5256
|
if (msg.chatId && msg.messageId) {
|
|
5276
|
-
this.reactMessageStatus(generalInstance, msg.chatId, msg.messageId, "
|
|
5257
|
+
this.reactMessageStatus(generalInstance, msg.chatId, msg.messageId, "received", msg.threadId || undefined);
|
|
5277
5258
|
}
|
|
5278
5259
|
this.warnIfRateLimited(generalInstance, msg);
|
|
5279
5260
|
const { text, extraMeta } = await processAttachments(msg, inboundAdapter, this.logger, generalInstance);
|
|
@@ -5365,7 +5346,7 @@ export class FleetManager {
|
|
|
5365
5346
|
// status path, so a later delivery status replaces (not stacks onto) this
|
|
5366
5347
|
// acknowledgement. Same bound-adapter routing as the status path itself.
|
|
5367
5348
|
if (msg.chatId && msg.messageId) {
|
|
5368
|
-
this.reactMessageStatus(instanceName, msg.chatId, msg.messageId, "
|
|
5349
|
+
this.reactMessageStatus(instanceName, msg.chatId, msg.messageId, "received", msg.threadId || undefined);
|
|
5369
5350
|
}
|
|
5370
5351
|
// These may hit Discord API (topic icon, archive) β do after react
|
|
5371
5352
|
if (this.topicArchiver.isArchived(threadId)) {
|
|
@@ -5407,7 +5388,7 @@ export class FleetManager {
|
|
|
5407
5388
|
// Reconciled status path (not a bare add): a retry that later
|
|
5408
5389
|
// succeeds replaces this β with β
instead of leaving both. Chat and
|
|
5409
5390
|
// thread travel separately so Telegram addresses the supergroup.
|
|
5410
|
-
this.finishDeliveryStatus(instanceName, msg.chatId, msg.messageId, "
|
|
5391
|
+
this.finishDeliveryStatus(instanceName, msg.chatId, msg.messageId, "failed", msg.threadId || undefined);
|
|
5411
5392
|
}
|
|
5412
5393
|
return;
|
|
5413
5394
|
}
|
|
@@ -6853,7 +6834,129 @@ export class FleetManager {
|
|
|
6853
6834
|
deliveryStatusChains = new Map();
|
|
6854
6835
|
/** #725: in-flight CLI env probes, keyed by backend name. Coalesces concurrent /model requests. */
|
|
6855
6836
|
pendingCliEnvProbes = new Map();
|
|
6856
|
-
|
|
6837
|
+
/**
|
|
6838
|
+
* The status emojis for an instance (#1005): its own `status_emojis`, then
|
|
6839
|
+
* its channel's `options.status_emojis`, then the platform built-in. An
|
|
6840
|
+
* unusable value warns once and falls through, never failing the reaction.
|
|
6841
|
+
* `adapterId` pins the channel for paths that already know the bot (classic).
|
|
6842
|
+
*/
|
|
6843
|
+
resolveStatusEmojisFor(instanceName, adapterId, adapter) {
|
|
6844
|
+
const worldId = adapterId ?? this.getInstanceAdapterId(instanceName);
|
|
6845
|
+
const channel = (worldId ? this.worlds.get(worldId)?.channelConfig : undefined) ?? this.getChannelConfig(worldId ?? undefined);
|
|
6846
|
+
// The adapter that will react decides the vocabulary: a Telegram bot gets
|
|
6847
|
+
// Telegram's reaction set even if the channel lookup fell back elsewhere.
|
|
6848
|
+
const reacting = adapter ?? (worldId ? this.worlds.get(worldId)?.adapter : undefined) ?? this.adapter;
|
|
6849
|
+
const platform = reacting instanceof TelegramAdapter ? "telegram" : channel?.type;
|
|
6850
|
+
const resolved = resolveStatusEmojis({
|
|
6851
|
+
platform,
|
|
6852
|
+
platformConfig: channel?.options?.status_emojis,
|
|
6853
|
+
instanceConfig: this.fleetConfig?.instances[instanceName]?.status_emojis,
|
|
6854
|
+
onInvalid: (source, key, value, problem) => {
|
|
6855
|
+
const at = source === "instance" ? `instances.${instanceName}` : `channel ${channel?.id ?? platform ?? "?"}`;
|
|
6856
|
+
const once = `${at}:${key}:${String(value)}`;
|
|
6857
|
+
if (this.warnedStatusEmojis.has(once))
|
|
6858
|
+
return;
|
|
6859
|
+
this.warnedStatusEmojis.add(once);
|
|
6860
|
+
this.logger.warn({ at, key, value, problem }, `Ignoring status_emojis.${key}: ${problem} β using the default`);
|
|
6861
|
+
},
|
|
6862
|
+
});
|
|
6863
|
+
return { ...resolved, platform };
|
|
6864
|
+
}
|
|
6865
|
+
warnedStatusEmojis = new Set();
|
|
6866
|
+
/**
|
|
6867
|
+
* A Discord connection's server emojis for the Settings picker (#1005).
|
|
6868
|
+
* Cached per connection β a server's emoji set changes rarely β and one
|
|
6869
|
+
* fetch at a time; `refresh` forces a new one. Uses the running adapter, so
|
|
6870
|
+
* the bot token never leaves the server.
|
|
6871
|
+
*/
|
|
6872
|
+
async listGuildEmojis(channelId, refresh = false) {
|
|
6873
|
+
const world = this.worlds.get(channelId);
|
|
6874
|
+
if (!world)
|
|
6875
|
+
return { ok: false, error: `connection "${channelId}" is not running` };
|
|
6876
|
+
if (world.type !== "discord")
|
|
6877
|
+
return { ok: false, error: "only Discord has server custom emoji" };
|
|
6878
|
+
const adapter = world.adapter;
|
|
6879
|
+
if (!adapter.listGuildEmojis)
|
|
6880
|
+
return { ok: false, error: "this adapter cannot list server emojis" };
|
|
6881
|
+
const cached = this.guildEmojiCache.get(channelId);
|
|
6882
|
+
if (!refresh && cached && Date.now() - cached.fetched_at < FleetManager.GUILD_EMOJI_TTL_MS)
|
|
6883
|
+
return { ok: true, ...cached };
|
|
6884
|
+
let pending = this.guildEmojiFetches.get(channelId);
|
|
6885
|
+
if (!pending) {
|
|
6886
|
+
pending = adapter.listGuildEmojis().then(emojis => {
|
|
6887
|
+
const entry = { fetched_at: Date.now(), emojis };
|
|
6888
|
+
this.guildEmojiCache.set(channelId, entry);
|
|
6889
|
+
return entry;
|
|
6890
|
+
}).finally(() => this.guildEmojiFetches.delete(channelId));
|
|
6891
|
+
this.guildEmojiFetches.set(channelId, pending);
|
|
6892
|
+
}
|
|
6893
|
+
try {
|
|
6894
|
+
return { ok: true, ...(await pending) };
|
|
6895
|
+
}
|
|
6896
|
+
catch (e) {
|
|
6897
|
+
return { ok: false, error: `Discord refused the emoji list: ${e.message}` };
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
static GUILD_EMOJI_TTL_MS = 10 * 60_000;
|
|
6901
|
+
guildEmojiCache = new Map();
|
|
6902
|
+
guildEmojiFetches = new Map();
|
|
6903
|
+
/** What the instructions tell `instanceName` not to react with (its own status set). */
|
|
6904
|
+
statusEmojiAvoidList(instanceName) {
|
|
6905
|
+
return statusAvoidList(this.resolveStatusEmojisFor(instanceName));
|
|
6906
|
+
}
|
|
6907
|
+
/** The progress bubble's leading emoji, in the form the channel renders. */
|
|
6908
|
+
progressPrefixFor(instanceName) {
|
|
6909
|
+
const r = this.resolveStatusEmojisFor(instanceName);
|
|
6910
|
+
const builtin = builtinStatusEmojis(r.platform).progress_prefix;
|
|
6911
|
+
return r.progress_prefix === builtin ? undefined : textForm(r.platform, r.progress_prefix);
|
|
6912
|
+
}
|
|
6913
|
+
/**
|
|
6914
|
+
* Is this inbound reaction one of the fleet's own delivery-status stamps?
|
|
6915
|
+
* Those are machine indicators and must not reach any instance as a user
|
|
6916
|
+
* reaction (#1005 point 3). When every bot's user id is known, the check
|
|
6917
|
+
* keys on WHO reacted: a fleet bot's reaction is a status stamp only if it
|
|
6918
|
+
* is in that bot's instances' status set (other bot reactions are agent
|
|
6919
|
+
* signals and pass), and a human's reaction always passes β whatever emoji
|
|
6920
|
+
* a persona picked. Without every id, fall back to the emoji alone.
|
|
6921
|
+
*/
|
|
6922
|
+
isOwnStatusReaction(r) {
|
|
6923
|
+
const key = reactionMatchKey(r.emoji, r.emojiId);
|
|
6924
|
+
const worlds = [...this.worlds.values()];
|
|
6925
|
+
const names = new Set([
|
|
6926
|
+
...Object.keys(this.fleetConfig?.instances ?? {}),
|
|
6927
|
+
...(this.classicChannels?.getAll?.() ?? []).map(c => c.instanceName),
|
|
6928
|
+
...this.instanceWorldBinding.keys(),
|
|
6929
|
+
]);
|
|
6930
|
+
if (worlds.length > 0 && worlds.every(w => !!w.botUserId)) {
|
|
6931
|
+
const reactorWorlds = new Set(worlds.filter(w => w.botUserId === r.userId).map(w => w.id));
|
|
6932
|
+
if (reactorWorlds.size === 0)
|
|
6933
|
+
return false; // a human, or a bot outside this fleet
|
|
6934
|
+
return [...names]
|
|
6935
|
+
.filter(n => reactorWorlds.has(this.getInstanceAdapterId(n) ?? ""))
|
|
6936
|
+
.some(n => statusMatchKeys(this.resolveStatusEmojisFor(n)).includes(key));
|
|
6937
|
+
}
|
|
6938
|
+
// Fallback: the built-in ladder (as before #1005) plus every value an
|
|
6939
|
+
// operator configured. Not the Telegram built-ins β π is AgEnD's failed
|
|
6940
|
+
// stamp there, but also far too common a human reaction to swallow blind.
|
|
6941
|
+
const keys = new Set(statusMatchKeys(builtinStatusEmojis(undefined)));
|
|
6942
|
+
for (const name of names) {
|
|
6943
|
+
const resolved = this.resolveStatusEmojisFor(name);
|
|
6944
|
+
const builtin = builtinStatusEmojis(resolved.platform);
|
|
6945
|
+
for (const k of STATUS_EMOJI_KEYS)
|
|
6946
|
+
if (resolved[k] !== builtin[k])
|
|
6947
|
+
keys.add(statusMatchKey(resolved[k]));
|
|
6948
|
+
}
|
|
6949
|
+
return keys.has(key);
|
|
6950
|
+
}
|
|
6951
|
+
/**
|
|
6952
|
+
* The "received" reaction for paths that react with the inbound message's
|
|
6953
|
+
* own adapter rather than through reactMessageStatus (classic bots).
|
|
6954
|
+
*/
|
|
6955
|
+
receivedReactionFor(instanceName, adapter, adapterId) {
|
|
6956
|
+
const resolved = this.resolveStatusEmojisFor(instanceName, adapterId, adapter);
|
|
6957
|
+
return reactionForm(resolved.platform, resolved.received);
|
|
6958
|
+
}
|
|
6959
|
+
reactMessageStatus(instanceName, chatId, messageId, status, threadId) {
|
|
6857
6960
|
// React via the adapter BOUND to this instance β NOT the first discord world.
|
|
6858
6961
|
// Otherwise, in a same-channel/same-guild multi-bot setup, the inbound π
|
|
6859
6962
|
// (bound bot) and the delivery/confirm reactions (some other bot) come from
|
|
@@ -6861,40 +6964,41 @@ export class FleetManager {
|
|
|
6861
6964
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
6862
6965
|
if (!adapter)
|
|
6863
6966
|
return;
|
|
6864
|
-
const
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
//
|
|
6868
|
-
|
|
6869
|
-
if (!statusEmoji)
|
|
6967
|
+
const resolved = this.resolveStatusEmojisFor(instanceName, undefined, adapter);
|
|
6968
|
+
const configured = resolved[status];
|
|
6969
|
+
// Unknown statuses stay a no-op. Every resolved Telegram value has been
|
|
6970
|
+
// checked against the ReactionTypeEmoji set (status-emojis.ts).
|
|
6971
|
+
if (!configured)
|
|
6870
6972
|
return;
|
|
6973
|
+
const statusEmoji = reactionForm(resolved.platform, configured);
|
|
6871
6974
|
// Bot-scoped: sibling bots reacting on the same message must not clear
|
|
6872
6975
|
// each other's state β every removal below targets this adapter's own
|
|
6873
6976
|
// reactions (@me on Discord, the bot's list on Telegram).
|
|
6874
6977
|
const adapterId = typeof adapter.id === "string"
|
|
6875
6978
|
? adapter.id : "?";
|
|
6876
6979
|
const key = `${adapterId}:${chatId}:${threadId ?? ""}:${messageId}`;
|
|
6877
|
-
this.queueDeliveryStatusReaction(adapter, key, chatId, messageId, statusEmoji, threadId);
|
|
6980
|
+
this.queueDeliveryStatusReaction(adapter, key, chatId, messageId, statusEmoji, threadId, status);
|
|
6878
6981
|
}
|
|
6879
6982
|
/**
|
|
6880
|
-
* Apply a terminal delivery verdict. Telegram
|
|
6881
|
-
*
|
|
6983
|
+
* Apply a terminal delivery verdict. On Telegram the built-in delivered is
|
|
6984
|
+
* π and failed is π; Discord shows β
/β β both configurable (#1005).
|
|
6882
6985
|
*/
|
|
6883
|
-
finishDeliveryStatus(instanceName, chatId, messageId,
|
|
6884
|
-
this.reactMessageStatus(instanceName, chatId, messageId,
|
|
6986
|
+
finishDeliveryStatus(instanceName, chatId, messageId, status, threadId) {
|
|
6987
|
+
this.reactMessageStatus(instanceName, chatId, messageId, status, threadId);
|
|
6885
6988
|
}
|
|
6886
|
-
queueDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId) {
|
|
6989
|
+
queueDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId, status) {
|
|
6887
6990
|
const prev = this.deliveryStatusChains.get(key) ?? Promise.resolve();
|
|
6888
|
-
const run = prev.then(() => this.applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId));
|
|
6991
|
+
const run = prev.then(() => this.applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId, status));
|
|
6889
6992
|
this.deliveryStatusChains.set(key, run);
|
|
6890
6993
|
void run.then(() => { if (this.deliveryStatusChains.get(key) === run)
|
|
6891
6994
|
this.deliveryStatusChains.delete(key); }, () => { if (this.deliveryStatusChains.get(key) === run)
|
|
6892
6995
|
this.deliveryStatusChains.delete(key); });
|
|
6893
6996
|
}
|
|
6894
|
-
async applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId) {
|
|
6997
|
+
async applyDeliveryStatusReaction(adapter, key, chatId, messageId, emoji, threadId, status) {
|
|
6895
6998
|
try {
|
|
6896
6999
|
const target = adapter instanceof TelegramAdapter ? chatId : (threadId ?? chatId);
|
|
6897
|
-
const
|
|
7000
|
+
const last = this.lastStatusEmoji.get(key);
|
|
7001
|
+
const prev = last?.emoji;
|
|
6898
7002
|
if (emoji == null) {
|
|
6899
7003
|
if (prev && adapter.unreact) {
|
|
6900
7004
|
await adapter.unreact(target, messageId, prev, threadId);
|
|
@@ -6909,15 +7013,16 @@ export class FleetManager {
|
|
|
6909
7013
|
return;
|
|
6910
7014
|
// Thread-aware adapters (Discord) react where the thread is; Telegram
|
|
6911
7015
|
// addresses the supergroup chat and ignores the thread part.
|
|
6912
|
-
// #972: add-only by default β only remove
|
|
6913
|
-
// state. π/β³/β
stacking together is harmless; leaving β
|
|
6914
|
-
// after a recovery would be misleading.
|
|
6915
|
-
|
|
7016
|
+
// #972: add-only by default β only remove the failed emoji when leaving
|
|
7017
|
+
// the failed state. π/β³/β
stacking together is harmless; leaving β
|
|
7018
|
+
// visible after a recovery would be misleading. `prev` is the exact
|
|
7019
|
+
// form it was added with, so a custom emoji comes off too (#1005).
|
|
7020
|
+
if (last?.status === "failed" && adapter.unreact && !(adapter instanceof TelegramAdapter)) {
|
|
6916
7021
|
// Best effort: a failed removal must not block the new status.
|
|
6917
|
-
await adapter.unreact(target, messageId,
|
|
7022
|
+
await adapter.unreact(target, messageId, last.emoji, threadId).catch(e => this.logger.debug({ err: e.message }, "Delivery status reaction removal failed"));
|
|
6918
7023
|
}
|
|
6919
7024
|
await adapter.react(target, messageId, emoji, threadId);
|
|
6920
|
-
this.lastStatusEmoji.set(key, emoji);
|
|
7025
|
+
this.lastStatusEmoji.set(key, { emoji, status });
|
|
6921
7026
|
}
|
|
6922
7027
|
catch (e) {
|
|
6923
7028
|
this.logger.debug({ err: e.message }, "Message status react failed");
|
|
@@ -8124,11 +8229,12 @@ export class FleetManager {
|
|
|
8124
8229
|
return;
|
|
8125
8230
|
}
|
|
8126
8231
|
const publication = this.beginCancelButtonPublication(instanceName, correlationId);
|
|
8232
|
+
const initialProgressText = FleetManager.progressText(0, null, 1, this.progressPrefixFor(instanceName));
|
|
8127
8233
|
try {
|
|
8128
8234
|
const sent = await adapter.notifyAlert(chatId, {
|
|
8129
8235
|
type: "cancel",
|
|
8130
8236
|
instanceName,
|
|
8131
|
-
message:
|
|
8237
|
+
message: initialProgressText,
|
|
8132
8238
|
choices: [{ id: `cancel:${instanceName}`, label: t("cancel.button") }],
|
|
8133
8239
|
}, threadId ? { threadId } : undefined);
|
|
8134
8240
|
publication.inFlight = false;
|
|
@@ -8147,7 +8253,7 @@ export class FleetManager {
|
|
|
8147
8253
|
// Matches the text notifyAlert just posted, so the first 60s tick does
|
|
8148
8254
|
// not re-edit identical text β which put a "(edited)" mark on Discord
|
|
8149
8255
|
// with nothing visibly changed.
|
|
8150
|
-
lastProgressText:
|
|
8256
|
+
lastProgressText: initialProgressText,
|
|
8151
8257
|
// A reply can re-post the same in-flight bubble below the reply. Carry
|
|
8152
8258
|
// its current list into that replacement; fresh inbound work must start
|
|
8153
8259
|
// empty even if the daemon's reset broadcast is still in flight.
|
|
@@ -8234,14 +8340,16 @@ export class FleetManager {
|
|
|
8234
8340
|
* progress indicator (#409) β the channel showed nothing at all during long work,
|
|
8235
8341
|
* and once the agent had replied once there was no sign it was still going.
|
|
8236
8342
|
*/
|
|
8237
|
-
static progressText(elapsedMs, activity, minElapsedMs = PROGRESS_MIN_ELAPSED_MS) {
|
|
8343
|
+
static progressText(elapsedMs, activity, minElapsedMs = PROGRESS_MIN_ELAPSED_MS, prefix) {
|
|
8344
|
+
// A configured progress_prefix (#1005) leads both phases; the built-in
|
|
8345
|
+
// switches π β β³ once the elapsed time shows.
|
|
8238
8346
|
if (elapsedMs < minElapsedMs)
|
|
8239
|
-
return "π
|
|
8347
|
+
return `${prefix ?? "π"} θηδΈβ¦`;
|
|
8240
8348
|
const elapsed = FleetManager.formatProgressElapsed(elapsedMs);
|
|
8241
8349
|
const detail = FleetManager.sanitizeActivity(activity);
|
|
8242
8350
|
return detail
|
|
8243
|
-
?
|
|
8244
|
-
:
|
|
8351
|
+
? `${prefix ?? "β³"} θηδΈβ¦ (ε·²ι²θ‘ ${elapsed} Β· ${detail})`
|
|
8352
|
+
: `${prefix ?? "β³"} θηδΈβ¦ (ε·²ι²θ‘ ${elapsed})`;
|
|
8245
8353
|
}
|
|
8246
8354
|
/** Render elapsed time consistently in live and retained progress bubbles. */
|
|
8247
8355
|
static formatProgressElapsed(elapsedMs) {
|
|
@@ -8337,13 +8445,13 @@ export class FleetManager {
|
|
|
8337
8445
|
* wiped by the next elapsed tick (#528 trap 2).
|
|
8338
8446
|
*/
|
|
8339
8447
|
composeBubbleText(entry) {
|
|
8340
|
-
return FleetManager.bubbleText(Date.now() - (entry.startedAt ?? Date.now()), this.instanceActivity.get(entry.instanceName), this.progressMinElapsedMs(), entry.toolProgress);
|
|
8448
|
+
return FleetManager.bubbleText(Date.now() - (entry.startedAt ?? Date.now()), this.instanceActivity.get(entry.instanceName), this.progressMinElapsedMs(), entry.toolProgress, this.progressPrefixFor(entry.instanceName));
|
|
8341
8449
|
}
|
|
8342
8450
|
/** Pure composition of header + tool list, exposed for tests. */
|
|
8343
|
-
static bubbleText(elapsedMs, activity, minElapsedMs, progress) {
|
|
8451
|
+
static bubbleText(elapsedMs, activity, minElapsedMs, progress, prefix) {
|
|
8344
8452
|
const header = FleetManager.progressText(elapsedMs,
|
|
8345
8453
|
// The single-line activity detail is redundant once a tool list exists.
|
|
8346
|
-
progress ? undefined : activity, minElapsedMs);
|
|
8454
|
+
progress ? undefined : activity, minElapsedMs, prefix);
|
|
8347
8455
|
return progress ? `${header}\n${progress}` : header;
|
|
8348
8456
|
}
|
|
8349
8457
|
/** Recompose and edit the bubble in place; skips when nothing changed. */
|
|
@@ -9963,7 +10071,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
9963
10071
|
const classicAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
9964
10072
|
const collabReactChatId = msg.threadId ?? msg.chatId;
|
|
9965
10073
|
if (classicAdapter && collabReactChatId && msg.messageId) {
|
|
9966
|
-
classicAdapter.react(collabReactChatId, msg.messageId,
|
|
10074
|
+
classicAdapter.react(collabReactChatId, msg.messageId, this.receivedReactionFor(instanceName, classicAdapter, msg.adapterId))
|
|
9967
10075
|
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
9968
10076
|
}
|
|
9969
10077
|
// Block /raw bypass β check the mention-stripped text so the self-marker
|
|
@@ -10068,7 +10176,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
10068
10176
|
}
|
|
10069
10177
|
if (msg.chatId && msg.messageId) {
|
|
10070
10178
|
const reactChatId = msg.threadId ?? msg.chatId;
|
|
10071
|
-
classicMsgAdapter.react(reactChatId, msg.messageId,
|
|
10179
|
+
classicMsgAdapter.react(reactChatId, msg.messageId, this.receivedReactionFor(instanceName, classicMsgAdapter, msg.adapterId))
|
|
10072
10180
|
.catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
|
|
10073
10181
|
if (saved) {
|
|
10074
10182
|
const savedEmoji = msg.source === "telegram"
|