@songsid/agend 2.2.0-beta.10 → 2.2.0-beta.12
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/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +4 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +15 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +5 -0
- package/dist/backend/antigravity.js +27 -13
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +36 -0
- package/dist/backend/effort-metadata.js +83 -13
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +29 -1
- package/dist/backend/launch-effort.js +53 -6
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +12 -0
- package/dist/channel/adapters/discord.js +39 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +18 -0
- package/dist/channel/adapters/telegram.js +24 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +16 -0
- package/dist/cli.js +34 -11
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +9 -0
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -1
- package/dist/delivery-outbox.js +6 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +59 -3
- package/dist/fleet-manager.js +157 -22
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +26 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +21 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +12 -0
- package/dist/outbound-schemas.js +12 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +3 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +86 -5
- package/dist/reply-buttons-controller.js +271 -7
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +9 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/ui/chat-render.js +19 -2
- package/dist/ui/chat-store.js +35 -1
- package/dist/ui/chat-strings.js +2 -0
- package/dist/ui/chat-thread.js +42 -0
- package/dist/ui/panel-chat.js +2 -0
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-settings.js +48 -1
- package/dist/ui/settings-strings.js +10 -0
- package/dist/ui/shared/app-i18n.js +1 -1
- package/dist/ui/shared/app-stream.js +4 -1
- package/dist/ui/shared/app.css +28 -5
- package/dist/ui/shared/header-tools.js +12 -4
- package/dist/ui/shared/instance-switch.js +5 -2
- package/dist/ui/shared/view-strings.js +2 -2
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-launch.d.ts +46 -1
- package/dist/update-launch.js +146 -3
- package/dist/update-launch.js.map +1 -1
- package/dist/web-api.d.ts +40 -2
- package/dist/web-api.js +130 -14
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +12 -1
- package/dist/web-chat-history.js +29 -7
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-prefs.d.ts +18 -0
- package/dist/web-prefs.js +36 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +10 -0
- package/dist/web-session.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -20,7 +20,7 @@ import { withinBudget } from "./monotonic-budget.js";
|
|
|
20
20
|
import { measureSyncWork } from "./sync-work-attribution.js";
|
|
21
21
|
import { RuntimeCpuProfiler, ProfileBusyError, profileDuration } from "./runtime-cpu-profile.js";
|
|
22
22
|
import { ProfileControlServer } from "./profile-control.js";
|
|
23
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync, accessSync, realpathSync, constants as fsConstants } from "node:fs";
|
|
23
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync, accessSync, realpathSync, constants as fsConstants, closeSync } from "node:fs";
|
|
24
24
|
import { atomicWriteFileSync } from "./atomic-write.js";
|
|
25
25
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
26
26
|
import { spawnSync } from "node:child_process";
|
|
@@ -141,8 +141,10 @@ import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId } fr
|
|
|
141
141
|
import { WebChatDiskStore } from "./web-chat-store.js";
|
|
142
142
|
import { SeenUsers, resolveMentions } from "./web-mentions.js";
|
|
143
143
|
import { WebPrefsStore } from "./web-prefs.js";
|
|
144
|
+
import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPersonKey } from "./web-profile.js";
|
|
144
145
|
import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
|
|
145
146
|
import { ReplyButtonsController } from "./reply-buttons-controller.js";
|
|
147
|
+
import { parsePoll } from "./reply-polls.js";
|
|
146
148
|
import { publicAttachment, sweepOrphanedUploads, WebFileLedger } from "./web-upload.js";
|
|
147
149
|
import { REPLY_FILES_MAX_BYTES, snapshotReplyFile, sweepReplySnapshots, webFilesDir } from "./web-reply-files.js";
|
|
148
150
|
import { handleViewRequest, isViewPath, profileIdentities, resolveInstanceIdentity } from "./view-api.js";
|
|
@@ -162,7 +164,7 @@ import { canonicalCommand, commandSpec, decideCommand, ruleFor } from "./command
|
|
|
162
164
|
import { runVisibilityCommand } from "./cross-instance-notice.js";
|
|
163
165
|
import { installedChannel, isPrereleaseVersion, updateNoticeKey } from "./update-check.js";
|
|
164
166
|
import { resolveInstalledAgend } from "./update-dispatch.js";
|
|
165
|
-
import { resolveUpdateLaunch, watchUpdateLaunch } from "./update-launch.js";
|
|
167
|
+
import { resolveUpdateLaunch, openUpdateLog, updateLogPath, watchUpdateLaunch } from "./update-launch.js";
|
|
166
168
|
import { admitProfileLogin, LoginController, LOGIN_TOKEN_RESEND_PREFIX, POST_LOGIN_RECOVERY_DEADLINE_MS } from "./login-controller.js";
|
|
167
169
|
import { runBeforeDeadline } from "./deadline.js";
|
|
168
170
|
import { LoginWindowLock } from "./login-window-lock.js";
|
|
@@ -380,6 +382,7 @@ const WEB_MIRRORED_PROMPT_PREFIXES = new Set([
|
|
|
380
382
|
const WEB_ONLY_REPLY_SINK = {
|
|
381
383
|
type: "web",
|
|
382
384
|
supportsReplyButtons: true, // #1266: the web chat shows a reply's buttons itself
|
|
385
|
+
supportsPolls: true, // #1823: …and a poll (its pages are told; there is nothing to edit)
|
|
383
386
|
sendText: async () => ({ chatId: "web", messageId: newWebMessageId() }),
|
|
384
387
|
sendFile: async () => ({ chatId: "web", messageId: newWebMessageId() }),
|
|
385
388
|
};
|
|
@@ -807,6 +810,78 @@ export class FleetManager {
|
|
|
807
810
|
seenUsers = new SeenUsers();
|
|
808
811
|
webPrefsStore;
|
|
809
812
|
/** #1665: the web preferences kept on the host (<home>/web-prefs.json), read on first use. */
|
|
813
|
+
/**
|
|
814
|
+
* #1827: a web person as agents and chats see them. Signed in as an account (a sign-in code DMed to it): that
|
|
815
|
+
* account's user id — still `via web`, and only for recognition — and the name they chose, else their account's name
|
|
816
|
+
* at sign-in (made safe). Otherwise (an `agend web` session): "web-user", as before, under a name the operator may set.
|
|
817
|
+
* Names are checked again now: one an instance or bot has taken since reads as the next choice down.
|
|
818
|
+
*/
|
|
819
|
+
webPerson(owner) {
|
|
820
|
+
const key = webPersonKey(owner);
|
|
821
|
+
let profile = null;
|
|
822
|
+
try {
|
|
823
|
+
profile = this.webPrefs.profile(key);
|
|
824
|
+
}
|
|
825
|
+
catch (err) {
|
|
826
|
+
this.logger.warn({ err: err.message }, "Web profile unavailable");
|
|
827
|
+
}
|
|
828
|
+
const reserved = (n) => this.webNameReserved(n);
|
|
829
|
+
const own = profile?.name !== undefined ? webNameInUse(profile.name, reserved) : WEB_NAME_FALLBACK;
|
|
830
|
+
const chosen = own !== WEB_NAME_FALLBACK;
|
|
831
|
+
const platformName = owner ? safeWebName(profile?.platformName, reserved) : undefined;
|
|
832
|
+
return {
|
|
833
|
+
key, bound: !!owner,
|
|
834
|
+
userId: owner ? owner.userId : WEB_NAME_FALLBACK,
|
|
835
|
+
// A poll ballot is the account's — the same key its votes on the platform have — or the operator's.
|
|
836
|
+
voterKey: owner ? `${owner.adapterId}:${owner.userId}` : "web:operator",
|
|
837
|
+
name: chosen ? own : platformName ?? WEB_NAME_FALLBACK,
|
|
838
|
+
set: chosen,
|
|
839
|
+
...(platformName !== undefined ? { platformName } : {}),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
/** #1827: set (null: clear — back to their account's name) a web person's own name; validated in full. */
|
|
843
|
+
setWebPersonName(owner, raw) {
|
|
844
|
+
let name = null;
|
|
845
|
+
if (raw !== null) {
|
|
846
|
+
const v = validateWebName(raw, (n) => this.webNameReserved(n));
|
|
847
|
+
if (!v.ok)
|
|
848
|
+
return { ok: false, status: 400, error: v.error };
|
|
849
|
+
name = v.name;
|
|
850
|
+
}
|
|
851
|
+
const w = this.webPrefs.setProfileName(webPersonKey(owner), name);
|
|
852
|
+
return w.ok ? { ok: true, person: this.webPerson(owner) } : { ok: false, status: w.status, error: w.error };
|
|
853
|
+
}
|
|
854
|
+
/** #1827 (auth-api): a sign-in as this account — its display name then is the web name it starts with. */
|
|
855
|
+
noteWebSignIn(account) {
|
|
856
|
+
if (account.username)
|
|
857
|
+
this.webPrefs.setPlatformName(webPersonKey(account), account.username);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* #1827: a name that belongs to something here — an instance (or ClassicBot room), its display name, a bot. On the web
|
|
861
|
+
* send path: it never throws (a lookup that fails reserves nothing; the name was checked in full when it was set).
|
|
862
|
+
*/
|
|
863
|
+
webNameReserved(lowered) {
|
|
864
|
+
try {
|
|
865
|
+
return this.webNameTaken(lowered);
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
webNameTaken(lowered) {
|
|
872
|
+
const low = (s) => (typeof s === "string" ? s.normalize("NFC").toLocaleLowerCase("en-US") : "");
|
|
873
|
+
for (const [name, cfg] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
874
|
+
if (low(name) === lowered || low(cfg.display_name) === lowered)
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
for (const ch of this.classicChannels?.getAll() ?? [])
|
|
878
|
+
if (low(ch.instanceName) === lowered || low(ch.displayName) === lowered)
|
|
879
|
+
return true;
|
|
880
|
+
for (const w of this.worlds.values())
|
|
881
|
+
if (w.botUsername && low(w.botUsername) === lowered)
|
|
882
|
+
return true;
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
810
885
|
get webPrefs() {
|
|
811
886
|
// #1664: a favorite must be an instance of this fleet now — fleet.yaml's (own keys) or a ClassicBot room's.
|
|
812
887
|
return (this.webPrefsStore ??= new WebPrefsStore(this.dataDir, {
|
|
@@ -1155,6 +1230,8 @@ export class FleetManager {
|
|
|
1155
1230
|
mayClick: (set, userId) => this.mayAnswerReplyButtons(set.instance, set.adapterId, userId),
|
|
1156
1231
|
deliver: (set, button, by) => this.deliverReplyButtonChoice(set, button, by),
|
|
1157
1232
|
publish: (instance, view) => this.emitSseEvent("reply_buttons", { instance, buttons: view }),
|
|
1233
|
+
admitResults: (set, key, text) => this.admitPollResults(set, key, text),
|
|
1234
|
+
monotonic: () => performance.now(),
|
|
1158
1235
|
logger: this.logger,
|
|
1159
1236
|
setTimer: (fn, ms) => { const h = setTimeout(fn, ms); h.unref?.(); return h; },
|
|
1160
1237
|
clearTimer: (h) => clearTimeout(h),
|
|
@@ -1163,6 +1240,21 @@ export class FleetManager {
|
|
|
1163
1240
|
return this.replyButtonsCtl;
|
|
1164
1241
|
}
|
|
1165
1242
|
replyButtonsStore = null;
|
|
1243
|
+
/**
|
|
1244
|
+
* #1823: a closed poll's results go to its instance through the delivery outbox, as a system notice keyed by the
|
|
1245
|
+
* poll: durable (held until the instance can take it) and admitted once, however often this is asked. Addressed to
|
|
1246
|
+
* the poll's chat, so the instance's answer (the right option, the scores) goes there.
|
|
1247
|
+
*/
|
|
1248
|
+
admitPollResults(set, key, text) {
|
|
1249
|
+
this.ensureDeliveryOutbox();
|
|
1250
|
+
const outbox = this.deliveryOutbox;
|
|
1251
|
+
if (!outbox)
|
|
1252
|
+
return false;
|
|
1253
|
+
outbox.admitSystemNotice(set.instance, key, text, new Date(), {
|
|
1254
|
+
chatId: set.chatId, threadId: set.threadId || undefined, adapterId: set.adapterId === "web" ? undefined : set.adapterId,
|
|
1255
|
+
});
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1166
1258
|
/**
|
|
1167
1259
|
* #1266: who may answer a reply's buttons on a platform — whoever may message that instance there: in a ClassicBot
|
|
1168
1260
|
* room anyone there (as a typed message), elsewhere someone the sending connection lets speak (#754/#1148), never a
|
|
@@ -2238,10 +2330,15 @@ export class FleetManager {
|
|
|
2238
2330
|
this.failUpdateProgress(t("not_authorized"));
|
|
2239
2331
|
return;
|
|
2240
2332
|
}
|
|
2333
|
+
const logPath = updateLogPath(this.dataDir);
|
|
2334
|
+
const ts = new Date().toISOString();
|
|
2335
|
+
const logFd = openUpdateLog(logPath, `--- /update ${ts} ---`);
|
|
2241
2336
|
const child = spawn(launch.command, launch.args, {
|
|
2242
|
-
detached: true, stdio: "ignore", env: withOrigin(origin),
|
|
2337
|
+
detached: true, stdio: ["ignore", logFd ? logFd.fd : "ignore", logFd ? logFd.fd : "ignore"], env: withOrigin(origin),
|
|
2243
2338
|
});
|
|
2244
|
-
|
|
2339
|
+
if (logFd)
|
|
2340
|
+
closeSync(logFd.fd); // parent closes its copy; the child's fd stays open
|
|
2341
|
+
watchUpdateLaunch(child, message => this.failUpdateProgress(message), logFd ? logPath : undefined, logFd?.offset, installed.agend);
|
|
2245
2342
|
child.unref();
|
|
2246
2343
|
}
|
|
2247
2344
|
async handleProfileSlash(data, adapterId) {
|
|
@@ -3123,7 +3220,8 @@ export class FleetManager {
|
|
|
3123
3220
|
* or a rebound adapter is never posted through a stale route. A copy
|
|
3124
3221
|
* already in flight may still land late — the contract permits that.
|
|
3125
3222
|
*/
|
|
3126
|
-
|
|
3223
|
+
/** `sender` (#1827): the web person's name, as the room log records them ("web-user" when none). */
|
|
3224
|
+
async sendClassicWebEcho(instance, text, sender = WEB_NAME_FALLBACK) {
|
|
3127
3225
|
const guard = this.webChannelEchoGuards.get(instance);
|
|
3128
3226
|
const handled = new Set();
|
|
3129
3227
|
let posted = 0;
|
|
@@ -3154,7 +3252,7 @@ export class FleetManager {
|
|
|
3154
3252
|
if (!adapter?.sendText)
|
|
3155
3253
|
continue;
|
|
3156
3254
|
await adapter.sendText(entry.channelId, text, { format: "text", allowedMentions: { parse: [] } });
|
|
3157
|
-
ClassicChannelManager.logMessage(instance,
|
|
3255
|
+
ClassicChannelManager.logMessage(instance, sender, text, new Date());
|
|
3158
3256
|
posted++;
|
|
3159
3257
|
}
|
|
3160
3258
|
catch (err) {
|
|
@@ -3713,7 +3811,7 @@ export class FleetManager {
|
|
|
3713
3811
|
await data.respond(result.durable ? t("dashboard.revoked", result.count) : t("dashboard.revoked_not_durable", result.count));
|
|
3714
3812
|
}
|
|
3715
3813
|
else {
|
|
3716
|
-
const owner = this.dashboardOwner(data.userId, adapterId, data.channelId);
|
|
3814
|
+
const owner = this.dashboardOwner(data.userId, adapterId, data.channelId, undefined, data.username);
|
|
3717
3815
|
if (!owner) {
|
|
3718
3816
|
await this.sendLocalDashboard(data, adapterId);
|
|
3719
3817
|
return;
|
|
@@ -5035,7 +5133,7 @@ export class FleetManager {
|
|
|
5035
5133
|
&& cfg.topic_id?.toString() === owner.threadId);
|
|
5036
5134
|
}
|
|
5037
5135
|
/** Exact owning General. No primary-adapter or name fallback participates in authorization. */
|
|
5038
|
-
dashboardOwner(userId, adapterId, address, threadId) {
|
|
5136
|
+
dashboardOwner(userId, adapterId, address, threadId, username) {
|
|
5039
5137
|
const world = this.worlds.get(adapterId);
|
|
5040
5138
|
if (!world || !this.hasFleetAdmins(adapterId) || !this.isFleetAdmin(userId, adapterId))
|
|
5041
5139
|
return null;
|
|
@@ -5050,7 +5148,7 @@ export class FleetManager {
|
|
|
5050
5148
|
const matched = dc ? topic === address || (group === address && topic === threadId)
|
|
5051
5149
|
: group === address && ((topic === "1" && (threadId === undefined || threadId === "1")) || topic === threadId);
|
|
5052
5150
|
if (group && matched)
|
|
5053
|
-
return { userId, adapterId, chatId: group, threadId: topic, binding: world.adapter };
|
|
5151
|
+
return { userId, adapterId, chatId: group, threadId: topic, binding: world.adapter, ...(username ? { username } : {}) };
|
|
5054
5152
|
}
|
|
5055
5153
|
return null;
|
|
5056
5154
|
}
|
|
@@ -5059,7 +5157,7 @@ export class FleetManager {
|
|
|
5059
5157
|
* — this bot's configured group and its one General topic. No address to match in a DM, so it must be unambiguous:
|
|
5060
5158
|
* no General, or more than one on this bot and group, and there is no owner (nothing is guessed).
|
|
5061
5159
|
*/
|
|
5062
|
-
dmDashboardOwner(userId, adapterId) {
|
|
5160
|
+
dmDashboardOwner(userId, adapterId, username) {
|
|
5063
5161
|
const world = this.worlds.get(adapterId);
|
|
5064
5162
|
if (!world || !this.hasFleetAdmins(adapterId) || !this.isFleetAdmin(userId, adapterId))
|
|
5065
5163
|
return null;
|
|
@@ -5073,7 +5171,7 @@ export class FleetManager {
|
|
|
5073
5171
|
.map(([, cfg]) => cfg.topic_id.toString());
|
|
5074
5172
|
if (topics.length !== 1)
|
|
5075
5173
|
return null;
|
|
5076
|
-
const owner = { userId, adapterId, chatId: group, threadId: topics[0], binding: world.adapter };
|
|
5174
|
+
const owner = { userId, adapterId, chatId: group, threadId: topics[0], binding: world.adapter, ...(username ? { username } : {}) };
|
|
5077
5175
|
return this.publicOwnerCurrent(owner) ? owner : null;
|
|
5078
5176
|
}
|
|
5079
5177
|
/**
|
|
@@ -5100,7 +5198,7 @@ export class FleetManager {
|
|
|
5100
5198
|
async startWebInDm(msg, adapter) {
|
|
5101
5199
|
if (!adapter)
|
|
5102
5200
|
return;
|
|
5103
|
-
const owner = msg.chatId === msg.userId ? this.dmDashboardOwner(msg.userId, msg.adapterId ?? "") : null;
|
|
5201
|
+
const owner = msg.chatId === msg.userId ? this.dmDashboardOwner(msg.userId, msg.adapterId ?? "", msg.username) : null;
|
|
5104
5202
|
if (!owner) {
|
|
5105
5203
|
await adapter.sendText(msg.chatId, t("dashboard.start_web_ack"));
|
|
5106
5204
|
return;
|
|
@@ -5153,7 +5251,7 @@ export class FleetManager {
|
|
|
5153
5251
|
/** Telegram's typed command enters through the same strict owner and nonce path as Discord. */
|
|
5154
5252
|
async dashboardMenu(msg) {
|
|
5155
5253
|
const id = msg.adapterId;
|
|
5156
|
-
const owner = id ? this.dashboardOwner(msg.userId, id, msg.chatId, msg.threadId) : null;
|
|
5254
|
+
const owner = id ? this.dashboardOwner(msg.userId, id, msg.chatId, msg.threadId, msg.username) : null;
|
|
5157
5255
|
if (!owner) {
|
|
5158
5256
|
const adapter = id ? this.adapters.get(id) : undefined;
|
|
5159
5257
|
await adapter?.sendText(msg.chatId, t("dashboard.public_general"), { threadId: msg.threadId });
|
|
@@ -5186,7 +5284,9 @@ export class FleetManager {
|
|
|
5186
5284
|
return;
|
|
5187
5285
|
}
|
|
5188
5286
|
this.initializeWebSessions();
|
|
5189
|
-
|
|
5287
|
+
// #1827: the code goes privately to this account (a DM, or else this slash command's ephemeral answer): whoever
|
|
5288
|
+
// redeems it is recognised as that account. Recognition only — no owner (this is not an owning General's code).
|
|
5289
|
+
const login = this.webLoginCodes.issue({ epoch: tokenEpoch(token), recipient: { adapterId, userId: data.userId, ...(data.username ? { username: data.username } : {}) } });
|
|
5190
5290
|
const current = () => !this.shuttingDown && this.adapters.get(adapterId) === adapter
|
|
5191
5291
|
&& this.webToken === token && this.hasFleetAdmins(adapterId) && this.isFleetAdmin(data.userId, adapterId);
|
|
5192
5292
|
const privateText = `${text}\n${t("dashboard.code", login.display, Math.round(LOGIN_CODE_TTL_MS / 60_000))}`;
|
|
@@ -6412,6 +6512,7 @@ export class FleetManager {
|
|
|
6412
6512
|
// Health HTTP endpoint
|
|
6413
6513
|
this.startHealthServer(fleet.health_port ?? 19280);
|
|
6414
6514
|
// #1266: buttons that ended while AgEnD was down (expired, or chosen before a restart) are shown as ended now.
|
|
6515
|
+
// #1823: and polls: a deadline that passed meanwhile closes now (results once); an open one's countdown is redrawn.
|
|
6415
6516
|
void this.replyButtons()?.sweep().catch(err => this.logger.warn({ err }, "Reply-button sweep failed"));
|
|
6416
6517
|
// Daily update check — first check after 1 hour, then every 24 hours
|
|
6417
6518
|
this.updateCheckTimer = setTimeout(() => {
|
|
@@ -8524,6 +8625,17 @@ export class FleetManager {
|
|
|
8524
8625
|
respond(null, `reply: ${parsedButtons.error}`);
|
|
8525
8626
|
return;
|
|
8526
8627
|
}
|
|
8628
|
+
// #1823: a poll is checked with its buttons. Where it cannot be shown it is refused, never sent as text: text
|
|
8629
|
+
// options would never come back as results.
|
|
8630
|
+
const parsedPoll = parsePoll(args.poll, parsedButtons && "buttons" in parsedButtons ? parsedButtons.buttons : null, args.text);
|
|
8631
|
+
if (parsedPoll && "error" in parsedPoll) {
|
|
8632
|
+
respond(null, `reply: ${parsedPoll.error}`);
|
|
8633
|
+
return;
|
|
8634
|
+
}
|
|
8635
|
+
if (parsedPoll && (!outAdapter.supportsPolls || !this.replyButtons())) {
|
|
8636
|
+
respond(null, `reply: a poll cannot be shown here${outAdapter.supportsPolls ? " (reply buttons are unavailable until AgEnD restarts)" : " — polls work in Discord and Telegram chats and the web chat"}; send the question without poll`);
|
|
8637
|
+
return;
|
|
8638
|
+
}
|
|
8527
8639
|
// Stickers (#1226) are checked before anything is sent: a refused one is the reply's error, not a gap.
|
|
8528
8640
|
const stickerProblem = await this.replyStickerProblem(outAdapter, args, threadId, contextAdapterId ?? this.getInstanceAdapterId(senderInstanceName ?? instanceName));
|
|
8529
8641
|
if (stickerProblem) {
|
|
@@ -8543,7 +8655,7 @@ export class FleetManager {
|
|
|
8543
8655
|
const buttons = outAdapter.supportsReplyButtons ? this.replyButtons() : null;
|
|
8544
8656
|
if (buttons) {
|
|
8545
8657
|
const outId = outAdapter === WEB_ONLY_REPLY_SINK ? "web" : outAdapter.id ?? contextAdapterId ?? "";
|
|
8546
|
-
replySet = buttons.prepare({ instance: instanceName, adapterId: outId, chatId: String(args.chat_id ?? ""), threadId }, parsedButtons.buttons);
|
|
8658
|
+
replySet = buttons.prepare({ instance: instanceName, adapterId: outId, chatId: String(args.chat_id ?? ""), threadId }, parsedButtons.buttons, parsedPoll && "poll" in parsedPoll ? parsedPoll.poll : null);
|
|
8547
8659
|
}
|
|
8548
8660
|
else {
|
|
8549
8661
|
args.text = `${String(args.text)}\n\n${replyButtonsFallbackText(parsedButtons.buttons)}`;
|
|
@@ -8563,6 +8675,9 @@ export class FleetManager {
|
|
|
8563
8675
|
this.replyButtonsCtl?.discard(replySet.id);
|
|
8564
8676
|
}
|
|
8565
8677
|
}
|
|
8678
|
+
// #1823: the agent learns its poll's id (the results name it).
|
|
8679
|
+
if (replySet?.poll && !error && result && typeof result === "object")
|
|
8680
|
+
result = { ...result, poll_id: replySet.id };
|
|
8566
8681
|
ticket.complete(result, error);
|
|
8567
8682
|
// Return the platform outcome first. Bookkeeping below must never turn
|
|
8568
8683
|
// a confirmed Discord/Telegram POST into a tool error if a secondary
|
|
@@ -8590,7 +8705,7 @@ export class FleetManager {
|
|
|
8590
8705
|
});
|
|
8591
8706
|
}
|
|
8592
8707
|
};
|
|
8593
|
-
if (routeToolCall(outAdapter, tool, args, threadId, respondAndRecord, replySet ? { replyButtons: replySet.callbacks } : {})) {
|
|
8708
|
+
if (routeToolCall(outAdapter, tool, args, threadId, respondAndRecord, replySet ? { replyButtons: replySet.callbacks, ...(replySet.poll ? { replyPoll: replySet.poll } : {}) } : {})) {
|
|
8594
8709
|
return;
|
|
8595
8710
|
}
|
|
8596
8711
|
if (replySet)
|
|
@@ -12120,10 +12235,18 @@ export class FleetManager {
|
|
|
12120
12235
|
* The caller has passed the /ui gate (session, same origin, CSRF). Here: the prompt must be one offered
|
|
12121
12236
|
* on the dashboard, about the instance the page named, and the action one of its own buttons.
|
|
12122
12237
|
*/
|
|
12123
|
-
/** #
|
|
12124
|
-
|
|
12238
|
+
/** #1823: close_poll — `instance` closes one of its own polls before the deadline. */
|
|
12239
|
+
closePollEarly(instance, id) {
|
|
12240
|
+
const buttons = this.replyButtons();
|
|
12241
|
+
return buttons ? buttons.closeEarly(instance, id) : Promise.resolve({ ok: false, error: "polls are unavailable until AgEnD restarts" });
|
|
12242
|
+
}
|
|
12243
|
+
/**
|
|
12244
|
+
* #1266: a click on a reply's button in the web chat (the /ui gate is passed: a session, or the public link). On a
|
|
12245
|
+
* poll (#1823) it is `voter`'s vote: the page's session.
|
|
12246
|
+
*/
|
|
12247
|
+
clickWebReplyButton(instance, id, index, voter) {
|
|
12125
12248
|
const buttons = this.replyButtons();
|
|
12126
|
-
return buttons ? buttons.clickWeb(instance, id, index) : Promise.resolve({ status: 409, error: t("reply_buttons.closed") });
|
|
12249
|
+
return buttons ? buttons.clickWeb(instance, id, index, voter) : Promise.resolve({ status: 409, error: t("reply_buttons.closed") });
|
|
12127
12250
|
}
|
|
12128
12251
|
async clickWebPrompt(instance, nonce, action) {
|
|
12129
12252
|
if (!/^[0-9a-f]{32}$/.test(nonce) || !/^[a-z][a-z-]{0,23}$/.test(action))
|
|
@@ -12485,7 +12608,9 @@ export class FleetManager {
|
|
|
12485
12608
|
const webMeta = (instance) => {
|
|
12486
12609
|
const topicId = this.fleetConfig?.instances[instance]?.topic_id;
|
|
12487
12610
|
return {
|
|
12488
|
-
|
|
12611
|
+
// #1827: a /steer or /btw reaches the agent as the web person who wrote it.
|
|
12612
|
+
chatId: this.getGroupIdForInstance(instance) || "", messageId: newWebMessageId(),
|
|
12613
|
+
username: opts.person?.name ?? WEB_NAME_FALLBACK, userId: opts.person?.userId ?? WEB_NAME_FALLBACK,
|
|
12489
12614
|
threadId: topicId != null ? String(topicId) : undefined, adapterId: this.getAdapterForInstance(instance)?.id ?? "", source: "web", humanOrigin: webHumanOrigin(true),
|
|
12490
12615
|
};
|
|
12491
12616
|
};
|
|
@@ -16195,7 +16320,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
16195
16320
|
/** How this instance's backend applies an effort change. */
|
|
16196
16321
|
effortStrategyFor(instanceName) {
|
|
16197
16322
|
try {
|
|
16198
|
-
const { strategy, levels } =
|
|
16323
|
+
const { strategy, levels } = this.instanceEffortMetadata(instanceName);
|
|
16199
16324
|
return strategy !== "unsupported" && levels.length > 0 ? strategy : "unsupported";
|
|
16200
16325
|
}
|
|
16201
16326
|
catch {
|
|
@@ -16205,13 +16330,23 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
16205
16330
|
/** Effort levels this instance's backend actually accepts (empty = unsupported). */
|
|
16206
16331
|
effortLevelsFor(instanceName) {
|
|
16207
16332
|
try {
|
|
16208
|
-
const { strategy, levels } =
|
|
16333
|
+
const { strategy, levels } = this.instanceEffortMetadata(instanceName);
|
|
16209
16334
|
return strategy === "unsupported" ? [] : levels;
|
|
16210
16335
|
}
|
|
16211
16336
|
catch {
|
|
16212
16337
|
return [];
|
|
16213
16338
|
}
|
|
16214
16339
|
}
|
|
16340
|
+
/** The instance's backend's effort metadata; agy's levels are its model's (#1825), the one the instance runs. */
|
|
16341
|
+
instanceEffortMetadata(instanceName) {
|
|
16342
|
+
const backend = this.backendNameForInstance(instanceName);
|
|
16343
|
+
let model = null;
|
|
16344
|
+
if (backend === "antigravity") {
|
|
16345
|
+
const resolved = this.resolveInstanceModel(instanceName);
|
|
16346
|
+
model = resolved.source === "unresolved" ? null : resolved.model;
|
|
16347
|
+
}
|
|
16348
|
+
return readEffortMetadata(backend, this.getInstanceDir(instanceName), model);
|
|
16349
|
+
}
|
|
16215
16350
|
/** Configured effort for an instance: per-instance, else fleet default, else none. */
|
|
16216
16351
|
resolveInstanceEffort(instanceName) {
|
|
16217
16352
|
const own = this.fleetConfig?.instances[instanceName]?.effort;
|