@songsid/agend 2.1.8-beta.3 → 2.1.8-beta.5
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/codex.d.ts +3 -4
- package/dist/backend/codex.js +19 -4
- package/dist/backend/codex.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.d.ts +10 -3
- package/dist/daemon.js +80 -7
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +22 -3
- package/dist/fleet-manager.js +122 -11
- 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/dist/transcript-monitor.js +3 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +33 -0
- package/dist/transcript-sources.js +93 -27
- package/dist/transcript-sources.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -552,8 +552,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
552
552
|
/**
|
|
553
553
|
* Ask the daemon to capture the pane before answering, then wait for any state
|
|
554
554
|
* report produced after this request. The ordinary query is intentionally
|
|
555
|
-
* cache-only
|
|
556
|
-
*
|
|
555
|
+
* cache-only; delivery idle gates opt into their own pane/input proof, and
|
|
556
|
+
* this refresh serves lifecycle decisions where a stale state would strand UI.
|
|
557
557
|
*/
|
|
558
558
|
private refreshInstanceExecutionState;
|
|
559
559
|
private deliverWithIdleGate;
|
|
@@ -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";
|
|
@@ -1730,7 +1730,10 @@ export class FleetManager {
|
|
|
1730
1730
|
const query = () => {
|
|
1731
1731
|
const ipc = this.instanceIpcClients.get(instanceName);
|
|
1732
1732
|
if (ipc?.connected) {
|
|
1733
|
-
|
|
1733
|
+
// The daemon owns the backend/TTY proof and its 10s startup-input
|
|
1734
|
+
// timer. A cache-only query can never discover a Context-less idle
|
|
1735
|
+
// pane, and a second fleet timer would stack another 10s wait.
|
|
1736
|
+
ipc.send({ type: "query_instance_state", requestId: `idle-gate-${Date.now()}`, deliveryIdle: true });
|
|
1734
1737
|
}
|
|
1735
1738
|
check();
|
|
1736
1739
|
};
|
|
@@ -1745,8 +1748,8 @@ export class FleetManager {
|
|
|
1745
1748
|
/**
|
|
1746
1749
|
* Ask the daemon to capture the pane before answering, then wait for any state
|
|
1747
1750
|
* report produced after this request. The ordinary query is intentionally
|
|
1748
|
-
* cache-only
|
|
1749
|
-
*
|
|
1751
|
+
* cache-only; delivery idle gates opt into their own pane/input proof, and
|
|
1752
|
+
* this refresh serves lifecycle decisions where a stale state would strand UI.
|
|
1750
1753
|
*/
|
|
1751
1754
|
refreshInstanceExecutionState(instanceName, timeoutMs) {
|
|
1752
1755
|
const ipc = this.instanceIpcClients.get(instanceName);
|
|
@@ -5851,7 +5854,7 @@ export class FleetManager {
|
|
|
5851
5854
|
: type.startsWith("fleet_decision_") ? "fleet_decision_response"
|
|
5852
5855
|
: type === "fleet_task" ? "fleet_task_response"
|
|
5853
5856
|
: type === "fleet_set_display_name" ? "fleet_display_name_response"
|
|
5854
|
-
: type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" ? "fleet_persona_emoji_response"
|
|
5857
|
+
: type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" || type === "fleet_preview_emojis" ? "fleet_persona_emoji_response"
|
|
5855
5858
|
: "fleet_description_response";
|
|
5856
5859
|
ipc.send({ type: responseType, fleetRequestId, error: message });
|
|
5857
5860
|
}
|
|
@@ -5877,21 +5880,21 @@ export class FleetManager {
|
|
|
5877
5880
|
this.handleSetDescription(name, msg);
|
|
5878
5881
|
return;
|
|
5879
5882
|
}
|
|
5880
|
-
if (type === "fleet_list_emojis" || type === "fleet_set_persona_emoji") {
|
|
5883
|
+
if (type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" || type === "fleet_preview_emojis") {
|
|
5881
5884
|
this.handlePersonaEmoji(name, msg);
|
|
5882
5885
|
return;
|
|
5883
5886
|
}
|
|
5884
5887
|
}
|
|
5885
|
-
/** `list_emojis` / `set_persona_emoji` over IPC; the agent endpoint calls the same
|
|
5888
|
+
/** `list_emojis` / `set_persona_emoji` / `preview_emojis` over IPC; the agent endpoint calls the same methods. */
|
|
5886
5889
|
handlePersonaEmoji(instanceName, msg) {
|
|
5887
5890
|
const fleetRequestId = msg.fleetRequestId;
|
|
5888
5891
|
const payload = (msg.payload ?? {});
|
|
5889
5892
|
const ipc = this.instanceIpcClients.get(instanceName);
|
|
5890
5893
|
if (!ipc)
|
|
5891
5894
|
return;
|
|
5892
|
-
const op = msg.type === "fleet_list_emojis"
|
|
5893
|
-
? this.
|
|
5894
|
-
|
|
5895
|
+
const op = msg.type === "fleet_list_emojis" ? this.listEmojisFor(instanceName, payload.refresh === true)
|
|
5896
|
+
: msg.type === "fleet_preview_emojis" ? this.previewEmojis(instanceName, payload)
|
|
5897
|
+
: this.setPersonaEmoji(instanceName, payload);
|
|
5895
5898
|
op.then((r) => typeof r.error === "string"
|
|
5896
5899
|
? ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, error: r.error })
|
|
5897
5900
|
: ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, result: r }), (err) => ipc.send({ type: "fleet_persona_emoji_response", fleetRequestId, error: err.message }));
|
|
@@ -6243,6 +6246,9 @@ export class FleetManager {
|
|
|
6243
6246
|
async handleSetPersonaEmojiHttp(instance, args) {
|
|
6244
6247
|
return this.setPersonaEmoji(instance, args);
|
|
6245
6248
|
}
|
|
6249
|
+
async handlePreviewEmojisHttp(instance, args) {
|
|
6250
|
+
return this.previewEmojis(instance, args);
|
|
6251
|
+
}
|
|
6246
6252
|
async handleSetDescriptionHttp(instance, description) {
|
|
6247
6253
|
if (!this.fleetConfig)
|
|
6248
6254
|
return { error: "Fleet config not available" };
|
|
@@ -6982,12 +6988,117 @@ export class FleetManager {
|
|
|
6982
6988
|
const listed = await this.listGuildEmojis(worldId, refresh);
|
|
6983
6989
|
out.server_emojis = listed.ok
|
|
6984
6990
|
? listed.guilds.map(g => g.emojis
|
|
6985
|
-
? {
|
|
6991
|
+
? {
|
|
6992
|
+
server: g.name || g.id, primary: g.primary,
|
|
6993
|
+
emojis: g.emojis.filter(e => e.available).map(e => {
|
|
6994
|
+
const value = customEmojiValue(e);
|
|
6995
|
+
return { value, image_url: emojiImageUrl(value) };
|
|
6996
|
+
}),
|
|
6997
|
+
}
|
|
6986
6998
|
: { server: g.name || g.id, primary: g.primary, error: g.error })
|
|
6987
6999
|
: { error: listed.error };
|
|
6988
7000
|
}
|
|
6989
7001
|
return out;
|
|
6990
7002
|
}
|
|
7003
|
+
/**
|
|
7004
|
+
* `preview_emojis` (#1040): download a few Discord server emojis so an agent
|
|
7005
|
+
* can look at them before it picks one — a name and an id say nothing about
|
|
7006
|
+
* what an emoji looks like. The fleet fetches, not the agent (not every
|
|
7007
|
+
* backend can), and returns a local path per emoji to Read, as
|
|
7008
|
+
* download_attachment does for an attachment.
|
|
7009
|
+
*
|
|
7010
|
+
* SSRF-safe by construction: nothing from the caller reaches a URL. The
|
|
7011
|
+
* caller names emojis; each must be an available one in a server this bot
|
|
7012
|
+
* can draw on (the list set_persona_emoji checks), and the CDN URL is built
|
|
7013
|
+
* from that listed emoji's numeric id. Static PNG, size-capped, cached by id.
|
|
7014
|
+
*/
|
|
7015
|
+
async previewEmojis(instanceName, args) {
|
|
7016
|
+
const self = this.fleetConfig?.instances[instanceName];
|
|
7017
|
+
if (!self)
|
|
7018
|
+
return { error: this.personaEmojiMissing(instanceName) };
|
|
7019
|
+
const wanted = args.emojis;
|
|
7020
|
+
if (!Array.isArray(wanted) || wanted.length === 0 || wanted.some(e => typeof e !== "string")) {
|
|
7021
|
+
return { error: "emojis is required: a list of <:name:id> values from list_emojis" };
|
|
7022
|
+
}
|
|
7023
|
+
if (wanted.length > FleetManager.EMOJI_PREVIEW_MAX) {
|
|
7024
|
+
return { error: `at most ${FleetManager.EMOJI_PREVIEW_MAX} at a time: narrow them down by name first` };
|
|
7025
|
+
}
|
|
7026
|
+
const { platform } = this.resolveStatusEmojisFor(instanceName);
|
|
7027
|
+
const worldId = this.getInstanceAdapterId(instanceName);
|
|
7028
|
+
if (platform !== "discord" || !worldId) {
|
|
7029
|
+
return { error: "only Discord server emojis need a preview; standard emojis are what they look like" };
|
|
7030
|
+
}
|
|
7031
|
+
const listed = await this.listGuildEmojis(worldId);
|
|
7032
|
+
if (!listed.ok)
|
|
7033
|
+
return { error: `cannot list this bot's server emojis: ${listed.error}` };
|
|
7034
|
+
const previews = [];
|
|
7035
|
+
const errors = [];
|
|
7036
|
+
for (const raw of wanted) {
|
|
7037
|
+
const e = normalizeEmoji(raw);
|
|
7038
|
+
if (e?.kind !== "custom") {
|
|
7039
|
+
errors.push({ emoji: raw, error: "not a server emoji (a standard emoji needs no preview)" });
|
|
7040
|
+
continue;
|
|
7041
|
+
}
|
|
7042
|
+
const found = listed.emojis.find(g => g.id === e.id);
|
|
7043
|
+
if (!found?.available) {
|
|
7044
|
+
errors.push({ emoji: raw, error: "not a server emoji this bot can use" });
|
|
7045
|
+
continue;
|
|
7046
|
+
}
|
|
7047
|
+
try {
|
|
7048
|
+
previews.push({ emoji: customEmojiValue(found), path: await this.fetchEmojiPreview(found.id) });
|
|
7049
|
+
}
|
|
7050
|
+
catch (err) {
|
|
7051
|
+
errors.push({ emoji: raw, error: `download failed: ${err.message}` });
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
return { previews, errors, note: "Read each path to see the emoji (a static PNG)." };
|
|
7055
|
+
}
|
|
7056
|
+
static EMOJI_PREVIEW_MAX = 8;
|
|
7057
|
+
static EMOJI_PREVIEW_MAX_BYTES = 256 * 1024;
|
|
7058
|
+
static EMOJI_PREVIEW_TTL_MS = 24 * 60 * 60_000;
|
|
7059
|
+
/** One emoji's static PNG under the inbox, reused while fresh; stale ones are pruned. */
|
|
7060
|
+
async fetchEmojiPreview(id) {
|
|
7061
|
+
if (!/^\d{15,25}$/.test(id))
|
|
7062
|
+
throw new Error("not a Discord emoji id");
|
|
7063
|
+
const dir = join(this.dataDir, "inbox", "emoji-previews");
|
|
7064
|
+
mkdirSync(dir, { recursive: true });
|
|
7065
|
+
const now = Date.now();
|
|
7066
|
+
for (const name of readdirSync(dir)) {
|
|
7067
|
+
try {
|
|
7068
|
+
if (now - statSync(join(dir, name)).mtimeMs > FleetManager.EMOJI_PREVIEW_TTL_MS)
|
|
7069
|
+
unlinkSync(join(dir, name));
|
|
7070
|
+
}
|
|
7071
|
+
catch { /* raced */ }
|
|
7072
|
+
}
|
|
7073
|
+
const path = join(dir, `${id}.png`);
|
|
7074
|
+
if (existsSync(path))
|
|
7075
|
+
return path;
|
|
7076
|
+
const response = await fetch(`https://cdn.discordapp.com/emojis/${id}.png?size=96`, { signal: AbortSignal.timeout(10_000), redirect: "error" });
|
|
7077
|
+
if (!response.ok)
|
|
7078
|
+
throw new Error(`HTTP ${response.status}`);
|
|
7079
|
+
if (!/^image\/png\b/.test(response.headers.get("content-type") ?? ""))
|
|
7080
|
+
throw new Error("not a PNG");
|
|
7081
|
+
const reader = response.body?.getReader();
|
|
7082
|
+
if (!reader)
|
|
7083
|
+
throw new Error("empty response");
|
|
7084
|
+
const chunks = [];
|
|
7085
|
+
let size = 0;
|
|
7086
|
+
for (;;) {
|
|
7087
|
+
const { done, value } = await reader.read();
|
|
7088
|
+
if (done)
|
|
7089
|
+
break;
|
|
7090
|
+
size += value.byteLength;
|
|
7091
|
+
if (size > FleetManager.EMOJI_PREVIEW_MAX_BYTES) {
|
|
7092
|
+
await reader.cancel();
|
|
7093
|
+
throw new Error("image too large");
|
|
7094
|
+
}
|
|
7095
|
+
chunks.push(value);
|
|
7096
|
+
}
|
|
7097
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
7098
|
+
writeFileSync(tmp, Buffer.concat(chunks));
|
|
7099
|
+
renameSync(tmp, path);
|
|
7100
|
+
return path;
|
|
7101
|
+
}
|
|
6991
7102
|
/**
|
|
6992
7103
|
* `set_persona_emoji`: `instanceName` sets one of its own status emojis —
|
|
6993
7104
|
* `delivered` unless it names another — in its per-instance `status_emojis`
|