@songsid/agend 2.1.8-beta.3 → 2.1.8-beta.4
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-cli.js +5 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +1 -0
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/muse.js +20 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/daemon.js +3 -2
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +20 -1
- package/dist/fleet-manager.js +116 -8
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +13 -6
- package/dist/outbound-schemas.d.ts +3 -0
- package/dist/outbound-schemas.js +4 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/tool-permissions.js +4 -2
- package/dist/tool-permissions.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -961,7 +961,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
961
961
|
*/
|
|
962
962
|
private refuseTypedIpc;
|
|
963
963
|
private dispatchTypedIpc;
|
|
964
|
-
/** `list_emojis` / `set_persona_emoji` over IPC; the agent endpoint calls the same
|
|
964
|
+
/** `list_emojis` / `set_persona_emoji` / `preview_emojis` over IPC; the agent endpoint calls the same methods. */
|
|
965
965
|
private handlePersonaEmoji;
|
|
966
966
|
private handleScheduleCrud;
|
|
967
967
|
private handleDecisionCrud;
|
|
@@ -992,6 +992,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
992
992
|
handleSetDisplayNameHttp(instance: string, name: string): Promise<unknown>;
|
|
993
993
|
handleListEmojisHttp(instance: string, refresh: boolean): Promise<unknown>;
|
|
994
994
|
handleSetPersonaEmojiHttp(instance: string, args: Record<string, unknown>): Promise<unknown>;
|
|
995
|
+
handlePreviewEmojisHttp(instance: string, args: Record<string, unknown>): Promise<unknown>;
|
|
995
996
|
handleSetDescriptionHttp(instance: string, description: string): Promise<unknown>;
|
|
996
997
|
private summarizeToolCall;
|
|
997
998
|
private handleTaskCrud;
|
|
@@ -1126,6 +1127,24 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1126
1127
|
* Settings picker (#1005/#1021), so an agent and an operator see one truth.
|
|
1127
1128
|
*/
|
|
1128
1129
|
listEmojisFor(instanceName: string, refresh?: boolean): Promise<Record<string, unknown>>;
|
|
1130
|
+
/**
|
|
1131
|
+
* `preview_emojis` (#1040): download a few Discord server emojis so an agent
|
|
1132
|
+
* can look at them before it picks one — a name and an id say nothing about
|
|
1133
|
+
* what an emoji looks like. The fleet fetches, not the agent (not every
|
|
1134
|
+
* backend can), and returns a local path per emoji to Read, as
|
|
1135
|
+
* download_attachment does for an attachment.
|
|
1136
|
+
*
|
|
1137
|
+
* SSRF-safe by construction: nothing from the caller reaches a URL. The
|
|
1138
|
+
* caller names emojis; each must be an available one in a server this bot
|
|
1139
|
+
* can draw on (the list set_persona_emoji checks), and the CDN URL is built
|
|
1140
|
+
* from that listed emoji's numeric id. Static PNG, size-capped, cached by id.
|
|
1141
|
+
*/
|
|
1142
|
+
previewEmojis(instanceName: string, args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
1143
|
+
private static EMOJI_PREVIEW_MAX;
|
|
1144
|
+
private static EMOJI_PREVIEW_MAX_BYTES;
|
|
1145
|
+
private static EMOJI_PREVIEW_TTL_MS;
|
|
1146
|
+
/** One emoji's static PNG under the inbox, reused while fresh; stale ones are pruned. */
|
|
1147
|
+
private fetchEmojiPreview;
|
|
1129
1148
|
/**
|
|
1130
1149
|
* `set_persona_emoji`: `instanceName` sets one of its own status emojis —
|
|
1131
1150
|
* `delivered` unless it names another — in its per-instance `status_emojis`
|
package/dist/fleet-manager.js
CHANGED
|
@@ -17,7 +17,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
17
17
|
const __dirname = dirname(__filename);
|
|
18
18
|
/** Fallback access policy for a channel with no `access:` block — open (no gate). */
|
|
19
19
|
const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
|
|
20
|
-
import { STATUS_EMOJI_CONFIG_KEYS, STATUS_EMOJI_KEYS, STATUS_EMOJI_SUGGESTIONS, TELEGRAM_REACTION_EMOJIS, builtinStatusEmojis, customEmojiValue, normalizeEmoji, previewStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusEmojiProblem, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
|
|
20
|
+
import { STATUS_EMOJI_CONFIG_KEYS, STATUS_EMOJI_KEYS, STATUS_EMOJI_SUGGESTIONS, TELEGRAM_REACTION_EMOJIS, builtinStatusEmojis, customEmojiValue, emojiImageUrl, normalizeEmoji, previewStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusEmojiProblem, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
|
|
21
21
|
import { isProbeableRouteTarget } from "./fleet-context.js";
|
|
22
22
|
import { loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG } from "./config.js";
|
|
23
23
|
import { EventLog } from "./event-log.js";
|
|
@@ -5851,7 +5851,7 @@ export class FleetManager {
|
|
|
5851
5851
|
: type.startsWith("fleet_decision_") ? "fleet_decision_response"
|
|
5852
5852
|
: type === "fleet_task" ? "fleet_task_response"
|
|
5853
5853
|
: type === "fleet_set_display_name" ? "fleet_display_name_response"
|
|
5854
|
-
: type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" ? "fleet_persona_emoji_response"
|
|
5854
|
+
: type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" || type === "fleet_preview_emojis" ? "fleet_persona_emoji_response"
|
|
5855
5855
|
: "fleet_description_response";
|
|
5856
5856
|
ipc.send({ type: responseType, fleetRequestId, error: message });
|
|
5857
5857
|
}
|
|
@@ -5877,21 +5877,21 @@ export class FleetManager {
|
|
|
5877
5877
|
this.handleSetDescription(name, msg);
|
|
5878
5878
|
return;
|
|
5879
5879
|
}
|
|
5880
|
-
if (type === "fleet_list_emojis" || type === "fleet_set_persona_emoji") {
|
|
5880
|
+
if (type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" || type === "fleet_preview_emojis") {
|
|
5881
5881
|
this.handlePersonaEmoji(name, msg);
|
|
5882
5882
|
return;
|
|
5883
5883
|
}
|
|
5884
5884
|
}
|
|
5885
|
-
/** `list_emojis` / `set_persona_emoji` over IPC; the agent endpoint calls the same
|
|
5885
|
+
/** `list_emojis` / `set_persona_emoji` / `preview_emojis` over IPC; the agent endpoint calls the same methods. */
|
|
5886
5886
|
handlePersonaEmoji(instanceName, msg) {
|
|
5887
5887
|
const fleetRequestId = msg.fleetRequestId;
|
|
5888
5888
|
const payload = (msg.payload ?? {});
|
|
5889
5889
|
const ipc = this.instanceIpcClients.get(instanceName);
|
|
5890
5890
|
if (!ipc)
|
|
5891
5891
|
return;
|
|
5892
|
-
const op = msg.type === "fleet_list_emojis"
|
|
5893
|
-
? this.
|
|
5894
|
-
|
|
5892
|
+
const op = msg.type === "fleet_list_emojis" ? this.listEmojisFor(instanceName, payload.refresh === true)
|
|
5893
|
+
: msg.type === "fleet_preview_emojis" ? this.previewEmojis(instanceName, payload)
|
|
5894
|
+
: this.setPersonaEmoji(instanceName, payload);
|
|
5895
5895
|
op.then((r) => typeof r.error === "string"
|
|
5896
5896
|
? ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, error: r.error })
|
|
5897
5897
|
: ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, result: r }), (err) => ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, error: err.message }));
|
|
@@ -6243,6 +6243,9 @@ export class FleetManager {
|
|
|
6243
6243
|
async handleSetPersonaEmojiHttp(instance, args) {
|
|
6244
6244
|
return this.setPersonaEmoji(instance, args);
|
|
6245
6245
|
}
|
|
6246
|
+
async handlePreviewEmojisHttp(instance, args) {
|
|
6247
|
+
return this.previewEmojis(instance, args);
|
|
6248
|
+
}
|
|
6246
6249
|
async handleSetDescriptionHttp(instance, description) {
|
|
6247
6250
|
if (!this.fleetConfig)
|
|
6248
6251
|
return { error: "Fleet config not available" };
|
|
@@ -6982,12 +6985,117 @@ export class FleetManager {
|
|
|
6982
6985
|
const listed = await this.listGuildEmojis(worldId, refresh);
|
|
6983
6986
|
out.server_emojis = listed.ok
|
|
6984
6987
|
? listed.guilds.map(g => g.emojis
|
|
6985
|
-
? {
|
|
6988
|
+
? {
|
|
6989
|
+
server: g.name || g.id, primary: g.primary,
|
|
6990
|
+
emojis: g.emojis.filter(e => e.available).map(e => {
|
|
6991
|
+
const value = customEmojiValue(e);
|
|
6992
|
+
return { value, image_url: emojiImageUrl(value) };
|
|
6993
|
+
}),
|
|
6994
|
+
}
|
|
6986
6995
|
: { server: g.name || g.id, primary: g.primary, error: g.error })
|
|
6987
6996
|
: { error: listed.error };
|
|
6988
6997
|
}
|
|
6989
6998
|
return out;
|
|
6990
6999
|
}
|
|
7000
|
+
/**
|
|
7001
|
+
* `preview_emojis` (#1040): download a few Discord server emojis so an agent
|
|
7002
|
+
* can look at them before it picks one — a name and an id say nothing about
|
|
7003
|
+
* what an emoji looks like. The fleet fetches, not the agent (not every
|
|
7004
|
+
* backend can), and returns a local path per emoji to Read, as
|
|
7005
|
+
* download_attachment does for an attachment.
|
|
7006
|
+
*
|
|
7007
|
+
* SSRF-safe by construction: nothing from the caller reaches a URL. The
|
|
7008
|
+
* caller names emojis; each must be an available one in a server this bot
|
|
7009
|
+
* can draw on (the list set_persona_emoji checks), and the CDN URL is built
|
|
7010
|
+
* from that listed emoji's numeric id. Static PNG, size-capped, cached by id.
|
|
7011
|
+
*/
|
|
7012
|
+
async previewEmojis(instanceName, args) {
|
|
7013
|
+
const self = this.fleetConfig?.instances[instanceName];
|
|
7014
|
+
if (!self)
|
|
7015
|
+
return { error: this.personaEmojiMissing(instanceName) };
|
|
7016
|
+
const wanted = args.emojis;
|
|
7017
|
+
if (!Array.isArray(wanted) || wanted.length === 0 || wanted.some(e => typeof e !== "string")) {
|
|
7018
|
+
return { error: "emojis is required: a list of <:name:id> values from list_emojis" };
|
|
7019
|
+
}
|
|
7020
|
+
if (wanted.length > FleetManager.EMOJI_PREVIEW_MAX) {
|
|
7021
|
+
return { error: `at most ${FleetManager.EMOJI_PREVIEW_MAX} at a time: narrow them down by name first` };
|
|
7022
|
+
}
|
|
7023
|
+
const { platform } = this.resolveStatusEmojisFor(instanceName);
|
|
7024
|
+
const worldId = this.getInstanceAdapterId(instanceName);
|
|
7025
|
+
if (platform !== "discord" || !worldId) {
|
|
7026
|
+
return { error: "only Discord server emojis need a preview; standard emojis are what they look like" };
|
|
7027
|
+
}
|
|
7028
|
+
const listed = await this.listGuildEmojis(worldId);
|
|
7029
|
+
if (!listed.ok)
|
|
7030
|
+
return { error: `cannot list this bot's server emojis: ${listed.error}` };
|
|
7031
|
+
const previews = [];
|
|
7032
|
+
const errors = [];
|
|
7033
|
+
for (const raw of wanted) {
|
|
7034
|
+
const e = normalizeEmoji(raw);
|
|
7035
|
+
if (e?.kind !== "custom") {
|
|
7036
|
+
errors.push({ emoji: raw, error: "not a server emoji (a standard emoji needs no preview)" });
|
|
7037
|
+
continue;
|
|
7038
|
+
}
|
|
7039
|
+
const found = listed.emojis.find(g => g.id === e.id);
|
|
7040
|
+
if (!found?.available) {
|
|
7041
|
+
errors.push({ emoji: raw, error: "not a server emoji this bot can use" });
|
|
7042
|
+
continue;
|
|
7043
|
+
}
|
|
7044
|
+
try {
|
|
7045
|
+
previews.push({ emoji: customEmojiValue(found), path: await this.fetchEmojiPreview(found.id) });
|
|
7046
|
+
}
|
|
7047
|
+
catch (err) {
|
|
7048
|
+
errors.push({ emoji: raw, error: `download failed: ${err.message}` });
|
|
7049
|
+
}
|
|
7050
|
+
}
|
|
7051
|
+
return { previews, errors, note: "Read each path to see the emoji (a static PNG)." };
|
|
7052
|
+
}
|
|
7053
|
+
static EMOJI_PREVIEW_MAX = 8;
|
|
7054
|
+
static EMOJI_PREVIEW_MAX_BYTES = 256 * 1024;
|
|
7055
|
+
static EMOJI_PREVIEW_TTL_MS = 24 * 60 * 60_000;
|
|
7056
|
+
/** One emoji's static PNG under the inbox, reused while fresh; stale ones are pruned. */
|
|
7057
|
+
async fetchEmojiPreview(id) {
|
|
7058
|
+
if (!/^\d{15,25}$/.test(id))
|
|
7059
|
+
throw new Error("not a Discord emoji id");
|
|
7060
|
+
const dir = join(this.dataDir, "inbox", "emoji-previews");
|
|
7061
|
+
mkdirSync(dir, { recursive: true });
|
|
7062
|
+
const now = Date.now();
|
|
7063
|
+
for (const name of readdirSync(dir)) {
|
|
7064
|
+
try {
|
|
7065
|
+
if (now - statSync(join(dir, name)).mtimeMs > FleetManager.EMOJI_PREVIEW_TTL_MS)
|
|
7066
|
+
unlinkSync(join(dir, name));
|
|
7067
|
+
}
|
|
7068
|
+
catch { /* raced */ }
|
|
7069
|
+
}
|
|
7070
|
+
const path = join(dir, `${id}.png`);
|
|
7071
|
+
if (existsSync(path))
|
|
7072
|
+
return path;
|
|
7073
|
+
const response = await fetch(`https://cdn.discordapp.com/emojis/${id}.png?size=96`, { signal: AbortSignal.timeout(10_000), redirect: "error" });
|
|
7074
|
+
if (!response.ok)
|
|
7075
|
+
throw new Error(`HTTP ${response.status}`);
|
|
7076
|
+
if (!/^image\/png\b/.test(response.headers.get("content-type") ?? ""))
|
|
7077
|
+
throw new Error("not a PNG");
|
|
7078
|
+
const reader = response.body?.getReader();
|
|
7079
|
+
if (!reader)
|
|
7080
|
+
throw new Error("empty response");
|
|
7081
|
+
const chunks = [];
|
|
7082
|
+
let size = 0;
|
|
7083
|
+
for (;;) {
|
|
7084
|
+
const { done, value } = await reader.read();
|
|
7085
|
+
if (done)
|
|
7086
|
+
break;
|
|
7087
|
+
size += value.byteLength;
|
|
7088
|
+
if (size > FleetManager.EMOJI_PREVIEW_MAX_BYTES) {
|
|
7089
|
+
await reader.cancel();
|
|
7090
|
+
throw new Error("image too large");
|
|
7091
|
+
}
|
|
7092
|
+
chunks.push(value);
|
|
7093
|
+
}
|
|
7094
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
7095
|
+
writeFileSync(tmp, Buffer.concat(chunks));
|
|
7096
|
+
renameSync(tmp, path);
|
|
7097
|
+
return path;
|
|
7098
|
+
}
|
|
6991
7099
|
/**
|
|
6992
7100
|
* `set_persona_emoji`: `instanceName` sets one of its own status emojis —
|
|
6993
7101
|
* `delivered` unless it names another — in its per-instance `status_emojis`
|