@elizaos/plugin-discord 2.0.0-alpha.11 → 2.0.0-alpha.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/actions/setup-credentials.d.ts +25 -0
- package/dist/actions/setup-credentials.d.ts.map +1 -0
- package/dist/compat.d.ts +1 -0
- package/dist/compat.d.ts.map +1 -1
- package/dist/debouncer.d.ts +25 -0
- package/dist/debouncer.d.ts.map +1 -0
- package/dist/draft-chunking.d.ts +9 -0
- package/dist/draft-chunking.d.ts.map +1 -0
- package/dist/draft-stream.d.ts +21 -0
- package/dist/draft-stream.d.ts.map +1 -0
- package/dist/environment.d.ts +1 -0
- package/dist/environment.d.ts.map +1 -1
- package/dist/identity.d.ts +6 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/inbound-envelope.d.ts +8 -0
- package/dist/inbound-envelope.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3087 -550
- package/dist/index.js.map +24 -14
- package/dist/messages.d.ts +7 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/profileSync.d.ts +8 -0
- package/dist/profileSync.d.ts.map +1 -0
- package/dist/providers/channelState.d.ts.map +1 -1
- package/dist/providers/voiceState.d.ts.map +1 -1
- package/dist/reasoning-tags.d.ts +2 -0
- package/dist/reasoning-tags.d.ts.map +1 -0
- package/dist/service.d.ts +8 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/slash-commands.d.ts +31 -0
- package/dist/slash-commands.d.ts.map +1 -0
- package/dist/status-reactions.d.ts +11 -0
- package/dist/status-reactions.d.ts.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/typing.d.ts +7 -0
- package/dist/typing.d.ts.map +1 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/voice.d.ts +4 -0
- package/dist/voice.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -457,26 +457,26 @@ var require_channel = __commonJS((exports) => {
|
|
|
457
457
|
ForumLayoutType2[ForumLayoutType2["ListView"] = 1] = "ListView";
|
|
458
458
|
ForumLayoutType2[ForumLayoutType2["GalleryView"] = 2] = "GalleryView";
|
|
459
459
|
})(ForumLayoutType || (exports.ForumLayoutType = ForumLayoutType = {}));
|
|
460
|
-
var
|
|
461
|
-
(function(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
})(
|
|
460
|
+
var ChannelType10;
|
|
461
|
+
(function(ChannelType11) {
|
|
462
|
+
ChannelType11[ChannelType11["GuildText"] = 0] = "GuildText";
|
|
463
|
+
ChannelType11[ChannelType11["DM"] = 1] = "DM";
|
|
464
|
+
ChannelType11[ChannelType11["GuildVoice"] = 2] = "GuildVoice";
|
|
465
|
+
ChannelType11[ChannelType11["GroupDM"] = 3] = "GroupDM";
|
|
466
|
+
ChannelType11[ChannelType11["GuildCategory"] = 4] = "GuildCategory";
|
|
467
|
+
ChannelType11[ChannelType11["GuildAnnouncement"] = 5] = "GuildAnnouncement";
|
|
468
|
+
ChannelType11[ChannelType11["AnnouncementThread"] = 10] = "AnnouncementThread";
|
|
469
|
+
ChannelType11[ChannelType11["PublicThread"] = 11] = "PublicThread";
|
|
470
|
+
ChannelType11[ChannelType11["PrivateThread"] = 12] = "PrivateThread";
|
|
471
|
+
ChannelType11[ChannelType11["GuildStageVoice"] = 13] = "GuildStageVoice";
|
|
472
|
+
ChannelType11[ChannelType11["GuildDirectory"] = 14] = "GuildDirectory";
|
|
473
|
+
ChannelType11[ChannelType11["GuildForum"] = 15] = "GuildForum";
|
|
474
|
+
ChannelType11[ChannelType11["GuildMedia"] = 16] = "GuildMedia";
|
|
475
|
+
ChannelType11[ChannelType11["GuildNews"] = 5] = "GuildNews";
|
|
476
|
+
ChannelType11[ChannelType11["GuildNewsThread"] = 10] = "GuildNewsThread";
|
|
477
|
+
ChannelType11[ChannelType11["GuildPublicThread"] = 11] = "GuildPublicThread";
|
|
478
|
+
ChannelType11[ChannelType11["GuildPrivateThread"] = 12] = "GuildPrivateThread";
|
|
479
|
+
})(ChannelType10 || (exports.ChannelType = ChannelType10 = {}));
|
|
480
480
|
var VideoQualityMode;
|
|
481
481
|
(function(VideoQualityMode2) {
|
|
482
482
|
VideoQualityMode2[VideoQualityMode2["Auto"] = 1] = "Auto";
|
|
@@ -924,20 +924,20 @@ var require_role = __commonJS((exports) => {
|
|
|
924
924
|
var require_shared = __commonJS((exports) => {
|
|
925
925
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
926
926
|
exports.ApplicationCommandOptionType = undefined;
|
|
927
|
-
var
|
|
928
|
-
(function(
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
})(
|
|
927
|
+
var ApplicationCommandOptionType2;
|
|
928
|
+
(function(ApplicationCommandOptionType3) {
|
|
929
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Subcommand"] = 1] = "Subcommand";
|
|
930
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["SubcommandGroup"] = 2] = "SubcommandGroup";
|
|
931
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["String"] = 3] = "String";
|
|
932
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Integer"] = 4] = "Integer";
|
|
933
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Boolean"] = 5] = "Boolean";
|
|
934
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["User"] = 6] = "User";
|
|
935
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Channel"] = 7] = "Channel";
|
|
936
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Role"] = 8] = "Role";
|
|
937
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Mentionable"] = 9] = "Mentionable";
|
|
938
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Number"] = 10] = "Number";
|
|
939
|
+
ApplicationCommandOptionType3[ApplicationCommandOptionType3["Attachment"] = 11] = "Attachment";
|
|
940
|
+
})(ApplicationCommandOptionType2 || (exports.ApplicationCommandOptionType = ApplicationCommandOptionType2 = {}));
|
|
941
941
|
});
|
|
942
942
|
|
|
943
943
|
// ../../../node_modules/.bun/discord-api-types@0.37.120/node_modules/discord-api-types/payloads/v10/_interactions/_applicationCommands/_chatInput/string.js
|
|
@@ -2702,7 +2702,7 @@ var require_v106 = __commonJS((exports) => {
|
|
|
2702
2702
|
});
|
|
2703
2703
|
|
|
2704
2704
|
// index.ts
|
|
2705
|
-
import { logger as
|
|
2705
|
+
import { logger as logger6 } from "@elizaos/core";
|
|
2706
2706
|
|
|
2707
2707
|
// actions/chatWithAttachments.ts
|
|
2708
2708
|
import fs from "node:fs";
|
|
@@ -6250,13 +6250,653 @@ var serverInfo = {
|
|
|
6250
6250
|
};
|
|
6251
6251
|
var serverInfo_default = serverInfo;
|
|
6252
6252
|
|
|
6253
|
+
// actions/setup-credentials.ts
|
|
6254
|
+
import * as fs2 from "node:fs";
|
|
6255
|
+
import * as os from "node:os";
|
|
6256
|
+
import * as path from "node:path";
|
|
6257
|
+
import {
|
|
6258
|
+
logger
|
|
6259
|
+
} from "@elizaos/core";
|
|
6260
|
+
import { ChannelType as ChannelType2 } from "discord.js";
|
|
6261
|
+
var SAFE_PRESET_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
6262
|
+
var SESSION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6263
|
+
var presets = new Map;
|
|
6264
|
+
var activeSessions = new Map;
|
|
6265
|
+
function escapeRegex(value) {
|
|
6266
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6267
|
+
}
|
|
6268
|
+
function getCredentialsDir() {
|
|
6269
|
+
const configured = process.env.CREDENTIALS_DIR?.trim();
|
|
6270
|
+
if (configured) {
|
|
6271
|
+
return configured;
|
|
6272
|
+
}
|
|
6273
|
+
const home = (typeof os.homedir === "function" ? os.homedir() : "") || process.env.HOME || process.env.USERPROFILE;
|
|
6274
|
+
return home ? path.join(home, ".credentials") : path.join(process.cwd(), ".credentials");
|
|
6275
|
+
}
|
|
6276
|
+
function registerPreset(preset) {
|
|
6277
|
+
const normalizedName = preset.name.trim().toLowerCase();
|
|
6278
|
+
if (!SAFE_PRESET_NAME_RE.test(normalizedName)) {
|
|
6279
|
+
throw new Error(`Invalid credential preset name "${preset.name}". Only letters, numbers, underscores, and hyphens are allowed.`);
|
|
6280
|
+
}
|
|
6281
|
+
presets.set(normalizedName, { ...preset, name: normalizedName });
|
|
6282
|
+
}
|
|
6283
|
+
function getPreset(name) {
|
|
6284
|
+
return presets.get(name.toLowerCase());
|
|
6285
|
+
}
|
|
6286
|
+
function listPresets() {
|
|
6287
|
+
return [...presets.keys()];
|
|
6288
|
+
}
|
|
6289
|
+
registerPreset({
|
|
6290
|
+
name: "github",
|
|
6291
|
+
displayName: "GitHub",
|
|
6292
|
+
fields: [{ key: "token", label: "Personal Access Token", secret: true }],
|
|
6293
|
+
helpUrl: "https://github.com/settings/tokens",
|
|
6294
|
+
helpText: "Create a fine-grained PAT at the link above. Give it the repository permissions you need.",
|
|
6295
|
+
async validate(credentials) {
|
|
6296
|
+
try {
|
|
6297
|
+
const response = await fetch("https://api.github.com/user", {
|
|
6298
|
+
headers: {
|
|
6299
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
6300
|
+
Accept: "application/vnd.github+json"
|
|
6301
|
+
}
|
|
6302
|
+
});
|
|
6303
|
+
if (!response.ok) {
|
|
6304
|
+
return {
|
|
6305
|
+
valid: false,
|
|
6306
|
+
error: `GitHub returned ${response.status}`
|
|
6307
|
+
};
|
|
6308
|
+
}
|
|
6309
|
+
const data = await response.json();
|
|
6310
|
+
return {
|
|
6311
|
+
valid: true,
|
|
6312
|
+
identity: data.login ? `@${data.login}` : "verified"
|
|
6313
|
+
};
|
|
6314
|
+
} catch (error) {
|
|
6315
|
+
return {
|
|
6316
|
+
valid: false,
|
|
6317
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
});
|
|
6322
|
+
registerPreset({
|
|
6323
|
+
name: "vercel",
|
|
6324
|
+
displayName: "Vercel",
|
|
6325
|
+
fields: [{ key: "token", label: "API Token", secret: true }],
|
|
6326
|
+
helpUrl: "https://vercel.com/account/tokens",
|
|
6327
|
+
helpText: "Create a token at the link above. Full Account scope works best.",
|
|
6328
|
+
async validate(credentials) {
|
|
6329
|
+
try {
|
|
6330
|
+
const response = await fetch("https://api.vercel.com/v9/projects", {
|
|
6331
|
+
headers: { Authorization: `Bearer ${credentials.token}` }
|
|
6332
|
+
});
|
|
6333
|
+
if (!response.ok) {
|
|
6334
|
+
return {
|
|
6335
|
+
valid: false,
|
|
6336
|
+
error: `Vercel returned ${response.status}`
|
|
6337
|
+
};
|
|
6338
|
+
}
|
|
6339
|
+
const data = await response.json();
|
|
6340
|
+
return {
|
|
6341
|
+
valid: true,
|
|
6342
|
+
identity: `${data.projects?.length ?? 0} project(s) accessible`
|
|
6343
|
+
};
|
|
6344
|
+
} catch (error) {
|
|
6345
|
+
return {
|
|
6346
|
+
valid: false,
|
|
6347
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6348
|
+
};
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
});
|
|
6352
|
+
registerPreset({
|
|
6353
|
+
name: "cloudflare",
|
|
6354
|
+
displayName: "Cloudflare",
|
|
6355
|
+
fields: [
|
|
6356
|
+
{ key: "apiKey", label: "Global API Key", secret: true },
|
|
6357
|
+
{ key: "email", label: "Account Email", secret: false }
|
|
6358
|
+
],
|
|
6359
|
+
helpUrl: "https://dash.cloudflare.com/profile/api-tokens",
|
|
6360
|
+
helpText: 'Go to Cloudflare > Profile > API Tokens > "Global API Key". You will also need your account email.',
|
|
6361
|
+
async validate(credentials) {
|
|
6362
|
+
try {
|
|
6363
|
+
const response = await fetch("https://api.cloudflare.com/client/v4/zones", {
|
|
6364
|
+
headers: {
|
|
6365
|
+
"X-Auth-Key": credentials.apiKey,
|
|
6366
|
+
"X-Auth-Email": credentials.email
|
|
6367
|
+
}
|
|
6368
|
+
});
|
|
6369
|
+
if (!response.ok) {
|
|
6370
|
+
return {
|
|
6371
|
+
valid: false,
|
|
6372
|
+
error: `Cloudflare returned ${response.status}`
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
const data = await response.json();
|
|
6376
|
+
return {
|
|
6377
|
+
valid: true,
|
|
6378
|
+
identity: data.result && data.result.length > 0 ? `zones: ${data.result.map((zone) => zone.name).join(", ")}` : "verified"
|
|
6379
|
+
};
|
|
6380
|
+
} catch (error) {
|
|
6381
|
+
return {
|
|
6382
|
+
valid: false,
|
|
6383
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6384
|
+
};
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
});
|
|
6388
|
+
registerPreset({
|
|
6389
|
+
name: "anthropic",
|
|
6390
|
+
displayName: "Anthropic",
|
|
6391
|
+
fields: [{ key: "apiKey", label: "API Key", secret: true }],
|
|
6392
|
+
helpUrl: "https://console.anthropic.com/settings/keys",
|
|
6393
|
+
helpText: "Create an API key in the Anthropic console.",
|
|
6394
|
+
async validate(credentials) {
|
|
6395
|
+
try {
|
|
6396
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
6397
|
+
method: "POST",
|
|
6398
|
+
headers: {
|
|
6399
|
+
"x-api-key": credentials.apiKey,
|
|
6400
|
+
"anthropic-version": "2023-06-01",
|
|
6401
|
+
"Content-Type": "application/json"
|
|
6402
|
+
},
|
|
6403
|
+
body: JSON.stringify({
|
|
6404
|
+
model: "claude-3-5-haiku-20241022",
|
|
6405
|
+
max_tokens: 1,
|
|
6406
|
+
messages: [{ role: "user", content: "hi" }]
|
|
6407
|
+
})
|
|
6408
|
+
});
|
|
6409
|
+
if (response.ok || response.status === 429) {
|
|
6410
|
+
return { valid: true, identity: "key verified" };
|
|
6411
|
+
}
|
|
6412
|
+
return {
|
|
6413
|
+
valid: false,
|
|
6414
|
+
error: `Anthropic returned ${response.status}`
|
|
6415
|
+
};
|
|
6416
|
+
} catch (error) {
|
|
6417
|
+
return {
|
|
6418
|
+
valid: false,
|
|
6419
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
});
|
|
6424
|
+
registerPreset({
|
|
6425
|
+
name: "openai",
|
|
6426
|
+
displayName: "OpenAI",
|
|
6427
|
+
fields: [{ key: "apiKey", label: "API Key", secret: true }],
|
|
6428
|
+
helpUrl: "https://platform.openai.com/api-keys",
|
|
6429
|
+
helpText: "Create an API key at the OpenAI platform link above.",
|
|
6430
|
+
async validate(credentials) {
|
|
6431
|
+
try {
|
|
6432
|
+
const response = await fetch("https://api.openai.com/v1/models", {
|
|
6433
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` }
|
|
6434
|
+
});
|
|
6435
|
+
if (response.ok || response.status === 429) {
|
|
6436
|
+
return { valid: true, identity: "key verified" };
|
|
6437
|
+
}
|
|
6438
|
+
return {
|
|
6439
|
+
valid: false,
|
|
6440
|
+
error: `OpenAI returned ${response.status}`
|
|
6441
|
+
};
|
|
6442
|
+
} catch (error) {
|
|
6443
|
+
return {
|
|
6444
|
+
valid: false,
|
|
6445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6446
|
+
};
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
});
|
|
6450
|
+
registerPreset({
|
|
6451
|
+
name: "fal",
|
|
6452
|
+
displayName: "fal.ai",
|
|
6453
|
+
fields: [{ key: "apiKey", label: "API Key", secret: true }],
|
|
6454
|
+
helpUrl: "https://fal.ai/dashboard/keys",
|
|
6455
|
+
helpText: "Generate an API key from your fal.ai dashboard.",
|
|
6456
|
+
async validate(credentials) {
|
|
6457
|
+
try {
|
|
6458
|
+
const response = await fetch("https://rest.fal.run/fal-ai/fast-sdxl", {
|
|
6459
|
+
method: "POST",
|
|
6460
|
+
headers: {
|
|
6461
|
+
Authorization: `Key ${credentials.apiKey}`,
|
|
6462
|
+
"Content-Type": "application/json"
|
|
6463
|
+
},
|
|
6464
|
+
body: JSON.stringify({
|
|
6465
|
+
prompt: "test",
|
|
6466
|
+
image_size: { width: 64, height: 64 },
|
|
6467
|
+
num_images: 1
|
|
6468
|
+
})
|
|
6469
|
+
});
|
|
6470
|
+
if (response.ok || response.status === 422 || response.status === 429) {
|
|
6471
|
+
return { valid: true, identity: "key verified" };
|
|
6472
|
+
}
|
|
6473
|
+
return {
|
|
6474
|
+
valid: false,
|
|
6475
|
+
error: `fal.ai returned ${response.status}`
|
|
6476
|
+
};
|
|
6477
|
+
} catch (error) {
|
|
6478
|
+
return {
|
|
6479
|
+
valid: false,
|
|
6480
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6481
|
+
};
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
});
|
|
6485
|
+
registerPreset({
|
|
6486
|
+
name: "generic",
|
|
6487
|
+
displayName: "Custom Credential",
|
|
6488
|
+
fields: [
|
|
6489
|
+
{
|
|
6490
|
+
key: "envName",
|
|
6491
|
+
label: "environment variable name (for example MY_API_KEY)",
|
|
6492
|
+
secret: false
|
|
6493
|
+
},
|
|
6494
|
+
{ key: "value", label: "value", secret: true }
|
|
6495
|
+
],
|
|
6496
|
+
helpUrl: "",
|
|
6497
|
+
helpText: "I'll store this as a generic credential. Give me the env var name and value.",
|
|
6498
|
+
async validate() {
|
|
6499
|
+
return { valid: true, identity: "stored (unvalidated)" };
|
|
6500
|
+
}
|
|
6501
|
+
});
|
|
6502
|
+
async function ensureCredentialsDir() {
|
|
6503
|
+
await fs2.promises.mkdir(getCredentialsDir(), {
|
|
6504
|
+
recursive: true,
|
|
6505
|
+
mode: 448
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
async function storeCredentials(service, credentials) {
|
|
6509
|
+
await ensureCredentialsDir();
|
|
6510
|
+
const filePath = path.join(getCredentialsDir(), `${service}.json`);
|
|
6511
|
+
await fs2.promises.writeFile(filePath, JSON.stringify(credentials, null, 2), {
|
|
6512
|
+
mode: 384
|
|
6513
|
+
});
|
|
6514
|
+
}
|
|
6515
|
+
function cleanExpiredSessions() {
|
|
6516
|
+
const now = Date.now();
|
|
6517
|
+
for (const [userId, session] of activeSessions) {
|
|
6518
|
+
if (now - session.startedAt > SESSION_TIMEOUT_MS) {
|
|
6519
|
+
activeSessions.delete(userId);
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
async function tryDeleteMessage(discordService, channelId, messageId, fieldName) {
|
|
6524
|
+
try {
|
|
6525
|
+
const client = discordService.client;
|
|
6526
|
+
if (!client) {
|
|
6527
|
+
return false;
|
|
6528
|
+
}
|
|
6529
|
+
const channel = await client.channels.fetch(channelId);
|
|
6530
|
+
if (!channel || !("messages" in channel)) {
|
|
6531
|
+
return false;
|
|
6532
|
+
}
|
|
6533
|
+
const message = await channel.messages.fetch(messageId);
|
|
6534
|
+
await message.delete();
|
|
6535
|
+
return true;
|
|
6536
|
+
} catch (error) {
|
|
6537
|
+
logger.warn({
|
|
6538
|
+
src: "setup-credentials",
|
|
6539
|
+
channelId,
|
|
6540
|
+
messageId,
|
|
6541
|
+
fieldName,
|
|
6542
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6543
|
+
}, "Could not delete Discord message containing a credential");
|
|
6544
|
+
return false;
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
var TRIGGER_PATTERNS = [
|
|
6548
|
+
/\bsetup\s+(github|vercel|cloudflare|anthropic|openai|fal|credentials?)\b/i,
|
|
6549
|
+
/\badd\s+(my\s+)?(api\s+)?key\b/i,
|
|
6550
|
+
/\badd\s+credentials?\b/i,
|
|
6551
|
+
/\bconfigure\s+(github|vercel|cloudflare|anthropic|openai|fal)\b/i,
|
|
6552
|
+
/\bconnect\s+(github|vercel|cloudflare|anthropic|openai|fal)\b/i,
|
|
6553
|
+
/^\/setup\b/i
|
|
6554
|
+
];
|
|
6555
|
+
function detectSetupIntent(text) {
|
|
6556
|
+
const lower = text.toLowerCase().trim();
|
|
6557
|
+
for (const presetName of presets.keys()) {
|
|
6558
|
+
if (presetName === "generic") {
|
|
6559
|
+
continue;
|
|
6560
|
+
}
|
|
6561
|
+
const pattern = new RegExp(`\\b(setup|configure|connect|add)\\s+(my\\s+)?${escapeRegex(presetName)}\\b`, "i");
|
|
6562
|
+
if (pattern.test(lower)) {
|
|
6563
|
+
return presetName;
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
const slashMatch = lower.match(/^\/setup\s+(\w+)/);
|
|
6567
|
+
if (slashMatch) {
|
|
6568
|
+
const service = slashMatch[1].toLowerCase();
|
|
6569
|
+
if (presets.has(service)) {
|
|
6570
|
+
return service;
|
|
6571
|
+
}
|
|
6572
|
+
if (service === "custom") {
|
|
6573
|
+
return "generic";
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
for (const pattern of TRIGGER_PATTERNS) {
|
|
6577
|
+
if (pattern.test(lower)) {
|
|
6578
|
+
return null;
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6583
|
+
function isSetupTrigger(text) {
|
|
6584
|
+
return TRIGGER_PATTERNS.some((pattern) => pattern.test(text.toLowerCase().trim()));
|
|
6585
|
+
}
|
|
6586
|
+
function buildServiceListMessage() {
|
|
6587
|
+
const services = listPresets().filter((presetName) => presetName !== "generic").map((presetName) => {
|
|
6588
|
+
const preset = getPreset(presetName);
|
|
6589
|
+
return `- **${preset?.displayName ?? presetName}** (\`${presetName}\`)`;
|
|
6590
|
+
});
|
|
6591
|
+
return [
|
|
6592
|
+
"Which service do you want to set up? Here's what I support:",
|
|
6593
|
+
"",
|
|
6594
|
+
...services,
|
|
6595
|
+
"- **Custom** (`custom`) - any env var",
|
|
6596
|
+
"",
|
|
6597
|
+
"Just tell me the name, for example `github` or `custom`."
|
|
6598
|
+
].join(`
|
|
6599
|
+
`);
|
|
6600
|
+
}
|
|
6601
|
+
function resolveDeletionTarget(message, defaultChannelId) {
|
|
6602
|
+
const contentRecord = message.content && typeof message.content === "object" ? message.content : null;
|
|
6603
|
+
const metadataRecord = message.metadata && typeof message.metadata === "object" ? message.metadata : null;
|
|
6604
|
+
const channelId = typeof contentRecord?.channelId === "string" && contentRecord.channelId || typeof metadataRecord?.discordChannelId === "string" && metadataRecord.discordChannelId || defaultChannelId;
|
|
6605
|
+
const messageId = typeof contentRecord?.messageId === "string" && contentRecord.messageId || typeof metadataRecord?.discordMessageId === "string" && metadataRecord.discordMessageId;
|
|
6606
|
+
return { channelId, messageId };
|
|
6607
|
+
}
|
|
6608
|
+
var setupCredentials = {
|
|
6609
|
+
name: "SETUP_CREDENTIALS",
|
|
6610
|
+
similes: [
|
|
6611
|
+
"ADD_CREDENTIALS",
|
|
6612
|
+
"CONFIGURE_SERVICE",
|
|
6613
|
+
"CONNECT_SERVICE",
|
|
6614
|
+
"ADD_API_KEY",
|
|
6615
|
+
"SETUP_SERVICE"
|
|
6616
|
+
],
|
|
6617
|
+
description: "Guide the user through setting up API credentials for supported third-party services, validate them when possible, and store them securely.",
|
|
6618
|
+
validate: async (_runtime, message) => {
|
|
6619
|
+
if (message.content.source !== "discord") {
|
|
6620
|
+
return false;
|
|
6621
|
+
}
|
|
6622
|
+
const text = message.content.text?.trim() ?? "";
|
|
6623
|
+
const userId = message.entityId;
|
|
6624
|
+
return activeSessions.has(userId) || isSetupTrigger(text);
|
|
6625
|
+
},
|
|
6626
|
+
handler: async (runtime, message, state, _options, callback) => {
|
|
6627
|
+
const discordService = runtime.getService(DISCORD_SERVICE_NAME);
|
|
6628
|
+
if (!discordService?.client) {
|
|
6629
|
+
if (callback) {
|
|
6630
|
+
await callback({
|
|
6631
|
+
text: "Discord service isn't available right now.",
|
|
6632
|
+
source: "discord"
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
return { success: false, error: "Discord service unavailable" };
|
|
6636
|
+
}
|
|
6637
|
+
const text = message.content.text?.trim() ?? "";
|
|
6638
|
+
const userId = message.entityId;
|
|
6639
|
+
const room = state?.data?.room || await runtime.getRoom(message.roomId);
|
|
6640
|
+
const channelId = room?.channelId || message.roomId;
|
|
6641
|
+
cleanExpiredSessions();
|
|
6642
|
+
let isDm = false;
|
|
6643
|
+
try {
|
|
6644
|
+
const channel = await discordService.client.channels.fetch(channelId);
|
|
6645
|
+
isDm = channel?.type === ChannelType2.DM || channel?.type === ChannelType2.GroupDM;
|
|
6646
|
+
} catch {
|
|
6647
|
+
isDm = false;
|
|
6648
|
+
}
|
|
6649
|
+
if (!isDm && !activeSessions.has(userId)) {
|
|
6650
|
+
if (callback) {
|
|
6651
|
+
await callback({
|
|
6652
|
+
text: "Let's do this in DMs for security. I'll message you there.",
|
|
6653
|
+
source: "discord"
|
|
6654
|
+
});
|
|
6655
|
+
}
|
|
6656
|
+
try {
|
|
6657
|
+
const discordUser = await discordService.client.users.fetch(userId);
|
|
6658
|
+
const dmChannel = await discordUser.createDM();
|
|
6659
|
+
const detectedService2 = detectSetupIntent(text);
|
|
6660
|
+
if (detectedService2 && presets.has(detectedService2)) {
|
|
6661
|
+
const preset = presets.get(detectedService2);
|
|
6662
|
+
if (!preset) {
|
|
6663
|
+
await dmChannel.send(buildServiceListMessage());
|
|
6664
|
+
return {
|
|
6665
|
+
success: false,
|
|
6666
|
+
error: `Unsupported credential preset: ${detectedService2}`
|
|
6667
|
+
};
|
|
6668
|
+
}
|
|
6669
|
+
activeSessions.set(userId, {
|
|
6670
|
+
preset,
|
|
6671
|
+
currentFieldIndex: 0,
|
|
6672
|
+
collected: {},
|
|
6673
|
+
channelId: dmChannel.id,
|
|
6674
|
+
startedAt: Date.now()
|
|
6675
|
+
});
|
|
6676
|
+
const firstField = preset.fields[0];
|
|
6677
|
+
const helpLine = preset.helpUrl ? `Here's where to get one: ${preset.helpUrl}` : "";
|
|
6678
|
+
await dmChannel.send([
|
|
6679
|
+
`Setting up **${preset.displayName}** credentials.`,
|
|
6680
|
+
preset.helpText,
|
|
6681
|
+
helpLine,
|
|
6682
|
+
"",
|
|
6683
|
+
`Please paste your **${firstField.label}** here. ${firstField.secret ? "I'll delete your message right after reading it." : ""}`
|
|
6684
|
+
].filter(Boolean).join(`
|
|
6685
|
+
`));
|
|
6686
|
+
} else {
|
|
6687
|
+
await dmChannel.send(buildServiceListMessage());
|
|
6688
|
+
}
|
|
6689
|
+
} catch (error) {
|
|
6690
|
+
logger.warn({
|
|
6691
|
+
src: "setup-credentials",
|
|
6692
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6693
|
+
}, "Could not open DM with user");
|
|
6694
|
+
if (callback) {
|
|
6695
|
+
await callback({
|
|
6696
|
+
text: "I couldn't send you a DM. Make sure your DMs are open, then try again.",
|
|
6697
|
+
source: "discord"
|
|
6698
|
+
});
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
return { success: true, text: "Redirected credential setup to DMs" };
|
|
6702
|
+
}
|
|
6703
|
+
if (activeSessions.has(userId)) {
|
|
6704
|
+
const session = activeSessions.get(userId);
|
|
6705
|
+
if (!session) {
|
|
6706
|
+
return { success: false, error: "Credential session not found" };
|
|
6707
|
+
}
|
|
6708
|
+
const currentField = session.preset.fields[session.currentFieldIndex];
|
|
6709
|
+
if (currentField.secret) {
|
|
6710
|
+
const deletionTarget = resolveDeletionTarget(message, channelId);
|
|
6711
|
+
if (deletionTarget.messageId) {
|
|
6712
|
+
const deleted = await tryDeleteMessage(discordService, deletionTarget.channelId, deletionTarget.messageId, currentField.label);
|
|
6713
|
+
if (!deleted) {
|
|
6714
|
+
logger.warn({
|
|
6715
|
+
src: "setup-credentials",
|
|
6716
|
+
fieldName: currentField.label,
|
|
6717
|
+
channelId: deletionTarget.channelId,
|
|
6718
|
+
messageId: deletionTarget.messageId
|
|
6719
|
+
}, "Credential message could not be deleted automatically");
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
session.collected[currentField.key] = text;
|
|
6724
|
+
session.currentFieldIndex += 1;
|
|
6725
|
+
if (session.currentFieldIndex < session.preset.fields.length) {
|
|
6726
|
+
const nextField = session.preset.fields[session.currentFieldIndex];
|
|
6727
|
+
if (callback) {
|
|
6728
|
+
await callback({
|
|
6729
|
+
text: `Got it. Now paste your **${nextField.label}**${nextField.secret ? " (I'll delete your message)" : ""}.`,
|
|
6730
|
+
source: "discord"
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
return { success: true, text: "Collecting next credential field" };
|
|
6734
|
+
}
|
|
6735
|
+
if (callback) {
|
|
6736
|
+
await callback({
|
|
6737
|
+
text: "Validating your credentials...",
|
|
6738
|
+
source: "discord"
|
|
6739
|
+
});
|
|
6740
|
+
}
|
|
6741
|
+
const validation = await session.preset.validate(session.collected);
|
|
6742
|
+
if (validation.valid) {
|
|
6743
|
+
const storageKey = session.preset.name === "generic" ? (session.collected.envName ?? "custom").toLowerCase().replace(/[^a-z0-9_-]/g, "_") : session.preset.name;
|
|
6744
|
+
await storeCredentials(storageKey, session.collected);
|
|
6745
|
+
activeSessions.delete(userId);
|
|
6746
|
+
if (callback) {
|
|
6747
|
+
await callback({
|
|
6748
|
+
text: `Connected${validation.identity ? ` as ${validation.identity}` : ""}. **${session.preset.displayName}** is ready.`,
|
|
6749
|
+
source: "discord"
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
return { success: true, text: "Credentials stored" };
|
|
6753
|
+
}
|
|
6754
|
+
activeSessions.delete(userId);
|
|
6755
|
+
if (callback) {
|
|
6756
|
+
await callback({
|
|
6757
|
+
text: `Validation failed: ${validation.error ?? "unknown error"}. Please check your credentials and try again with \`/setup ${session.preset.name}\`.`,
|
|
6758
|
+
source: "discord"
|
|
6759
|
+
});
|
|
6760
|
+
}
|
|
6761
|
+
return {
|
|
6762
|
+
success: false,
|
|
6763
|
+
error: validation.error ?? "Validation failed"
|
|
6764
|
+
};
|
|
6765
|
+
}
|
|
6766
|
+
const detectedService = detectSetupIntent(text);
|
|
6767
|
+
if (detectedService && presets.has(detectedService)) {
|
|
6768
|
+
const preset = presets.get(detectedService);
|
|
6769
|
+
if (!preset) {
|
|
6770
|
+
return {
|
|
6771
|
+
success: false,
|
|
6772
|
+
error: `Unsupported credential preset: ${detectedService}`
|
|
6773
|
+
};
|
|
6774
|
+
}
|
|
6775
|
+
activeSessions.set(userId, {
|
|
6776
|
+
preset,
|
|
6777
|
+
currentFieldIndex: 0,
|
|
6778
|
+
collected: {},
|
|
6779
|
+
channelId,
|
|
6780
|
+
startedAt: Date.now()
|
|
6781
|
+
});
|
|
6782
|
+
const firstField = preset.fields[0];
|
|
6783
|
+
const helpLine = preset.helpUrl ? `Here's where to get one: ${preset.helpUrl}` : "";
|
|
6784
|
+
if (callback) {
|
|
6785
|
+
await callback({
|
|
6786
|
+
text: [
|
|
6787
|
+
`Setting up **${preset.displayName}** credentials.`,
|
|
6788
|
+
preset.helpText,
|
|
6789
|
+
helpLine,
|
|
6790
|
+
"",
|
|
6791
|
+
`Please paste your **${firstField.label}** here. ${firstField.secret ? "I'll delete your message right after reading it." : ""}`
|
|
6792
|
+
].filter(Boolean).join(`
|
|
6793
|
+
`),
|
|
6794
|
+
source: "discord"
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
return { success: true, text: `Started ${preset.displayName} setup` };
|
|
6798
|
+
}
|
|
6799
|
+
const serviceName = text.toLowerCase().trim();
|
|
6800
|
+
if (presets.has(serviceName) || serviceName === "custom") {
|
|
6801
|
+
const presetKey = serviceName === "custom" ? "generic" : serviceName;
|
|
6802
|
+
const preset = presets.get(presetKey);
|
|
6803
|
+
if (!preset) {
|
|
6804
|
+
return {
|
|
6805
|
+
success: false,
|
|
6806
|
+
error: `Unsupported credential preset: ${presetKey}`
|
|
6807
|
+
};
|
|
6808
|
+
}
|
|
6809
|
+
activeSessions.set(userId, {
|
|
6810
|
+
preset,
|
|
6811
|
+
currentFieldIndex: 0,
|
|
6812
|
+
collected: {},
|
|
6813
|
+
channelId,
|
|
6814
|
+
startedAt: Date.now()
|
|
6815
|
+
});
|
|
6816
|
+
const firstField = preset.fields[0];
|
|
6817
|
+
const helpLine = preset.helpUrl ? `Here's where to get one: ${preset.helpUrl}` : "";
|
|
6818
|
+
if (callback) {
|
|
6819
|
+
await callback({
|
|
6820
|
+
text: [
|
|
6821
|
+
`Setting up **${preset.displayName}** credentials.`,
|
|
6822
|
+
preset.helpText,
|
|
6823
|
+
helpLine,
|
|
6824
|
+
"",
|
|
6825
|
+
`Please paste your **${firstField.label}** here. ${firstField.secret ? "I'll delete your message right after reading it." : ""}`
|
|
6826
|
+
].filter(Boolean).join(`
|
|
6827
|
+
`),
|
|
6828
|
+
source: "discord"
|
|
6829
|
+
});
|
|
6830
|
+
}
|
|
6831
|
+
return { success: true, text: `Started ${preset.displayName} setup` };
|
|
6832
|
+
}
|
|
6833
|
+
if (callback) {
|
|
6834
|
+
await callback({
|
|
6835
|
+
text: buildServiceListMessage(),
|
|
6836
|
+
source: "discord"
|
|
6837
|
+
});
|
|
6838
|
+
}
|
|
6839
|
+
return { success: true, text: "Showed credential setup service list" };
|
|
6840
|
+
},
|
|
6841
|
+
examples: [
|
|
6842
|
+
[
|
|
6843
|
+
{
|
|
6844
|
+
name: "{{user1}}",
|
|
6845
|
+
content: { text: "setup github" }
|
|
6846
|
+
},
|
|
6847
|
+
{
|
|
6848
|
+
name: "{{agentName}}",
|
|
6849
|
+
content: {
|
|
6850
|
+
text: `Setting up **GitHub** credentials.
|
|
6851
|
+
Create a fine-grained PAT at the link above.
|
|
6852
|
+
Here's where to get one: https://github.com/settings/tokens
|
|
6853
|
+
|
|
6854
|
+
Please paste your **Personal Access Token** here. I'll delete your message right after reading it.`,
|
|
6855
|
+
action: "SETUP_CREDENTIALS"
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
],
|
|
6859
|
+
[
|
|
6860
|
+
{
|
|
6861
|
+
name: "{{user1}}",
|
|
6862
|
+
content: { text: "add my vercel key" }
|
|
6863
|
+
},
|
|
6864
|
+
{
|
|
6865
|
+
name: "{{agentName}}",
|
|
6866
|
+
content: {
|
|
6867
|
+
text: `Setting up **Vercel** credentials.
|
|
6868
|
+
Create a token at the link above.
|
|
6869
|
+
Here's where to get one: https://vercel.com/account/tokens
|
|
6870
|
+
|
|
6871
|
+
Please paste your **API Token** here. I'll delete your message right after reading it.`,
|
|
6872
|
+
action: "SETUP_CREDENTIALS"
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
],
|
|
6876
|
+
[
|
|
6877
|
+
{
|
|
6878
|
+
name: "{{user1}}",
|
|
6879
|
+
content: { text: "/setup" }
|
|
6880
|
+
},
|
|
6881
|
+
{
|
|
6882
|
+
name: "{{agentName}}",
|
|
6883
|
+
content: {
|
|
6884
|
+
text: "Which service do you want to set up? I support GitHub, Vercel, Cloudflare, Anthropic, OpenAI, fal.ai, or a custom credential.",
|
|
6885
|
+
action: "SETUP_CREDENTIALS"
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
]
|
|
6889
|
+
]
|
|
6890
|
+
};
|
|
6891
|
+
var setup_credentials_default = setupCredentials;
|
|
6892
|
+
|
|
6253
6893
|
// actions/summarizeConversation.ts
|
|
6254
|
-
import
|
|
6894
|
+
import fs3 from "node:fs";
|
|
6255
6895
|
import {
|
|
6256
6896
|
ContentType as ContentType3,
|
|
6257
6897
|
composePromptFromState as composePromptFromState15,
|
|
6258
6898
|
getEntityDetails,
|
|
6259
|
-
logger,
|
|
6899
|
+
logger as logger2,
|
|
6260
6900
|
MemoryType as MemoryType5,
|
|
6261
6901
|
ModelType as ModelType15,
|
|
6262
6902
|
parseJSONObjectFromText as parseJSONObjectFromText15,
|
|
@@ -6302,7 +6942,7 @@ function parseTimeToTimestamp(input) {
|
|
|
6302
6942
|
const milliseconds = value * (multipliers[unit] || 0);
|
|
6303
6943
|
return Date.now() - milliseconds;
|
|
6304
6944
|
}
|
|
6305
|
-
|
|
6945
|
+
logger2.warn(`[parseTimeToTimestamp] Could not parse time value, using current time: ${input}`);
|
|
6306
6946
|
return Date.now();
|
|
6307
6947
|
}
|
|
6308
6948
|
var getDateRange = async (runtime, _message, state) => {
|
|
@@ -6320,7 +6960,7 @@ var getDateRange = async (runtime, _message, state) => {
|
|
|
6320
6960
|
const startRaw = parseTimeToTimestamp(parsedResponse.start);
|
|
6321
6961
|
const endRaw = parseTimeToTimestamp(parsedResponse.end);
|
|
6322
6962
|
if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) {
|
|
6323
|
-
|
|
6963
|
+
logger2.warn(`[getDateRange] Invalid timestamps parsed: start=${startRaw}, end=${endRaw}, retrying...`);
|
|
6324
6964
|
continue;
|
|
6325
6965
|
}
|
|
6326
6966
|
let start = startRaw <= endRaw ? startRaw : endRaw;
|
|
@@ -6533,8 +7173,8 @@ ${currentSummary.trim()}
|
|
|
6533
7173
|
const summaryDir = "cache";
|
|
6534
7174
|
const summaryFilename = `${summaryDir}/conversation_summary_${Date.now()}`;
|
|
6535
7175
|
await runtime.setCache(summaryFilename, currentSummary);
|
|
6536
|
-
await
|
|
6537
|
-
await
|
|
7176
|
+
await fs3.promises.mkdir(summaryDir, { recursive: true });
|
|
7177
|
+
await fs3.promises.writeFile(summaryFilename, currentSummary, "utf8");
|
|
6538
7178
|
if (callback) {
|
|
6539
7179
|
await callback?.({
|
|
6540
7180
|
...callbackData,
|
|
@@ -7156,7 +7796,7 @@ function printBanner(options) {
|
|
|
7156
7796
|
}
|
|
7157
7797
|
|
|
7158
7798
|
// providers/channelState.ts
|
|
7159
|
-
import { ChannelType as
|
|
7799
|
+
import { ChannelType as ChannelType3 } from "@elizaos/core";
|
|
7160
7800
|
|
|
7161
7801
|
// types.ts
|
|
7162
7802
|
var DiscordEventTypes;
|
|
@@ -7186,6 +7826,39 @@ var ServiceType2 = {
|
|
|
7186
7826
|
|
|
7187
7827
|
// providers/channelState.ts
|
|
7188
7828
|
var spec18 = requireProviderSpec("channelState");
|
|
7829
|
+
function asRecord(value) {
|
|
7830
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7831
|
+
}
|
|
7832
|
+
function readString(value) {
|
|
7833
|
+
if (typeof value !== "string") {
|
|
7834
|
+
return;
|
|
7835
|
+
}
|
|
7836
|
+
const trimmed = value.trim();
|
|
7837
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
7838
|
+
}
|
|
7839
|
+
function normalizeName(value) {
|
|
7840
|
+
return value.trim().toLowerCase();
|
|
7841
|
+
}
|
|
7842
|
+
function formatDiscordIdentity(displayName, userName) {
|
|
7843
|
+
if (displayName && userName && normalizeName(displayName) !== normalizeName(userName)) {
|
|
7844
|
+
return `${displayName} (discord username: ${userName})`;
|
|
7845
|
+
}
|
|
7846
|
+
return displayName ?? userName ?? "someone";
|
|
7847
|
+
}
|
|
7848
|
+
function describeCurrentSpeaker(message, fallback) {
|
|
7849
|
+
const metadata = asRecord(message.metadata);
|
|
7850
|
+
const discordMetadata = asRecord(metadata?.discord);
|
|
7851
|
+
const displayName = readString(metadata?.entityName) ?? readString(metadata?.displayName) ?? readString(discordMetadata?.name) ?? readString(discordMetadata?.globalName) ?? readString(fallback);
|
|
7852
|
+
const userName = readString(metadata?.entityUserName) ?? readString(discordMetadata?.userName) ?? readString(discordMetadata?.username);
|
|
7853
|
+
return formatDiscordIdentity(displayName, userName);
|
|
7854
|
+
}
|
|
7855
|
+
function describeAgentDiscordAccount(agentName, userName) {
|
|
7856
|
+
const candidate = readString(userName);
|
|
7857
|
+
if (!candidate || normalizeName(candidate) === normalizeName(agentName)) {
|
|
7858
|
+
return agentName;
|
|
7859
|
+
}
|
|
7860
|
+
return `${agentName} (discord username: ${candidate})`;
|
|
7861
|
+
}
|
|
7189
7862
|
var channelStateProvider = {
|
|
7190
7863
|
name: spec18.name,
|
|
7191
7864
|
dynamic: true,
|
|
@@ -7201,15 +7874,17 @@ var channelStateProvider = {
|
|
|
7201
7874
|
text: ""
|
|
7202
7875
|
};
|
|
7203
7876
|
}
|
|
7204
|
-
const
|
|
7205
|
-
const
|
|
7877
|
+
const stateRecord = state;
|
|
7878
|
+
const agentName = readString(stateRecord?.agentName) ?? "The agent";
|
|
7879
|
+
const senderName = readString(stateRecord?.senderName) ?? "someone";
|
|
7880
|
+
const senderIdentity = describeCurrentSpeaker(message, senderName);
|
|
7206
7881
|
let responseText = "";
|
|
7207
7882
|
let channelType = "";
|
|
7208
7883
|
let serverName = "";
|
|
7209
7884
|
const channelId = room.channelId ?? "";
|
|
7210
|
-
if (room.type ===
|
|
7885
|
+
if (room.type === ChannelType3.DM) {
|
|
7211
7886
|
channelType = "DM";
|
|
7212
|
-
responseText = `${agentName} is currently in a direct message conversation with ${
|
|
7887
|
+
responseText = `${agentName} is currently in a direct message conversation with ${senderIdentity}. ${agentName} should engage in conversation, should respond to messages that are addressed to them and only ignore messages that seem to not require a response.`;
|
|
7213
7888
|
} else {
|
|
7214
7889
|
channelType = "GROUP";
|
|
7215
7890
|
if (!channelId) {
|
|
@@ -7283,8 +7958,15 @@ var channelStateProvider = {
|
|
|
7283
7958
|
};
|
|
7284
7959
|
}
|
|
7285
7960
|
serverName = guild.name;
|
|
7961
|
+
const agentIdentity = describeAgentDiscordAccount(agentName, discordService.client?.user?.username);
|
|
7286
7962
|
responseText = `${agentName} is currently having a conversation in the channel \`#${channel?.name || channelId}\` in the server \`${serverName}\``;
|
|
7287
7963
|
responseText += `
|
|
7964
|
+
The current speaker is ${senderIdentity}.`;
|
|
7965
|
+
if (agentIdentity !== agentName) {
|
|
7966
|
+
responseText += `
|
|
7967
|
+
On Discord, ${agentName} is logged in as ${agentIdentity}.`;
|
|
7968
|
+
}
|
|
7969
|
+
responseText += `
|
|
7288
7970
|
${agentName} is in a room with other users and should be self-conscious and only participate when directly addressed or when the conversation is relevant to them.`;
|
|
7289
7971
|
}
|
|
7290
7972
|
return {
|
|
@@ -7453,8 +8135,7 @@ function formatGuildInfoText(guild, info) {
|
|
|
7453
8135
|
}
|
|
7454
8136
|
|
|
7455
8137
|
// providers/voiceState.ts
|
|
7456
|
-
import {
|
|
7457
|
-
import { ChannelType as ChannelType3 } from "@elizaos/core";
|
|
8138
|
+
import { ChannelType as ChannelType4 } from "@elizaos/core";
|
|
7458
8139
|
var spec20 = requireProviderSpec("voiceState");
|
|
7459
8140
|
var voiceStateProvider = {
|
|
7460
8141
|
name: spec20.name,
|
|
@@ -7464,7 +8145,7 @@ var voiceStateProvider = {
|
|
|
7464
8145
|
if (!room) {
|
|
7465
8146
|
throw new Error("No room found");
|
|
7466
8147
|
}
|
|
7467
|
-
if (room.type !==
|
|
8148
|
+
if (room.type !== ChannelType4.GROUP) {
|
|
7468
8149
|
return {
|
|
7469
8150
|
data: {
|
|
7470
8151
|
isInVoiceChannel: false,
|
|
@@ -7533,7 +8214,7 @@ var voiceStateProvider = {
|
|
|
7533
8214
|
text: `${agentName} is not currently in a voice channel`
|
|
7534
8215
|
};
|
|
7535
8216
|
}
|
|
7536
|
-
const connection = getVoiceConnection(guildId);
|
|
8217
|
+
const connection = discordService.voiceManager?.getVoiceConnection(guildId);
|
|
7537
8218
|
if (!connection) {
|
|
7538
8219
|
return {
|
|
7539
8220
|
data: {
|
|
@@ -7576,17 +8257,21 @@ var voiceStateProvider = {
|
|
|
7576
8257
|
|
|
7577
8258
|
// service.ts
|
|
7578
8259
|
import {
|
|
7579
|
-
ChannelType as
|
|
7580
|
-
createUniqueUuid as
|
|
8260
|
+
ChannelType as ChannelType8,
|
|
8261
|
+
createUniqueUuid as createUniqueUuid7,
|
|
7581
8262
|
EventType as EventType3,
|
|
7582
8263
|
MemoryType as MemoryType7,
|
|
7583
8264
|
Service,
|
|
7584
|
-
stringToUuid as
|
|
8265
|
+
stringToUuid as stringToUuid4
|
|
7585
8266
|
} from "@elizaos/core";
|
|
8267
|
+
import {
|
|
8268
|
+
getConnectorAdminWhitelist,
|
|
8269
|
+
setConnectorAdminWhitelist
|
|
8270
|
+
} from "@elizaos/core/roles";
|
|
7586
8271
|
import {
|
|
7587
8272
|
AttachmentBuilder as AttachmentBuilder2,
|
|
7588
8273
|
AuditLogEvent,
|
|
7589
|
-
ChannelType as
|
|
8274
|
+
ChannelType as DiscordChannelType6,
|
|
7590
8275
|
Client as DiscordJsClient,
|
|
7591
8276
|
Events,
|
|
7592
8277
|
GatewayIntentBits,
|
|
@@ -7625,6 +8310,171 @@ function createCompatRuntime(runtime) {
|
|
|
7625
8310
|
});
|
|
7626
8311
|
}
|
|
7627
8312
|
|
|
8313
|
+
// debouncer.ts
|
|
8314
|
+
var DEFAULT_DEBOUNCE_MS = 400;
|
|
8315
|
+
var DEFAULT_CHANNEL_DEBOUNCE_MS = 3000;
|
|
8316
|
+
function escapeRegex2(value) {
|
|
8317
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8318
|
+
}
|
|
8319
|
+
function runSafely(callback) {
|
|
8320
|
+
try {
|
|
8321
|
+
callback();
|
|
8322
|
+
} catch {}
|
|
8323
|
+
}
|
|
8324
|
+
function createChannelDebouncer(onFlush, options = {}) {
|
|
8325
|
+
const debounceMs = options.debounceMs ?? DEFAULT_CHANNEL_DEBOUNCE_MS;
|
|
8326
|
+
const responseCooldownMs = options.responseCooldownMs ?? 30000;
|
|
8327
|
+
const botName = options.botName?.trim();
|
|
8328
|
+
const botNameRegex = botName && botName.length >= 2 ? new RegExp(`(^|[^\\p{L}\\p{N}])${escapeRegex2(botName)}(?=$|[^\\p{L}\\p{N}])`, "iu") : null;
|
|
8329
|
+
const pending = new Map;
|
|
8330
|
+
const lastResponseTime = new Map;
|
|
8331
|
+
const isBotTargeted = (message) => {
|
|
8332
|
+
const botId = options.getBotUserId?.() ?? options.botUserId;
|
|
8333
|
+
if (botId && message.mentions?.users?.has(botId)) {
|
|
8334
|
+
return true;
|
|
8335
|
+
}
|
|
8336
|
+
if (botId && message.reference?.messageId && message.mentions?.repliedUser?.id === botId) {
|
|
8337
|
+
return true;
|
|
8338
|
+
}
|
|
8339
|
+
return Boolean(botNameRegex?.test(message.content ?? ""));
|
|
8340
|
+
};
|
|
8341
|
+
const isInCooldown = (channelId) => {
|
|
8342
|
+
const lastRespondedAt = lastResponseTime.get(channelId);
|
|
8343
|
+
if (!lastRespondedAt) {
|
|
8344
|
+
return false;
|
|
8345
|
+
}
|
|
8346
|
+
if (Date.now() - lastRespondedAt >= responseCooldownMs) {
|
|
8347
|
+
lastResponseTime.delete(channelId);
|
|
8348
|
+
return false;
|
|
8349
|
+
}
|
|
8350
|
+
return true;
|
|
8351
|
+
};
|
|
8352
|
+
const flush = (channelId) => {
|
|
8353
|
+
const entry = pending.get(channelId);
|
|
8354
|
+
if (!entry) {
|
|
8355
|
+
return;
|
|
8356
|
+
}
|
|
8357
|
+
clearTimeout(entry.timer);
|
|
8358
|
+
pending.delete(channelId);
|
|
8359
|
+
if (entry.messages.length > 0) {
|
|
8360
|
+
runSafely(() => onFlush(entry.messages));
|
|
8361
|
+
}
|
|
8362
|
+
};
|
|
8363
|
+
const enqueue = (message) => {
|
|
8364
|
+
const channelId = message.channel.id;
|
|
8365
|
+
if (isBotTargeted(message)) {
|
|
8366
|
+
const entry = pending.get(channelId);
|
|
8367
|
+
if (entry) {
|
|
8368
|
+
clearTimeout(entry.timer);
|
|
8369
|
+
pending.delete(channelId);
|
|
8370
|
+
entry.messages.push(message);
|
|
8371
|
+
runSafely(() => onFlush(entry.messages));
|
|
8372
|
+
} else {
|
|
8373
|
+
runSafely(() => onFlush([message]));
|
|
8374
|
+
}
|
|
8375
|
+
return;
|
|
8376
|
+
}
|
|
8377
|
+
if (isInCooldown(channelId)) {
|
|
8378
|
+
return;
|
|
8379
|
+
}
|
|
8380
|
+
if (debounceMs <= 0) {
|
|
8381
|
+
runSafely(() => onFlush([message]));
|
|
8382
|
+
return;
|
|
8383
|
+
}
|
|
8384
|
+
const existing = pending.get(channelId);
|
|
8385
|
+
if (existing) {
|
|
8386
|
+
clearTimeout(existing.timer);
|
|
8387
|
+
existing.messages.push(message);
|
|
8388
|
+
existing.timer = setTimeout(() => flush(channelId), debounceMs);
|
|
8389
|
+
return;
|
|
8390
|
+
}
|
|
8391
|
+
pending.set(channelId, {
|
|
8392
|
+
messages: [message],
|
|
8393
|
+
timer: setTimeout(() => flush(channelId), debounceMs)
|
|
8394
|
+
});
|
|
8395
|
+
};
|
|
8396
|
+
return {
|
|
8397
|
+
enqueue,
|
|
8398
|
+
markResponded: (channelId) => {
|
|
8399
|
+
lastResponseTime.set(channelId, Date.now());
|
|
8400
|
+
},
|
|
8401
|
+
flushAll: () => {
|
|
8402
|
+
for (const key of [...pending.keys()]) {
|
|
8403
|
+
flush(key);
|
|
8404
|
+
}
|
|
8405
|
+
},
|
|
8406
|
+
pendingCount: () => pending.size,
|
|
8407
|
+
destroy: () => {
|
|
8408
|
+
for (const [, entry] of pending) {
|
|
8409
|
+
clearTimeout(entry.timer);
|
|
8410
|
+
}
|
|
8411
|
+
pending.clear();
|
|
8412
|
+
lastResponseTime.clear();
|
|
8413
|
+
}
|
|
8414
|
+
};
|
|
8415
|
+
}
|
|
8416
|
+
function createMessageDebouncer(onFlush, debounceMs = DEFAULT_DEBOUNCE_MS) {
|
|
8417
|
+
const pending = new Map;
|
|
8418
|
+
const makeKey = (message) => `${message.channel.id}:${message.author.id}`;
|
|
8419
|
+
const flush = (key) => {
|
|
8420
|
+
const entry = pending.get(key);
|
|
8421
|
+
if (!entry) {
|
|
8422
|
+
return;
|
|
8423
|
+
}
|
|
8424
|
+
clearTimeout(entry.timer);
|
|
8425
|
+
pending.delete(key);
|
|
8426
|
+
if (entry.messages.length > 0) {
|
|
8427
|
+
runSafely(() => onFlush(entry.messages));
|
|
8428
|
+
}
|
|
8429
|
+
};
|
|
8430
|
+
const hasMedia = (message) => (message.attachments?.size ?? 0) > 0 || (message.stickers?.size ?? 0) > 0;
|
|
8431
|
+
const enqueue = (message) => {
|
|
8432
|
+
if (debounceMs <= 0) {
|
|
8433
|
+
runSafely(() => onFlush([message]));
|
|
8434
|
+
return;
|
|
8435
|
+
}
|
|
8436
|
+
const key = makeKey(message);
|
|
8437
|
+
if (hasMedia(message)) {
|
|
8438
|
+
const entry = pending.get(key);
|
|
8439
|
+
if (entry) {
|
|
8440
|
+
clearTimeout(entry.timer);
|
|
8441
|
+
pending.delete(key);
|
|
8442
|
+
if (entry.messages.length > 0) {
|
|
8443
|
+
runSafely(() => onFlush(entry.messages));
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
runSafely(() => onFlush([message]));
|
|
8447
|
+
return;
|
|
8448
|
+
}
|
|
8449
|
+
const existing = pending.get(key);
|
|
8450
|
+
if (existing) {
|
|
8451
|
+
clearTimeout(existing.timer);
|
|
8452
|
+
existing.messages.push(message);
|
|
8453
|
+
existing.timer = setTimeout(() => flush(key), debounceMs);
|
|
8454
|
+
return;
|
|
8455
|
+
}
|
|
8456
|
+
pending.set(key, {
|
|
8457
|
+
messages: [message],
|
|
8458
|
+
timer: setTimeout(() => flush(key), debounceMs)
|
|
8459
|
+
});
|
|
8460
|
+
};
|
|
8461
|
+
return {
|
|
8462
|
+
enqueue,
|
|
8463
|
+
flushAll: () => {
|
|
8464
|
+
for (const key of [...pending.keys()]) {
|
|
8465
|
+
flush(key);
|
|
8466
|
+
}
|
|
8467
|
+
},
|
|
8468
|
+
pendingCount: () => pending.size,
|
|
8469
|
+
destroy: () => {
|
|
8470
|
+
for (const [, entry] of pending) {
|
|
8471
|
+
clearTimeout(entry.timer);
|
|
8472
|
+
}
|
|
8473
|
+
pending.clear();
|
|
8474
|
+
}
|
|
8475
|
+
};
|
|
8476
|
+
}
|
|
8477
|
+
|
|
7628
8478
|
// environment.ts
|
|
7629
8479
|
import { parseBooleanFromText } from "@elizaos/core";
|
|
7630
8480
|
import { z } from "zod";
|
|
@@ -7647,8 +8497,9 @@ var DISCORD_DEFAULTS = {
|
|
|
7647
8497
|
SHOULD_IGNORE_DIRECT_MESSAGES: getEnvBoolean("DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES", false),
|
|
7648
8498
|
SHOULD_RESPOND_ONLY_TO_MENTIONS: getEnvBoolean("DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS", false),
|
|
7649
8499
|
ALLOWED_CHANNEL_IDS: getEnvArray("CHANNEL_IDS", []),
|
|
7650
|
-
DM_POLICY: process.env?.DISCORD_DM_POLICY || "
|
|
7651
|
-
ALLOW_FROM: getEnvArray("DISCORD_ALLOW_FROM", [])
|
|
8500
|
+
DM_POLICY: process.env?.DISCORD_DM_POLICY || "pairing",
|
|
8501
|
+
ALLOW_FROM: getEnvArray("DISCORD_ALLOW_FROM", []),
|
|
8502
|
+
SYNC_PROFILE: getEnvBoolean("DISCORD_SYNC_PROFILE", true)
|
|
7652
8503
|
};
|
|
7653
8504
|
var discordEnvSchema = z.object({
|
|
7654
8505
|
DISCORD_API_TOKEN: z.string().min(1, "Discord API token is required"),
|
|
@@ -7681,50 +8532,128 @@ function getDiscordSettings(runtime) {
|
|
|
7681
8532
|
}
|
|
7682
8533
|
return DISCORD_DEFAULTS.DM_POLICY;
|
|
7683
8534
|
}),
|
|
7684
|
-
allowFrom: resolveSetting("DISCORD_ALLOW_FROM", characterSettings.allowFrom, DISCORD_DEFAULTS.ALLOW_FROM, (value) => value.split(",").map((s) => s.trim()).filter((s) => s.length > 0))
|
|
8535
|
+
allowFrom: resolveSetting("DISCORD_ALLOW_FROM", characterSettings.allowFrom, DISCORD_DEFAULTS.ALLOW_FROM, (value) => value.split(",").map((s) => s.trim()).filter((s) => s.length > 0)),
|
|
8536
|
+
syncProfile: resolveSetting("DISCORD_SYNC_PROFILE", characterSettings.syncProfile, DISCORD_DEFAULTS.SYNC_PROFILE, parseBooleanFromText),
|
|
8537
|
+
profileName: resolveSetting("DISCORD_PROFILE_NAME", characterSettings.profileName, undefined, (value) => value.trim()),
|
|
8538
|
+
profileAvatar: resolveSetting("DISCORD_PROFILE_AVATAR", characterSettings.profileAvatar, undefined, (value) => value.trim())
|
|
7685
8539
|
};
|
|
7686
8540
|
}
|
|
7687
8541
|
|
|
7688
8542
|
// identity.ts
|
|
7689
8543
|
import {
|
|
7690
8544
|
createUniqueUuid as createUniqueUuid3,
|
|
7691
|
-
Role
|
|
8545
|
+
Role,
|
|
8546
|
+
stringToUuid
|
|
7692
8547
|
} from "@elizaos/core";
|
|
7693
|
-
var
|
|
8548
|
+
var CANONICAL_OWNER_SETTING_KEYS = [
|
|
8549
|
+
"ELIZA_ADMIN_ENTITY_ID",
|
|
8550
|
+
"MILADY_ADMIN_ENTITY_ID"
|
|
8551
|
+
];
|
|
8552
|
+
var DISCORD_SNOWFLAKE_PATTERN = /^\d{15,20}$/;
|
|
7694
8553
|
function getCanonicalOwnerId(runtime) {
|
|
7695
|
-
const
|
|
8554
|
+
for (const key of CANONICAL_OWNER_SETTING_KEYS) {
|
|
8555
|
+
const value = runtime.getSetting?.(key);
|
|
8556
|
+
if (typeof value !== "string") {
|
|
8557
|
+
continue;
|
|
8558
|
+
}
|
|
8559
|
+
const trimmed = value.trim();
|
|
8560
|
+
if (trimmed.length > 0) {
|
|
8561
|
+
return trimmed;
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
return;
|
|
8565
|
+
}
|
|
8566
|
+
function asRecord2(value) {
|
|
8567
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8568
|
+
}
|
|
8569
|
+
function readDiscordSnowflake(value) {
|
|
7696
8570
|
if (typeof value !== "string") {
|
|
7697
|
-
return;
|
|
8571
|
+
return null;
|
|
7698
8572
|
}
|
|
7699
8573
|
const trimmed = value.trim();
|
|
7700
|
-
return trimmed
|
|
8574
|
+
return DISCORD_SNOWFLAKE_PATTERN.test(trimmed) ? trimmed : null;
|
|
7701
8575
|
}
|
|
7702
|
-
function
|
|
7703
|
-
const
|
|
7704
|
-
if (
|
|
7705
|
-
return
|
|
7706
|
-
ownership: { ownerId },
|
|
7707
|
-
roles: {
|
|
7708
|
-
[ownerId]: Role.OWNER
|
|
7709
|
-
}
|
|
7710
|
-
};
|
|
8576
|
+
function readUserIdFromOwnerLike(value) {
|
|
8577
|
+
const owner = asRecord2(value);
|
|
8578
|
+
if (!owner) {
|
|
8579
|
+
return null;
|
|
7711
8580
|
}
|
|
7712
|
-
|
|
7713
|
-
|
|
8581
|
+
return readDiscordSnowflake(owner.id) ?? readDiscordSnowflake(asRecord2(owner.user)?.id) ?? readDiscordSnowflake(owner.ownerId) ?? readDiscordSnowflake(owner.ownerUserId);
|
|
8582
|
+
}
|
|
8583
|
+
function resolveMiladyOwnerEntityId(runtime) {
|
|
8584
|
+
const configuredOwnerId = getCanonicalOwnerId(runtime);
|
|
8585
|
+
if (configuredOwnerId) {
|
|
8586
|
+
return configuredOwnerId;
|
|
8587
|
+
}
|
|
8588
|
+
const agentName = runtime.character?.name?.trim() || runtime.agentId;
|
|
8589
|
+
return stringToUuid(`${agentName}-admin-entity`);
|
|
8590
|
+
}
|
|
8591
|
+
function resolveDiscordRuntimeEntityId(runtime, userId, ownerDiscordUserIds = []) {
|
|
8592
|
+
for (const ownerUserId of ownerDiscordUserIds) {
|
|
8593
|
+
if (ownerUserId === userId) {
|
|
8594
|
+
return resolveMiladyOwnerEntityId(runtime);
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
return createUniqueUuid3(runtime, userId);
|
|
8598
|
+
}
|
|
8599
|
+
function extractDiscordOwnerUserIds(application) {
|
|
8600
|
+
const applicationRecord = asRecord2(application);
|
|
8601
|
+
if (!applicationRecord) {
|
|
8602
|
+
return [];
|
|
8603
|
+
}
|
|
8604
|
+
const ownerCandidates = new Set;
|
|
8605
|
+
const directOwnerId = readUserIdFromOwnerLike(applicationRecord.owner);
|
|
8606
|
+
if (directOwnerId) {
|
|
8607
|
+
ownerCandidates.add(directOwnerId);
|
|
8608
|
+
}
|
|
8609
|
+
const team = asRecord2(applicationRecord.team);
|
|
8610
|
+
const teamOwnerId = readDiscordSnowflake(team?.ownerId) ?? readDiscordSnowflake(team?.ownerUserId);
|
|
8611
|
+
if (teamOwnerId) {
|
|
8612
|
+
ownerCandidates.add(teamOwnerId);
|
|
7714
8613
|
}
|
|
7715
|
-
const
|
|
8614
|
+
const teamMembers = team?.members;
|
|
8615
|
+
if (Array.isArray(teamMembers)) {
|
|
8616
|
+
for (const member of teamMembers) {
|
|
8617
|
+
const memberId = readUserIdFromOwnerLike(member);
|
|
8618
|
+
if (memberId) {
|
|
8619
|
+
ownerCandidates.add(memberId);
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
return [...ownerCandidates];
|
|
8624
|
+
}
|
|
8625
|
+
function parseDiscordOwnerUserIds(value) {
|
|
8626
|
+
const rawValues = (() => {
|
|
8627
|
+
if (Array.isArray(value)) {
|
|
8628
|
+
return value;
|
|
8629
|
+
}
|
|
8630
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
8631
|
+
return [];
|
|
8632
|
+
}
|
|
8633
|
+
try {
|
|
8634
|
+
const parsed = JSON.parse(value);
|
|
8635
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
8636
|
+
} catch {
|
|
8637
|
+
return [];
|
|
8638
|
+
}
|
|
8639
|
+
})();
|
|
8640
|
+
return rawValues.map((entry) => readDiscordSnowflake(entry)).filter((entry) => Boolean(entry));
|
|
8641
|
+
}
|
|
8642
|
+
function buildDiscordWorldMetadata(runtime, _guildOwnerId) {
|
|
8643
|
+
const ownerId = resolveMiladyOwnerEntityId(runtime);
|
|
7716
8644
|
return {
|
|
7717
|
-
ownership: { ownerId
|
|
8645
|
+
ownership: { ownerId },
|
|
7718
8646
|
roles: {
|
|
7719
|
-
[
|
|
8647
|
+
[ownerId]: Role.OWNER
|
|
7720
8648
|
}
|
|
7721
8649
|
};
|
|
7722
8650
|
}
|
|
7723
|
-
function buildDiscordEntityMetadata(userId, userName, name, globalName) {
|
|
8651
|
+
function buildDiscordEntityMetadata(userId, userName, name, globalName, avatarUrl) {
|
|
7724
8652
|
return {
|
|
7725
8653
|
default: {
|
|
7726
8654
|
username: userName,
|
|
7727
|
-
name
|
|
8655
|
+
name,
|
|
8656
|
+
...typeof avatarUrl === "string" && avatarUrl.length > 0 ? { avatarUrl } : {}
|
|
7728
8657
|
},
|
|
7729
8658
|
discord: {
|
|
7730
8659
|
id: userId,
|
|
@@ -7732,33 +8661,35 @@ function buildDiscordEntityMetadata(userId, userName, name, globalName) {
|
|
|
7732
8661
|
userName,
|
|
7733
8662
|
username: userName,
|
|
7734
8663
|
name,
|
|
7735
|
-
...typeof globalName === "string" && globalName.length > 0 ? { globalName } : {}
|
|
8664
|
+
...typeof globalName === "string" && globalName.length > 0 ? { globalName } : {},
|
|
8665
|
+
...typeof avatarUrl === "string" && avatarUrl.length > 0 ? { avatarUrl } : {}
|
|
7736
8666
|
},
|
|
7737
8667
|
originalId: userId,
|
|
7738
8668
|
username: userName,
|
|
7739
|
-
displayName: name
|
|
8669
|
+
displayName: name,
|
|
8670
|
+
...typeof avatarUrl === "string" && avatarUrl.length > 0 ? { avatarUrl } : {}
|
|
7740
8671
|
};
|
|
7741
8672
|
}
|
|
7742
8673
|
|
|
7743
8674
|
// messages.ts
|
|
7744
8675
|
import {
|
|
7745
|
-
ChannelType as
|
|
8676
|
+
ChannelType as ChannelType6,
|
|
7746
8677
|
checkPairingAllowed,
|
|
7747
8678
|
createUniqueUuid as createUniqueUuid4,
|
|
7748
8679
|
EventType,
|
|
7749
8680
|
isInAllowlist,
|
|
7750
8681
|
ServiceType as ServiceType4,
|
|
7751
|
-
stringToUuid
|
|
8682
|
+
stringToUuid as stringToUuid2
|
|
7752
8683
|
} from "@elizaos/core";
|
|
7753
8684
|
import {
|
|
7754
8685
|
AttachmentBuilder,
|
|
7755
|
-
ChannelType as
|
|
8686
|
+
ChannelType as DiscordChannelType4
|
|
7756
8687
|
} from "discord.js";
|
|
7757
8688
|
|
|
7758
8689
|
// attachments.ts
|
|
7759
|
-
import
|
|
7760
|
-
import
|
|
7761
|
-
import
|
|
8690
|
+
import fs4 from "node:fs";
|
|
8691
|
+
import os2 from "node:os";
|
|
8692
|
+
import path2 from "node:path";
|
|
7762
8693
|
import {
|
|
7763
8694
|
ModelType as ModelType19,
|
|
7764
8695
|
ServiceType as ServiceType3
|
|
@@ -7768,7 +8699,7 @@ import ffmpeg from "fluent-ffmpeg";
|
|
|
7768
8699
|
|
|
7769
8700
|
// utils.ts
|
|
7770
8701
|
import {
|
|
7771
|
-
logger as
|
|
8702
|
+
logger as logger3,
|
|
7772
8703
|
ModelType as ModelType18,
|
|
7773
8704
|
parseJSONObjectFromText as parseJSONObjectFromText18,
|
|
7774
8705
|
trimTokens as trimTokens3
|
|
@@ -7776,13 +8707,13 @@ import {
|
|
|
7776
8707
|
import {
|
|
7777
8708
|
ActionRowBuilder,
|
|
7778
8709
|
ButtonBuilder,
|
|
7779
|
-
ChannelType as
|
|
8710
|
+
ChannelType as ChannelType5,
|
|
7780
8711
|
PermissionsBitField as PermissionsBitField4,
|
|
7781
8712
|
StringSelectMenuBuilder,
|
|
7782
8713
|
ThreadChannel
|
|
7783
8714
|
} from "discord.js";
|
|
7784
8715
|
function hasMessagingAPI(runtime) {
|
|
7785
|
-
return "elizaOS" in runtime && typeof runtime.elizaOS === "object" && runtime.elizaOS !== null && typeof runtime.elizaOS.sendMessage === "function";
|
|
8716
|
+
return "elizaOS" in runtime && typeof runtime.elizaOS === "object" && runtime.elizaOS !== null && (typeof runtime.elizaOS.handleMessage === "function" || typeof runtime.elizaOS.sendMessage === "function");
|
|
7786
8717
|
}
|
|
7787
8718
|
function hasMessageService(runtime) {
|
|
7788
8719
|
return runtime.messageService !== null && typeof runtime.messageService?.handleMessage === "function";
|
|
@@ -7800,6 +8731,59 @@ function getMessageService(runtime) {
|
|
|
7800
8731
|
return null;
|
|
7801
8732
|
}
|
|
7802
8733
|
var MAX_MESSAGE_LENGTH = 1900;
|
|
8734
|
+
function collectStructuredText(value, seen) {
|
|
8735
|
+
if (typeof value === "string") {
|
|
8736
|
+
return value.trim() ? [value] : [];
|
|
8737
|
+
}
|
|
8738
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
8739
|
+
return [String(value)];
|
|
8740
|
+
}
|
|
8741
|
+
if (!value || typeof value !== "object") {
|
|
8742
|
+
return [];
|
|
8743
|
+
}
|
|
8744
|
+
if (seen.has(value)) {
|
|
8745
|
+
return [];
|
|
8746
|
+
}
|
|
8747
|
+
seen.add(value);
|
|
8748
|
+
if (Array.isArray(value)) {
|
|
8749
|
+
return value.flatMap((entry) => collectStructuredText(entry, seen));
|
|
8750
|
+
}
|
|
8751
|
+
const record = value;
|
|
8752
|
+
for (const key of ["text", "responseText", "message", "body"]) {
|
|
8753
|
+
const normalized = collectStructuredText(record[key], seen);
|
|
8754
|
+
if (normalized.length > 0) {
|
|
8755
|
+
return normalized;
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
for (const key of [
|
|
8759
|
+
"content",
|
|
8760
|
+
"parts",
|
|
8761
|
+
"blocks",
|
|
8762
|
+
"items",
|
|
8763
|
+
"segments"
|
|
8764
|
+
]) {
|
|
8765
|
+
const normalized = collectStructuredText(record[key], seen);
|
|
8766
|
+
if (normalized.length > 0) {
|
|
8767
|
+
return normalized;
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
for (const key of ["title", "summary"]) {
|
|
8771
|
+
const normalized = collectStructuredText(record[key], seen);
|
|
8772
|
+
if (normalized.length > 0) {
|
|
8773
|
+
return normalized;
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
return [];
|
|
8777
|
+
}
|
|
8778
|
+
function normalizeDiscordMessageText(value) {
|
|
8779
|
+
const fragments = collectStructuredText(value, new Set).map((fragment) => fragment.trim()).filter((fragment) => fragment.length > 0);
|
|
8780
|
+
if (fragments.length === 0) {
|
|
8781
|
+
return "";
|
|
8782
|
+
}
|
|
8783
|
+
return fragments.join(`
|
|
8784
|
+
|
|
8785
|
+
`);
|
|
8786
|
+
}
|
|
7803
8787
|
function cleanUrl(url) {
|
|
7804
8788
|
let clean = url;
|
|
7805
8789
|
clean = clean.replace(/\\([._\-~])/g, "$1");
|
|
@@ -7923,6 +8907,13 @@ async function generateSummary(runtime, text) {
|
|
|
7923
8907
|
function isDiscordAPIError(error) {
|
|
7924
8908
|
return error instanceof Error && "code" in error;
|
|
7925
8909
|
}
|
|
8910
|
+
function isReplyReferenceFailure(error) {
|
|
8911
|
+
if (!isDiscordAPIError(error)) {
|
|
8912
|
+
return false;
|
|
8913
|
+
}
|
|
8914
|
+
const errorMessage = error.message.toLowerCase();
|
|
8915
|
+
return error.code === 10008 || errorMessage.includes("unknown message") || errorMessage.includes("message reference") || errorMessage.includes("message_reference");
|
|
8916
|
+
}
|
|
7926
8917
|
function isDiscordJsComponent(component) {
|
|
7927
8918
|
return component !== null && typeof component === "object" && "toJSON" in component && typeof component.toJSON === "function";
|
|
7928
8919
|
}
|
|
@@ -7934,16 +8925,20 @@ function safeStringify(obj) {
|
|
|
7934
8925
|
}
|
|
7935
8926
|
async function sendMessageInChunks(channel, content, inReplyTo, files, components, runtime) {
|
|
7936
8927
|
const sentMessages = [];
|
|
8928
|
+
let lastSendError = null;
|
|
7937
8929
|
let messages;
|
|
7938
8930
|
if (runtime && content.length > MAX_MESSAGE_LENGTH && needsSmartSplit(content)) {
|
|
7939
8931
|
messages = await smartSplitMessage(runtime, content);
|
|
7940
8932
|
} else {
|
|
7941
8933
|
messages = splitMessage(content);
|
|
7942
8934
|
}
|
|
8935
|
+
if (messages.length === 0 && (files && files.length > 0 || components && components.length > 0)) {
|
|
8936
|
+
messages = [""];
|
|
8937
|
+
}
|
|
7943
8938
|
try {
|
|
7944
8939
|
for (let i = 0;i < messages.length; i++) {
|
|
7945
8940
|
const message = messages[i];
|
|
7946
|
-
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0 || components) {
|
|
8941
|
+
if (message.trim().length > 0 || i === messages.length - 1 && files && files.length > 0 || i === messages.length - 1 && components && components.length > 0) {
|
|
7947
8942
|
const options = {
|
|
7948
8943
|
content: message.trim()
|
|
7949
8944
|
};
|
|
@@ -7957,26 +8952,26 @@ async function sendMessageInChunks(channel, content, inReplyTo, files, component
|
|
|
7957
8952
|
}
|
|
7958
8953
|
if (i === messages.length - 1 && components && components.length > 0) {
|
|
7959
8954
|
try {
|
|
7960
|
-
|
|
8955
|
+
logger3.info(`Components received: ${safeStringify(components)}`);
|
|
7961
8956
|
if (!Array.isArray(components)) {
|
|
7962
|
-
|
|
8957
|
+
logger3.warn("Components is not an array, skipping component processing");
|
|
7963
8958
|
} else if (isDiscordJsComponentArray(components)) {
|
|
7964
8959
|
options.components = components;
|
|
7965
8960
|
} else {
|
|
7966
8961
|
const discordComponents = components.map((row) => {
|
|
7967
8962
|
if (!row || typeof row !== "object" || row.type !== 1) {
|
|
7968
|
-
|
|
8963
|
+
logger3.warn("Invalid component row structure, skipping");
|
|
7969
8964
|
return null;
|
|
7970
8965
|
}
|
|
7971
8966
|
if (row.type === 1) {
|
|
7972
8967
|
const actionRow = new ActionRowBuilder;
|
|
7973
8968
|
if (!Array.isArray(row.components)) {
|
|
7974
|
-
|
|
8969
|
+
logger3.warn("Row components is not an array, skipping");
|
|
7975
8970
|
return null;
|
|
7976
8971
|
}
|
|
7977
8972
|
const validComponents = row.components.map((comp) => {
|
|
7978
8973
|
if (!comp || typeof comp !== "object") {
|
|
7979
|
-
|
|
8974
|
+
logger3.warn("Invalid component, skipping");
|
|
7980
8975
|
return null;
|
|
7981
8976
|
}
|
|
7982
8977
|
try {
|
|
@@ -8001,7 +8996,7 @@ async function sendMessageInChunks(channel, content, inReplyTo, files, component
|
|
|
8001
8996
|
return selectMenu;
|
|
8002
8997
|
}
|
|
8003
8998
|
} catch (err) {
|
|
8004
|
-
|
|
8999
|
+
logger3.error(`Error creating component: ${err}`);
|
|
8005
9000
|
return null;
|
|
8006
9001
|
}
|
|
8007
9002
|
return null;
|
|
@@ -8018,15 +9013,15 @@ async function sendMessageInChunks(channel, content, inReplyTo, files, component
|
|
|
8018
9013
|
}
|
|
8019
9014
|
}
|
|
8020
9015
|
} catch (error) {
|
|
8021
|
-
|
|
9016
|
+
logger3.error(`Error processing components: ${error}`);
|
|
8022
9017
|
}
|
|
8023
9018
|
}
|
|
8024
9019
|
try {
|
|
8025
9020
|
const m = await channel.send(options);
|
|
8026
9021
|
sentMessages.push(m);
|
|
8027
9022
|
} catch (error) {
|
|
8028
|
-
if (
|
|
8029
|
-
|
|
9023
|
+
if (isReplyReferenceFailure(error) && options.reply) {
|
|
9024
|
+
logger3.warn("Message reference no longer valid (message may have been deleted). Sending without reply threading.");
|
|
8030
9025
|
const optionsWithoutReply = { ...options };
|
|
8031
9026
|
delete optionsWithoutReply.reply;
|
|
8032
9027
|
try {
|
|
@@ -8034,17 +9029,27 @@ async function sendMessageInChunks(channel, content, inReplyTo, files, component
|
|
|
8034
9029
|
sentMessages.push(m);
|
|
8035
9030
|
} catch (retryError) {
|
|
8036
9031
|
const errorMessage = retryError instanceof Error ? retryError.message : String(retryError);
|
|
8037
|
-
|
|
9032
|
+
lastSendError = retryError;
|
|
9033
|
+
logger3.error(`Error sending message after removing reply reference: ${errorMessage}`);
|
|
8038
9034
|
throw retryError;
|
|
8039
9035
|
}
|
|
8040
9036
|
} else {
|
|
9037
|
+
lastSendError = error;
|
|
8041
9038
|
throw error;
|
|
8042
9039
|
}
|
|
8043
9040
|
}
|
|
8044
9041
|
}
|
|
8045
9042
|
}
|
|
8046
9043
|
} catch (error) {
|
|
8047
|
-
|
|
9044
|
+
lastSendError = error;
|
|
9045
|
+
logger3.error(`Error sending message: ${error}`);
|
|
9046
|
+
}
|
|
9047
|
+
const attemptedSend = content.trim().length > 0 || files && files.length > 0 || components && components.length > 0;
|
|
9048
|
+
if (attemptedSend && sentMessages.length === 0) {
|
|
9049
|
+
if (lastSendError instanceof Error) {
|
|
9050
|
+
throw lastSendError;
|
|
9051
|
+
}
|
|
9052
|
+
throw new Error("Discord message send completed without delivering any chunks");
|
|
8048
9053
|
}
|
|
8049
9054
|
return sentMessages;
|
|
8050
9055
|
}
|
|
@@ -8165,7 +9170,7 @@ function canSendMessage(channel) {
|
|
|
8165
9170
|
reason: "No channel given"
|
|
8166
9171
|
};
|
|
8167
9172
|
}
|
|
8168
|
-
if (channel.type ===
|
|
9173
|
+
if (channel.type === ChannelType5.DM) {
|
|
8169
9174
|
return {
|
|
8170
9175
|
canSend: true,
|
|
8171
9176
|
reason: null
|
|
@@ -8346,12 +9351,12 @@ class AttachmentManager {
|
|
|
8346
9351
|
}
|
|
8347
9352
|
}
|
|
8348
9353
|
async extractAudioFromMP4(mp4Data) {
|
|
8349
|
-
const tmpDir =
|
|
9354
|
+
const tmpDir = os2.tmpdir();
|
|
8350
9355
|
const timestamp = Date.now();
|
|
8351
|
-
const tempMP4File =
|
|
8352
|
-
const tempAudioFile =
|
|
9356
|
+
const tempMP4File = path2.join(tmpDir, `discord_video_${timestamp}.mp4`);
|
|
9357
|
+
const tempAudioFile = path2.join(tmpDir, `discord_audio_${timestamp}.mp3`);
|
|
8353
9358
|
try {
|
|
8354
|
-
|
|
9359
|
+
fs4.writeFileSync(tempMP4File, Buffer.from(mp4Data));
|
|
8355
9360
|
await new Promise((resolve, reject) => {
|
|
8356
9361
|
ffmpeg.ffprobe(tempMP4File, (err, metadata) => {
|
|
8357
9362
|
if (err) {
|
|
@@ -8383,7 +9388,7 @@ class AttachmentManager {
|
|
|
8383
9388
|
reject(err);
|
|
8384
9389
|
}).output(tempAudioFile).run();
|
|
8385
9390
|
});
|
|
8386
|
-
const audioData =
|
|
9391
|
+
const audioData = fs4.readFileSync(tempAudioFile);
|
|
8387
9392
|
this.runtime.logger.debug({
|
|
8388
9393
|
src: "plugin:discord",
|
|
8389
9394
|
agentId: this.runtime.agentId,
|
|
@@ -8392,11 +9397,11 @@ class AttachmentManager {
|
|
|
8392
9397
|
return audioData;
|
|
8393
9398
|
} finally {
|
|
8394
9399
|
try {
|
|
8395
|
-
if (
|
|
8396
|
-
|
|
9400
|
+
if (fs4.existsSync(tempMP4File)) {
|
|
9401
|
+
fs4.unlinkSync(tempMP4File);
|
|
8397
9402
|
}
|
|
8398
|
-
if (
|
|
8399
|
-
|
|
9403
|
+
if (fs4.existsSync(tempAudioFile)) {
|
|
9404
|
+
fs4.unlinkSync(tempAudioFile);
|
|
8400
9405
|
}
|
|
8401
9406
|
} catch (cleanupError) {
|
|
8402
9407
|
this.runtime.logger.warn({
|
|
@@ -8562,99 +9567,614 @@ class AttachmentManager {
|
|
|
8562
9567
|
}
|
|
8563
9568
|
}
|
|
8564
9569
|
|
|
8565
|
-
//
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
if (!discordService.client) {
|
|
8575
|
-
const errorMsg = "Discord client not initialized - cannot create MessageManager";
|
|
8576
|
-
runtime.logger.error({ src: "plugin:discord", agentId: runtime.agentId }, errorMsg);
|
|
8577
|
-
throw new Error(errorMsg);
|
|
8578
|
-
}
|
|
8579
|
-
this.client = discordService.client;
|
|
8580
|
-
this.runtime = runtime;
|
|
8581
|
-
this.attachmentManager = new AttachmentManager(this.runtime);
|
|
8582
|
-
this.getChannelType = discordService.getChannelType;
|
|
8583
|
-
this.discordService = discordService;
|
|
8584
|
-
this.discordSettings = getDiscordSettings(this.runtime);
|
|
9570
|
+
// draft-chunking.ts
|
|
9571
|
+
var DEFAULT_DRAFT_CHUNK_CONFIG = {
|
|
9572
|
+
minChars: 80,
|
|
9573
|
+
maxChars: 1900,
|
|
9574
|
+
breakPreference: "sentence"
|
|
9575
|
+
};
|
|
9576
|
+
function findBreakPoint(text, maxLen, breakPreference = "sentence") {
|
|
9577
|
+
if (text.length <= maxLen) {
|
|
9578
|
+
return text.length;
|
|
8585
9579
|
}
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
const
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
userId
|
|
8594
|
-
}, "DM blocked: policy is disabled");
|
|
8595
|
-
return { allowed: false };
|
|
8596
|
-
}
|
|
8597
|
-
if (policy === "open") {
|
|
8598
|
-
return { allowed: true };
|
|
9580
|
+
const region = text.slice(0, maxLen);
|
|
9581
|
+
if (breakPreference === "paragraph" || breakPreference === "newline") {
|
|
9582
|
+
const paragraphBreak = region.lastIndexOf(`
|
|
9583
|
+
|
|
9584
|
+
`);
|
|
9585
|
+
if (paragraphBreak > maxLen * 0.3) {
|
|
9586
|
+
return paragraphBreak + 2;
|
|
8599
9587
|
}
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
return { allowed: true };
|
|
8607
|
-
}
|
|
8608
|
-
this.runtime.logger.debug({
|
|
8609
|
-
src: "plugin:discord",
|
|
8610
|
-
agentId: this.runtime.agentId,
|
|
8611
|
-
userId
|
|
8612
|
-
}, "DM blocked: user not in allowlist");
|
|
8613
|
-
return { allowed: false };
|
|
9588
|
+
}
|
|
9589
|
+
if (breakPreference !== "sentence") {
|
|
9590
|
+
const newlineBreak = region.lastIndexOf(`
|
|
9591
|
+
`);
|
|
9592
|
+
if (newlineBreak > maxLen * 0.3) {
|
|
9593
|
+
return newlineBreak + 1;
|
|
8614
9594
|
}
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
senderId: userId,
|
|
8622
|
-
metadata: {
|
|
8623
|
-
username: message.author.username,
|
|
8624
|
-
displayName: message.author.displayName ?? message.author.username,
|
|
8625
|
-
discriminator: message.author.discriminator ?? ""
|
|
8626
|
-
}
|
|
8627
|
-
});
|
|
8628
|
-
if (result.allowed) {
|
|
8629
|
-
return { allowed: true };
|
|
8630
|
-
}
|
|
8631
|
-
this.runtime.logger.debug({
|
|
8632
|
-
src: "plugin:discord",
|
|
8633
|
-
agentId: this.runtime.agentId,
|
|
8634
|
-
userId,
|
|
8635
|
-
pairingCode: result.pairingCode,
|
|
8636
|
-
newRequest: result.newRequest
|
|
8637
|
-
}, "DM blocked: pairing required");
|
|
8638
|
-
return {
|
|
8639
|
-
allowed: false,
|
|
8640
|
-
replyMessage: result.newRequest ? result.replyMessage : undefined
|
|
8641
|
-
};
|
|
9595
|
+
}
|
|
9596
|
+
const sentenceMatch = region.match(/[.!?]\s+(?=[A-Z])/g);
|
|
9597
|
+
if (sentenceMatch) {
|
|
9598
|
+
const lastSentenceEnd = region.lastIndexOf(sentenceMatch[sentenceMatch.length - 1]);
|
|
9599
|
+
if (lastSentenceEnd > maxLen * 0.3) {
|
|
9600
|
+
return lastSentenceEnd + sentenceMatch[sentenceMatch.length - 1].length;
|
|
8642
9601
|
}
|
|
8643
|
-
return { allowed: true };
|
|
8644
9602
|
}
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
9603
|
+
const simpleSentenceBreak = region.lastIndexOf(". ");
|
|
9604
|
+
if (simpleSentenceBreak > maxLen * 0.3) {
|
|
9605
|
+
return simpleSentenceBreak + 2;
|
|
9606
|
+
}
|
|
9607
|
+
const wordBreak = region.lastIndexOf(" ");
|
|
9608
|
+
if (wordBreak > maxLen * 0.5) {
|
|
9609
|
+
return wordBreak + 1;
|
|
9610
|
+
}
|
|
9611
|
+
return maxLen;
|
|
9612
|
+
}
|
|
9613
|
+
|
|
9614
|
+
// draft-stream.ts
|
|
9615
|
+
var DEFAULT_THROTTLE_MS = 1200;
|
|
9616
|
+
var DEFAULT_MIN_INITIAL_CHARS = 40;
|
|
9617
|
+
var DISCORD_MAX_CHARS = 2000;
|
|
9618
|
+
function createDraftStreamController(options = {}) {
|
|
9619
|
+
const throttleMs = Math.max(250, options.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
9620
|
+
const minInitialChars = options.minInitialChars ?? DEFAULT_MIN_INITIAL_CHARS;
|
|
9621
|
+
const maxChars = Math.min(options.maxChars ?? 1900, DISCORD_MAX_CHARS);
|
|
9622
|
+
const log = options.log ?? (() => {});
|
|
9623
|
+
const warn = options.warn ?? (() => {});
|
|
9624
|
+
let channel = null;
|
|
9625
|
+
let draftMessage = null;
|
|
9626
|
+
let lastSentText = "";
|
|
9627
|
+
let pendingText = null;
|
|
9628
|
+
let throttleTimer = null;
|
|
9629
|
+
let started = false;
|
|
9630
|
+
let done = false;
|
|
9631
|
+
const clearThrottle = () => {
|
|
9632
|
+
if (throttleTimer) {
|
|
9633
|
+
clearTimeout(throttleTimer);
|
|
9634
|
+
throttleTimer = null;
|
|
8649
9635
|
}
|
|
8650
|
-
|
|
8651
|
-
|
|
9636
|
+
};
|
|
9637
|
+
const sendOrEdit = async (text) => {
|
|
9638
|
+
if (done || !channel) {
|
|
9639
|
+
return false;
|
|
8652
9640
|
}
|
|
8653
|
-
|
|
8654
|
-
|
|
9641
|
+
const trimmed = text.trimEnd();
|
|
9642
|
+
if (!trimmed) {
|
|
9643
|
+
return false;
|
|
8655
9644
|
}
|
|
8656
|
-
|
|
8657
|
-
|
|
9645
|
+
const displayText = trimmed.length > maxChars ? `${trimmed.slice(0, maxChars - 3)}...` : trimmed;
|
|
9646
|
+
if (displayText === lastSentText) {
|
|
9647
|
+
return true;
|
|
9648
|
+
}
|
|
9649
|
+
try {
|
|
9650
|
+
if (draftMessage) {
|
|
9651
|
+
await draftMessage.edit({ content: displayText });
|
|
9652
|
+
} else {
|
|
9653
|
+
warn("draft-stream: sendOrEdit called before start, sending new message");
|
|
9654
|
+
draftMessage = await channel.send({ content: displayText });
|
|
9655
|
+
}
|
|
9656
|
+
lastSentText = displayText;
|
|
9657
|
+
return true;
|
|
9658
|
+
} catch (error) {
|
|
9659
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9660
|
+
if (errorMessage.includes("Unknown Message") || errorMessage.includes("10008")) {
|
|
9661
|
+
warn("draft-stream: message was deleted externally, stopping");
|
|
9662
|
+
done = true;
|
|
9663
|
+
return false;
|
|
9664
|
+
}
|
|
9665
|
+
warn(`draft-stream: edit failed: ${errorMessage}`);
|
|
9666
|
+
return false;
|
|
9667
|
+
}
|
|
9668
|
+
};
|
|
9669
|
+
const flush = async () => {
|
|
9670
|
+
clearThrottle();
|
|
9671
|
+
if (pendingText !== null) {
|
|
9672
|
+
const text = pendingText;
|
|
9673
|
+
pendingText = null;
|
|
9674
|
+
await sendOrEdit(text);
|
|
9675
|
+
}
|
|
9676
|
+
};
|
|
9677
|
+
const scheduleUpdate = (text) => {
|
|
9678
|
+
pendingText = text;
|
|
9679
|
+
if (!throttleTimer) {
|
|
9680
|
+
throttleTimer = setTimeout(async () => {
|
|
9681
|
+
throttleTimer = null;
|
|
9682
|
+
await flush();
|
|
9683
|
+
}, throttleMs);
|
|
9684
|
+
}
|
|
9685
|
+
};
|
|
9686
|
+
const start = async (nextChannel, replyToMessageId) => {
|
|
9687
|
+
if (started) {
|
|
9688
|
+
warn("draft-stream: start() called twice, ignoring");
|
|
9689
|
+
return draftMessage;
|
|
9690
|
+
}
|
|
9691
|
+
started = true;
|
|
9692
|
+
channel = nextChannel;
|
|
9693
|
+
try {
|
|
9694
|
+
const sendOptions = { content: "..." };
|
|
9695
|
+
if (replyToMessageId) {
|
|
9696
|
+
sendOptions.reply = { messageReference: replyToMessageId };
|
|
9697
|
+
}
|
|
9698
|
+
draftMessage = await nextChannel.send(sendOptions);
|
|
9699
|
+
lastSentText = "...";
|
|
9700
|
+
log(`draft-stream: started (messageId=${draftMessage.id}, throttle=${throttleMs}ms)`);
|
|
9701
|
+
return draftMessage;
|
|
9702
|
+
} catch (error) {
|
|
9703
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9704
|
+
warn(`draft-stream: failed to send initial message: ${errorMessage}`);
|
|
9705
|
+
done = true;
|
|
9706
|
+
return null;
|
|
9707
|
+
}
|
|
9708
|
+
};
|
|
9709
|
+
const update = (text) => {
|
|
9710
|
+
if (done || !started) {
|
|
9711
|
+
return;
|
|
9712
|
+
}
|
|
9713
|
+
if (draftMessage && lastSentText === "..." && text.length < minInitialChars) {
|
|
9714
|
+
return;
|
|
9715
|
+
}
|
|
9716
|
+
scheduleUpdate(text);
|
|
9717
|
+
};
|
|
9718
|
+
const finalize = async (text) => {
|
|
9719
|
+
if (done) {
|
|
9720
|
+
return draftMessage ? [draftMessage] : [];
|
|
9721
|
+
}
|
|
9722
|
+
clearThrottle();
|
|
9723
|
+
pendingText = null;
|
|
9724
|
+
if (!started || !draftMessage) {
|
|
9725
|
+
warn("draft-stream: finalize called before start");
|
|
9726
|
+
done = true;
|
|
9727
|
+
return [];
|
|
9728
|
+
}
|
|
9729
|
+
const trimmed = text.trimEnd();
|
|
9730
|
+
if (!trimmed) {
|
|
9731
|
+
try {
|
|
9732
|
+
await draftMessage.delete();
|
|
9733
|
+
} catch {}
|
|
9734
|
+
done = true;
|
|
9735
|
+
return [];
|
|
9736
|
+
}
|
|
9737
|
+
if (trimmed.length <= maxChars) {
|
|
9738
|
+
await sendOrEdit(trimmed);
|
|
9739
|
+
done = true;
|
|
9740
|
+
log("draft-stream: finalized (single message)");
|
|
9741
|
+
return [draftMessage];
|
|
9742
|
+
}
|
|
9743
|
+
const chunkConfig = {
|
|
9744
|
+
...DEFAULT_DRAFT_CHUNK_CONFIG,
|
|
9745
|
+
...options.chunkConfig
|
|
9746
|
+
};
|
|
9747
|
+
const breakPoint = findBreakPoint(trimmed, maxChars, chunkConfig.breakPreference);
|
|
9748
|
+
const firstChunk = trimmed.slice(0, breakPoint).trimEnd();
|
|
9749
|
+
let remaining = trimmed.slice(breakPoint).trimStart();
|
|
9750
|
+
await sendOrEdit(firstChunk);
|
|
9751
|
+
const allMessages = [draftMessage];
|
|
9752
|
+
while (remaining.length > 0 && channel) {
|
|
9753
|
+
const nextBreak = findBreakPoint(remaining, maxChars, chunkConfig.breakPreference);
|
|
9754
|
+
const chunk = remaining.slice(0, nextBreak).trimEnd();
|
|
9755
|
+
remaining = remaining.slice(nextBreak).trimStart();
|
|
9756
|
+
if (!chunk) {
|
|
9757
|
+
continue;
|
|
9758
|
+
}
|
|
9759
|
+
try {
|
|
9760
|
+
const overflowMessage = await channel.send({ content: chunk });
|
|
9761
|
+
allMessages.push(overflowMessage);
|
|
9762
|
+
} catch (error) {
|
|
9763
|
+
warn(`draft-stream: overflow send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
9764
|
+
break;
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
done = true;
|
|
9768
|
+
log("draft-stream: finalized (multi-message)");
|
|
9769
|
+
return allMessages;
|
|
9770
|
+
};
|
|
9771
|
+
const abort = async (reason) => {
|
|
9772
|
+
if (done) {
|
|
9773
|
+
return;
|
|
9774
|
+
}
|
|
9775
|
+
done = true;
|
|
9776
|
+
clearThrottle();
|
|
9777
|
+
pendingText = null;
|
|
9778
|
+
if (!draftMessage) {
|
|
9779
|
+
return;
|
|
9780
|
+
}
|
|
9781
|
+
const errorText = reason ? `⚠️ ${reason}` : "⚠️ Response generation was interrupted.";
|
|
9782
|
+
try {
|
|
9783
|
+
await draftMessage.edit({ content: errorText });
|
|
9784
|
+
} catch {
|
|
9785
|
+
try {
|
|
9786
|
+
await draftMessage.delete();
|
|
9787
|
+
} catch {}
|
|
9788
|
+
}
|
|
9789
|
+
log("draft-stream: aborted");
|
|
9790
|
+
};
|
|
9791
|
+
return {
|
|
9792
|
+
start,
|
|
9793
|
+
update,
|
|
9794
|
+
finalize,
|
|
9795
|
+
abort,
|
|
9796
|
+
messageId: () => draftMessage?.id,
|
|
9797
|
+
isStarted: () => started,
|
|
9798
|
+
isDone: () => done
|
|
9799
|
+
};
|
|
9800
|
+
}
|
|
9801
|
+
|
|
9802
|
+
// inbound-envelope.ts
|
|
9803
|
+
import {
|
|
9804
|
+
ChannelType as DiscordChannelType3
|
|
9805
|
+
} from "discord.js";
|
|
9806
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
9807
|
+
function formatTimestamp(timestamp) {
|
|
9808
|
+
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
9809
|
+
const weekday = WEEKDAYS[date.getDay()];
|
|
9810
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
9811
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
9812
|
+
const year = date.getFullYear();
|
|
9813
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
9814
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
9815
|
+
let timezone = "UTC";
|
|
9816
|
+
try {
|
|
9817
|
+
timezone = date.toLocaleTimeString("en-US", { timeZoneName: "short" }).split(" ").pop() ?? "UTC";
|
|
9818
|
+
} catch {}
|
|
9819
|
+
return `${weekday} ${month}/${day}/${year} ${hours}:${minutes} ${timezone}`;
|
|
9820
|
+
}
|
|
9821
|
+
function detectChatType(message) {
|
|
9822
|
+
const channelType = message.channel.type;
|
|
9823
|
+
if (channelType === DiscordChannelType3.DM || channelType === DiscordChannelType3.GroupDM) {
|
|
9824
|
+
return "dm";
|
|
9825
|
+
}
|
|
9826
|
+
if (channelType === DiscordChannelType3.PublicThread || channelType === DiscordChannelType3.PrivateThread || channelType === DiscordChannelType3.AnnouncementThread) {
|
|
9827
|
+
const thread = message.channel;
|
|
9828
|
+
if (thread.parent?.type === DiscordChannelType3.GuildForum) {
|
|
9829
|
+
return "forum";
|
|
9830
|
+
}
|
|
9831
|
+
return "thread";
|
|
9832
|
+
}
|
|
9833
|
+
return "channel";
|
|
9834
|
+
}
|
|
9835
|
+
function getSenderName(message) {
|
|
9836
|
+
if (message.member?.nickname) {
|
|
9837
|
+
return message.member.nickname;
|
|
9838
|
+
}
|
|
9839
|
+
if (message.author.globalName) {
|
|
9840
|
+
return message.author.globalName;
|
|
9841
|
+
}
|
|
9842
|
+
return message.author.displayName ?? message.author.username;
|
|
9843
|
+
}
|
|
9844
|
+
function buildChannelLabel(message, chatType) {
|
|
9845
|
+
if (chatType === "dm") {
|
|
9846
|
+
return "DM";
|
|
9847
|
+
}
|
|
9848
|
+
const guildName = message.guild?.name;
|
|
9849
|
+
let channelPart;
|
|
9850
|
+
if (chatType === "thread" || chatType === "forum") {
|
|
9851
|
+
const thread = message.channel;
|
|
9852
|
+
channelPart = `#${thread.parent?.name ?? "unknown"} › ${thread.name ?? "thread"}`;
|
|
9853
|
+
} else {
|
|
9854
|
+
const channel = message.channel;
|
|
9855
|
+
channelPart = `#${channel.name ?? message.channel.id}`;
|
|
9856
|
+
}
|
|
9857
|
+
return guildName ? `${channelPart} | ${guildName}` : channelPart;
|
|
9858
|
+
}
|
|
9859
|
+
async function formatInboundEnvelope(message, rawContent) {
|
|
9860
|
+
const chatType = detectChatType(message);
|
|
9861
|
+
const channelLabel = buildChannelLabel(message, chatType);
|
|
9862
|
+
const senderName = getSenderName(message);
|
|
9863
|
+
const timestamp = formatTimestamp(message.createdTimestamp ?? Date.now());
|
|
9864
|
+
let replyContext = "";
|
|
9865
|
+
if (message.reference?.messageId) {
|
|
9866
|
+
try {
|
|
9867
|
+
const refMessage = await message.fetchReference();
|
|
9868
|
+
const refAuthor = refMessage.author?.displayName ?? refMessage.author?.username ?? "unknown";
|
|
9869
|
+
const refContent = refMessage.content ?? "";
|
|
9870
|
+
const truncated = refContent.length > 200 ? `${refContent.slice(0, 200)}...` : refContent;
|
|
9871
|
+
replyContext = truncated ? ` replying to @${refAuthor}:
|
|
9872
|
+
> ${truncated}
|
|
9873
|
+
` : ` replying to @${refAuthor}:
|
|
9874
|
+
`;
|
|
9875
|
+
} catch {}
|
|
9876
|
+
}
|
|
9877
|
+
const header = `[Discord ${channelLabel}] @${senderName} (${timestamp})`;
|
|
9878
|
+
return {
|
|
9879
|
+
formattedContent: replyContext ? `${header}${replyContext}${rawContent}` : `${header}: ${rawContent}`,
|
|
9880
|
+
chatType
|
|
9881
|
+
};
|
|
9882
|
+
}
|
|
9883
|
+
|
|
9884
|
+
// reasoning-tags.ts
|
|
9885
|
+
var REASONING_TAGS = [
|
|
9886
|
+
"thinking",
|
|
9887
|
+
"reasoning",
|
|
9888
|
+
"reflection",
|
|
9889
|
+
"scratchpad",
|
|
9890
|
+
"thought",
|
|
9891
|
+
"antthinking"
|
|
9892
|
+
];
|
|
9893
|
+
var SELF_CLOSING_ARTIFACTS_RE = /<(?:STOP|END|end_turn|eot_id)\s*\/?>|<\|(?:end|stop|im_end|eot_id)\|>/gi;
|
|
9894
|
+
var QUICK_TAG_RE = /<\/?(?:thinking|reasoning|reflection|scratchpad|thought|antthinking|final|STOP|END|end_turn)\b|<\|(?:end|stop|im_end)/i;
|
|
9895
|
+
var CODE_BLOCK_RE = /```[\s\S]*?```/g;
|
|
9896
|
+
var PLACEHOLDER_PREFIX = "\x00CB";
|
|
9897
|
+
function stripReasoningTags(text) {
|
|
9898
|
+
if (!text || !QUICK_TAG_RE.test(text)) {
|
|
9899
|
+
return text;
|
|
9900
|
+
}
|
|
9901
|
+
const codeBlocks = [];
|
|
9902
|
+
let processed = text.replace(CODE_BLOCK_RE, (match) => {
|
|
9903
|
+
const index = codeBlocks.length;
|
|
9904
|
+
codeBlocks.push(match);
|
|
9905
|
+
return `${PLACEHOLDER_PREFIX}${index}${PLACEHOLDER_PREFIX}`;
|
|
9906
|
+
});
|
|
9907
|
+
processed = processed.replace(SELF_CLOSING_ARTIFACTS_RE, "");
|
|
9908
|
+
for (const tag of REASONING_TAGS) {
|
|
9909
|
+
const paired = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
|
|
9910
|
+
processed = processed.replace(paired, "");
|
|
9911
|
+
const unclosed = new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*$`, "gi");
|
|
9912
|
+
processed = processed.replace(unclosed, "");
|
|
9913
|
+
}
|
|
9914
|
+
processed = processed.replace(/<final\b[^>]*>([\s\S]*?)<\/final>/gi, "$1");
|
|
9915
|
+
for (let index = 0;index < codeBlocks.length; index++) {
|
|
9916
|
+
processed = processed.replace(`${PLACEHOLDER_PREFIX}${index}${PLACEHOLDER_PREFIX}`, codeBlocks[index]);
|
|
9917
|
+
}
|
|
9918
|
+
return processed.replace(/\n{3,}/g, `
|
|
9919
|
+
|
|
9920
|
+
`).trim();
|
|
9921
|
+
}
|
|
9922
|
+
|
|
9923
|
+
// status-reactions.ts
|
|
9924
|
+
var EMOJI_QUEUED = "⏳";
|
|
9925
|
+
var EMOJI_THINKING = "\uD83E\uDD14";
|
|
9926
|
+
var EMOJI_DONE = "✅";
|
|
9927
|
+
var EMOJI_ERROR = "❌";
|
|
9928
|
+
function shouldShowStatusReaction(scope, message, botId) {
|
|
9929
|
+
if (scope === "none") {
|
|
9930
|
+
return false;
|
|
9931
|
+
}
|
|
9932
|
+
if (scope === "all") {
|
|
9933
|
+
return true;
|
|
9934
|
+
}
|
|
9935
|
+
if (!message.guild) {
|
|
9936
|
+
return true;
|
|
9937
|
+
}
|
|
9938
|
+
const isMentioned = Boolean(botId && message.mentions.users?.has(botId));
|
|
9939
|
+
const isReplyToBot = message.mentions.repliedUser?.id === botId;
|
|
9940
|
+
return isMentioned || isReplyToBot;
|
|
9941
|
+
}
|
|
9942
|
+
function createStatusReactionController(message) {
|
|
9943
|
+
let currentEmoji = null;
|
|
9944
|
+
let finished = false;
|
|
9945
|
+
let chain = Promise.resolve();
|
|
9946
|
+
const botId = message.client?.user?.id;
|
|
9947
|
+
const transition = (emoji, terminal = false) => {
|
|
9948
|
+
if (finished) {
|
|
9949
|
+
return;
|
|
9950
|
+
}
|
|
9951
|
+
chain = chain.then(async () => {
|
|
9952
|
+
if (finished && !terminal) {
|
|
9953
|
+
return;
|
|
9954
|
+
}
|
|
9955
|
+
try {
|
|
9956
|
+
if (currentEmoji && currentEmoji !== emoji && botId) {
|
|
9957
|
+
try {
|
|
9958
|
+
const reaction = message.reactions.resolve(currentEmoji);
|
|
9959
|
+
if (reaction) {
|
|
9960
|
+
await reaction.users.remove(botId);
|
|
9961
|
+
}
|
|
9962
|
+
} catch {}
|
|
9963
|
+
}
|
|
9964
|
+
await message.react(emoji);
|
|
9965
|
+
currentEmoji = emoji;
|
|
9966
|
+
} catch {} finally {
|
|
9967
|
+
if (terminal) {
|
|
9968
|
+
finished = true;
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
});
|
|
9972
|
+
};
|
|
9973
|
+
return {
|
|
9974
|
+
setQueued: () => transition(EMOJI_QUEUED),
|
|
9975
|
+
setThinking: () => transition(EMOJI_THINKING),
|
|
9976
|
+
setDone: () => transition(EMOJI_DONE, true),
|
|
9977
|
+
setError: () => transition(EMOJI_ERROR, true)
|
|
9978
|
+
};
|
|
9979
|
+
}
|
|
9980
|
+
|
|
9981
|
+
// typing.ts
|
|
9982
|
+
var HEARTBEAT_MS = 9000;
|
|
9983
|
+
var DEFAULT_MAX_DURATION_MS = 20 * 60 * 1000;
|
|
9984
|
+
function createTypingController(channel, maxDurationMs = DEFAULT_MAX_DURATION_MS) {
|
|
9985
|
+
let interval = null;
|
|
9986
|
+
let ttlTimeout = null;
|
|
9987
|
+
let started = false;
|
|
9988
|
+
let stopped = false;
|
|
9989
|
+
const sendTyping = () => {
|
|
9990
|
+
if (stopped || typeof channel.sendTyping !== "function") {
|
|
9991
|
+
return;
|
|
9992
|
+
}
|
|
9993
|
+
try {
|
|
9994
|
+
const result = channel.sendTyping();
|
|
9995
|
+
if (result && typeof result.catch === "function") {
|
|
9996
|
+
result.catch(() => {});
|
|
9997
|
+
}
|
|
9998
|
+
} catch {}
|
|
9999
|
+
};
|
|
10000
|
+
const stop = () => {
|
|
10001
|
+
if (stopped) {
|
|
10002
|
+
return;
|
|
10003
|
+
}
|
|
10004
|
+
stopped = true;
|
|
10005
|
+
if (interval) {
|
|
10006
|
+
clearInterval(interval);
|
|
10007
|
+
interval = null;
|
|
10008
|
+
}
|
|
10009
|
+
if (ttlTimeout) {
|
|
10010
|
+
clearTimeout(ttlTimeout);
|
|
10011
|
+
ttlTimeout = null;
|
|
10012
|
+
}
|
|
10013
|
+
};
|
|
10014
|
+
const start = () => {
|
|
10015
|
+
if (started || stopped) {
|
|
10016
|
+
return;
|
|
10017
|
+
}
|
|
10018
|
+
started = true;
|
|
10019
|
+
sendTyping();
|
|
10020
|
+
interval = setInterval(sendTyping, HEARTBEAT_MS);
|
|
10021
|
+
ttlTimeout = setTimeout(stop, maxDurationMs);
|
|
10022
|
+
};
|
|
10023
|
+
return { start, stop };
|
|
10024
|
+
}
|
|
10025
|
+
|
|
10026
|
+
// messages.ts
|
|
10027
|
+
function escapeRegex3(value) {
|
|
10028
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10029
|
+
}
|
|
10030
|
+
function textMentionsAnyName(text, names) {
|
|
10031
|
+
if (!text) {
|
|
10032
|
+
return false;
|
|
10033
|
+
}
|
|
10034
|
+
return names.some((name) => {
|
|
10035
|
+
const candidate = name?.trim();
|
|
10036
|
+
if (!candidate) {
|
|
10037
|
+
return false;
|
|
10038
|
+
}
|
|
10039
|
+
const pattern = new RegExp(`(^|[^\\p{L}\\p{N}])${escapeRegex3(candidate)}(?=$|[^\\p{L}\\p{N}])`, "iu");
|
|
10040
|
+
return pattern.test(text);
|
|
10041
|
+
});
|
|
10042
|
+
}
|
|
10043
|
+
|
|
10044
|
+
class MessageManager {
|
|
10045
|
+
client;
|
|
10046
|
+
runtime;
|
|
10047
|
+
attachmentManager;
|
|
10048
|
+
getChannelType;
|
|
10049
|
+
discordSettings;
|
|
10050
|
+
discordService;
|
|
10051
|
+
statusReactionScope;
|
|
10052
|
+
envelopeEnabled;
|
|
10053
|
+
draftStreamingEnabled;
|
|
10054
|
+
recentlyProcessedMessageIds = new Map;
|
|
10055
|
+
static PROCESSED_MESSAGE_TTL_MS = 2 * 60 * 1000;
|
|
10056
|
+
constructor(discordService, runtime) {
|
|
10057
|
+
if (!discordService.client) {
|
|
10058
|
+
const errorMsg = "Discord client not initialized - cannot create MessageManager";
|
|
10059
|
+
runtime.logger.error({ src: "plugin:discord", agentId: runtime.agentId }, errorMsg);
|
|
10060
|
+
throw new Error(errorMsg);
|
|
10061
|
+
}
|
|
10062
|
+
this.client = discordService.client;
|
|
10063
|
+
this.runtime = runtime;
|
|
10064
|
+
this.attachmentManager = new AttachmentManager(this.runtime);
|
|
10065
|
+
this.getChannelType = discordService.getChannelType;
|
|
10066
|
+
this.discordService = discordService;
|
|
10067
|
+
this.discordSettings = getDiscordSettings(this.runtime);
|
|
10068
|
+
const reactionScopeSetting = this.runtime.getSetting("DISCORD_STATUS_REACTIONS");
|
|
10069
|
+
this.statusReactionScope = ["all", "group-mentions", "none"].includes(reactionScopeSetting ?? "") ? reactionScopeSetting : "group-mentions";
|
|
10070
|
+
const envelopeSetting = this.runtime.getSetting("DISCORD_ENVELOPE_ENABLED");
|
|
10071
|
+
this.envelopeEnabled = envelopeSetting !== "false" && envelopeSetting !== "0";
|
|
10072
|
+
const draftStreamSetting = this.runtime.getSetting("DISCORD_DRAFT_STREAMING");
|
|
10073
|
+
this.draftStreamingEnabled = draftStreamSetting === "true" || draftStreamSetting === "1";
|
|
10074
|
+
}
|
|
10075
|
+
async checkDmAccess(message) {
|
|
10076
|
+
const policy = this.discordSettings.dmPolicy ?? "pairing";
|
|
10077
|
+
const userId = message.author.id;
|
|
10078
|
+
if (policy === "disabled") {
|
|
10079
|
+
this.runtime.logger.debug({
|
|
10080
|
+
src: "plugin:discord",
|
|
10081
|
+
agentId: this.runtime.agentId,
|
|
10082
|
+
userId
|
|
10083
|
+
}, "DM blocked: policy is disabled");
|
|
10084
|
+
return { allowed: false };
|
|
10085
|
+
}
|
|
10086
|
+
if (policy === "open") {
|
|
10087
|
+
return { allowed: true };
|
|
10088
|
+
}
|
|
10089
|
+
if (policy === "allowlist") {
|
|
10090
|
+
if (this.discordSettings.allowFrom?.includes(userId)) {
|
|
10091
|
+
return { allowed: true };
|
|
10092
|
+
}
|
|
10093
|
+
const inDynamicAllowlist = await isInAllowlist(this.runtime, "discord", userId);
|
|
10094
|
+
if (inDynamicAllowlist) {
|
|
10095
|
+
return { allowed: true };
|
|
10096
|
+
}
|
|
10097
|
+
this.runtime.logger.debug({
|
|
10098
|
+
src: "plugin:discord",
|
|
10099
|
+
agentId: this.runtime.agentId,
|
|
10100
|
+
userId
|
|
10101
|
+
}, "DM blocked: user not in allowlist");
|
|
10102
|
+
return { allowed: false };
|
|
10103
|
+
}
|
|
10104
|
+
if (policy === "pairing") {
|
|
10105
|
+
if (this.discordSettings.allowFrom?.includes(userId)) {
|
|
10106
|
+
return { allowed: true };
|
|
10107
|
+
}
|
|
10108
|
+
const result = await checkPairingAllowed(this.runtime, {
|
|
10109
|
+
channel: "discord",
|
|
10110
|
+
senderId: userId,
|
|
10111
|
+
metadata: {
|
|
10112
|
+
username: message.author.username,
|
|
10113
|
+
displayName: message.author.displayName ?? message.author.username,
|
|
10114
|
+
discriminator: message.author.discriminator ?? ""
|
|
10115
|
+
}
|
|
10116
|
+
});
|
|
10117
|
+
if (result.allowed) {
|
|
10118
|
+
return { allowed: true };
|
|
10119
|
+
}
|
|
10120
|
+
this.runtime.logger.debug({
|
|
10121
|
+
src: "plugin:discord",
|
|
10122
|
+
agentId: this.runtime.agentId,
|
|
10123
|
+
userId,
|
|
10124
|
+
pairingCode: result.pairingCode,
|
|
10125
|
+
newRequest: result.newRequest
|
|
10126
|
+
}, "DM blocked: pairing required");
|
|
10127
|
+
return {
|
|
10128
|
+
allowed: false,
|
|
10129
|
+
replyMessage: result.newRequest ? result.replyMessage : undefined
|
|
10130
|
+
};
|
|
10131
|
+
}
|
|
10132
|
+
return { allowed: true };
|
|
10133
|
+
}
|
|
10134
|
+
async persistInboundMemory(memory) {
|
|
10135
|
+
if (!memory.id) {
|
|
10136
|
+
return;
|
|
10137
|
+
}
|
|
10138
|
+
const existing = await this.runtime.getMemoryById(memory.id);
|
|
10139
|
+
if (existing) {
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
await this.runtime.createMemory(memory, "messages");
|
|
10143
|
+
}
|
|
10144
|
+
markMessageAsProcessing(messageId) {
|
|
10145
|
+
const now = Date.now();
|
|
10146
|
+
for (const [candidateId, processedAt] of this.recentlyProcessedMessageIds) {
|
|
10147
|
+
if (now - processedAt > MessageManager.PROCESSED_MESSAGE_TTL_MS) {
|
|
10148
|
+
this.recentlyProcessedMessageIds.delete(candidateId);
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
if (this.recentlyProcessedMessageIds.has(messageId)) {
|
|
10152
|
+
return false;
|
|
10153
|
+
}
|
|
10154
|
+
this.recentlyProcessedMessageIds.set(messageId, now);
|
|
10155
|
+
return true;
|
|
10156
|
+
}
|
|
10157
|
+
async handleMessage(message) {
|
|
10158
|
+
const clientUser = this.client.user;
|
|
10159
|
+
if (message.interaction || clientUser && message.author.id === clientUser.id) {
|
|
10160
|
+
return;
|
|
10161
|
+
}
|
|
10162
|
+
if (this.discordSettings.shouldIgnoreBotMessages && message.author && message.author.bot) {
|
|
10163
|
+
return;
|
|
10164
|
+
}
|
|
10165
|
+
if (message.id && !this.markMessageAsProcessing(message.id)) {
|
|
10166
|
+
this.runtime.logger.debug({
|
|
10167
|
+
src: "plugin:discord",
|
|
10168
|
+
agentId: this.runtime.agentId,
|
|
10169
|
+
messageId: message.id
|
|
10170
|
+
}, "Skipping duplicate Discord message");
|
|
10171
|
+
return;
|
|
10172
|
+
}
|
|
10173
|
+
if (this.discordSettings.shouldIgnoreDirectMessages && message.channel.type === DiscordChannelType4.DM) {
|
|
10174
|
+
return;
|
|
10175
|
+
}
|
|
10176
|
+
if (message.channel.type === DiscordChannelType4.DM) {
|
|
10177
|
+
const accessCheck = await this.checkDmAccess(message);
|
|
8658
10178
|
if (!accessCheck.allowed) {
|
|
8659
10179
|
if (accessCheck.replyMessage) {
|
|
8660
10180
|
try {
|
|
@@ -8676,36 +10196,14 @@ class MessageManager {
|
|
|
8676
10196
|
const mentionedOtherUsers = message.mentions.users ? Array.from(message.mentions.users.values()).some((user) => user.id !== clientUser?.id && user.id !== message.author.id) : false;
|
|
8677
10197
|
const isReplyToOtherUser = !!message.reference?.messageId && !!message.mentions.repliedUser?.id && message.mentions.repliedUser.id !== clientUser?.id && message.mentions.repliedUser.id !== message.author.id;
|
|
8678
10198
|
const isInThread = message.channel.isThread();
|
|
8679
|
-
const isDM = message.channel.type ===
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
src: "plugin:discord",
|
|
8683
|
-
agentId: this.runtime.agentId,
|
|
8684
|
-
channelId: message.channel.id
|
|
8685
|
-
}, "Ignoring message that targets another mentioned user");
|
|
8686
|
-
return;
|
|
8687
|
-
}
|
|
8688
|
-
if (this.discordSettings.shouldRespondOnlyToMentions) {
|
|
8689
|
-
const shouldProcess = isDM || isBotMentioned || isReplyToBot;
|
|
8690
|
-
if (!shouldProcess) {
|
|
8691
|
-
this.runtime.logger.debug({
|
|
8692
|
-
src: "plugin:discord",
|
|
8693
|
-
agentId: this.runtime.agentId,
|
|
8694
|
-
channelId: message.channel.id
|
|
8695
|
-
}, "Strict mode: ignoring message (no mention or reply)");
|
|
8696
|
-
return;
|
|
8697
|
-
}
|
|
8698
|
-
this.runtime.logger.debug({
|
|
8699
|
-
src: "plugin:discord",
|
|
8700
|
-
agentId: this.runtime.agentId,
|
|
8701
|
-
channelId: message.channel.id
|
|
8702
|
-
}, "Strict mode: processing message");
|
|
8703
|
-
}
|
|
8704
|
-
const entityId = createUniqueUuid4(this.runtime, message.author.id);
|
|
10199
|
+
const isDM = message.channel.type === DiscordChannelType4.DM;
|
|
10200
|
+
const strictModeEnabled = this.discordSettings.shouldRespondOnlyToMentions === true;
|
|
10201
|
+
const strictModeShouldProcess = isDM || isBotMentioned || isReplyToBot;
|
|
8705
10202
|
const userName = message.author.bot ? `${message.author.username}#${message.author.discriminator}` : message.author.username;
|
|
8706
|
-
const name = message.author.displayName;
|
|
10203
|
+
const name = message.member?.displayName ?? message.author.globalName ?? message.author.displayName ?? message.author.username;
|
|
8707
10204
|
const channelId = message.channel.id;
|
|
8708
10205
|
const roomId = createUniqueUuid4(this.runtime, channelId);
|
|
10206
|
+
const roomName = message.guild && "name" in message.channel && typeof message.channel.name === "string" ? message.channel.name : name || userName;
|
|
8709
10207
|
let type;
|
|
8710
10208
|
let messageServerId;
|
|
8711
10209
|
if (message.guild) {
|
|
@@ -8720,43 +10218,27 @@ class MessageManager {
|
|
|
8720
10218
|
}
|
|
8721
10219
|
messageServerId = guild.id;
|
|
8722
10220
|
} else {
|
|
8723
|
-
type =
|
|
10221
|
+
type = ChannelType6.DM;
|
|
8724
10222
|
messageServerId = message.channel.id;
|
|
8725
10223
|
}
|
|
8726
|
-
await this.runtime.ensureConnection({
|
|
8727
|
-
entityId,
|
|
8728
|
-
roomId,
|
|
8729
|
-
userName,
|
|
8730
|
-
name,
|
|
8731
|
-
source: "discord",
|
|
8732
|
-
channelId: message.channel.id,
|
|
8733
|
-
messageServerId: messageServerId ? stringToUuid(messageServerId) : undefined,
|
|
8734
|
-
type,
|
|
8735
|
-
worldId: createUniqueUuid4(this.runtime, messageServerId ?? roomId),
|
|
8736
|
-
worldName: message.guild?.name,
|
|
8737
|
-
userId: message.author.id,
|
|
8738
|
-
metadata: buildDiscordWorldMetadata(this.runtime, message.guild?.ownerId ?? undefined)
|
|
8739
|
-
});
|
|
8740
10224
|
try {
|
|
8741
|
-
|
|
8742
|
-
if (
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
reason: canSendResult.reason
|
|
8748
|
-
}, "Cannot send message to channel");
|
|
10225
|
+
let { processedContent, attachments } = await this.processMessage(message);
|
|
10226
|
+
if (this.envelopeEnabled && processedContent) {
|
|
10227
|
+
try {
|
|
10228
|
+
const envelope = await formatInboundEnvelope(message, processedContent);
|
|
10229
|
+
processedContent = envelope.formattedContent;
|
|
10230
|
+
} catch {}
|
|
8749
10231
|
}
|
|
8750
|
-
const { processedContent, attachments } = await this.processMessage(message);
|
|
8751
10232
|
if (!processedContent && !attachments?.length) {
|
|
8752
10233
|
return;
|
|
8753
10234
|
}
|
|
8754
|
-
const
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
10235
|
+
const explicitlyAddressesBotByName = textMentionsAnyName(processedContent, [
|
|
10236
|
+
this.runtime.character.name,
|
|
10237
|
+
this.runtime.character.username,
|
|
10238
|
+
clientUser?.globalName,
|
|
10239
|
+
clientUser?.username
|
|
10240
|
+
]);
|
|
10241
|
+
const ignoresOtherTarget = !isDM && !isBotMentioned && !isReplyToBot && !explicitlyAddressesBotByName && (mentionedOtherUsers || isReplyToOtherUser);
|
|
8760
10242
|
const newMessage = await this.discordService.buildMemoryFromMessage(message, {
|
|
8761
10243
|
processedContent,
|
|
8762
10244
|
processedAttachments: attachments,
|
|
@@ -8771,9 +10253,15 @@ class MessageManager {
|
|
|
8771
10253
|
extraMetadata: {
|
|
8772
10254
|
replyToAuthor: message.mentions.repliedUser ? {
|
|
8773
10255
|
id: message.mentions.repliedUser.id,
|
|
10256
|
+
displayName: message.mentions.repliedUser.globalName ?? message.mentions.repliedUser.username,
|
|
8774
10257
|
username: message.mentions.repliedUser.username,
|
|
8775
10258
|
isBot: message.mentions.repliedUser.bot
|
|
8776
|
-
} : undefined
|
|
10259
|
+
} : undefined,
|
|
10260
|
+
replyToMessageId: message.reference?.messageId ? createUniqueUuid4(this.runtime, message.reference.messageId) : undefined,
|
|
10261
|
+
replyToExternalMessageId: message.reference?.messageId,
|
|
10262
|
+
replyToSenderId: message.mentions.repliedUser?.id,
|
|
10263
|
+
replyToSenderName: message.mentions.repliedUser?.globalName ?? message.mentions.repliedUser?.username,
|
|
10264
|
+
replyToSenderUserName: message.mentions.repliedUser?.username
|
|
8777
10265
|
}
|
|
8778
10266
|
});
|
|
8779
10267
|
if (!newMessage) {
|
|
@@ -8784,37 +10272,154 @@ class MessageManager {
|
|
|
8784
10272
|
}, "Failed to build memory from message");
|
|
8785
10273
|
return;
|
|
8786
10274
|
}
|
|
10275
|
+
await this.runtime.ensureConnection({
|
|
10276
|
+
entityId: newMessage.entityId,
|
|
10277
|
+
roomId,
|
|
10278
|
+
roomName,
|
|
10279
|
+
userName,
|
|
10280
|
+
name,
|
|
10281
|
+
source: "discord",
|
|
10282
|
+
channelId: message.channel.id,
|
|
10283
|
+
messageServerId: messageServerId ? stringToUuid2(messageServerId) : undefined,
|
|
10284
|
+
type,
|
|
10285
|
+
worldId: createUniqueUuid4(this.runtime, messageServerId ?? roomId),
|
|
10286
|
+
worldName: message.guild?.name,
|
|
10287
|
+
userId: message.author.id,
|
|
10288
|
+
metadata: buildDiscordWorldMetadata(this.runtime, message.guild?.ownerId ?? undefined)
|
|
10289
|
+
});
|
|
10290
|
+
if (ignoresOtherTarget) {
|
|
10291
|
+
await this.persistInboundMemory(newMessage);
|
|
10292
|
+
this.runtime.logger.debug({
|
|
10293
|
+
src: "plugin:discord",
|
|
10294
|
+
agentId: this.runtime.agentId,
|
|
10295
|
+
channelId: message.channel.id
|
|
10296
|
+
}, "Ignoring message that targets another mentioned user");
|
|
10297
|
+
return;
|
|
10298
|
+
}
|
|
10299
|
+
if (strictModeEnabled && !strictModeShouldProcess) {
|
|
10300
|
+
await this.persistInboundMemory(newMessage);
|
|
10301
|
+
this.runtime.logger.debug({
|
|
10302
|
+
src: "plugin:discord",
|
|
10303
|
+
agentId: this.runtime.agentId,
|
|
10304
|
+
channelId: message.channel.id
|
|
10305
|
+
}, "Strict mode: ignoring message (no mention or reply)");
|
|
10306
|
+
return;
|
|
10307
|
+
}
|
|
10308
|
+
if (strictModeEnabled) {
|
|
10309
|
+
this.runtime.logger.debug({
|
|
10310
|
+
src: "plugin:discord",
|
|
10311
|
+
agentId: this.runtime.agentId,
|
|
10312
|
+
channelId: message.channel.id
|
|
10313
|
+
}, "Strict mode: processing message");
|
|
10314
|
+
}
|
|
10315
|
+
const canSendResult = canSendMessage(message.channel);
|
|
10316
|
+
if (!canSendResult.canSend) {
|
|
10317
|
+
await this.persistInboundMemory(newMessage);
|
|
10318
|
+
return this.runtime.logger.warn({
|
|
10319
|
+
src: "plugin:discord",
|
|
10320
|
+
agentId: this.runtime.agentId,
|
|
10321
|
+
channelId: message.channel.id,
|
|
10322
|
+
reason: canSendResult.reason
|
|
10323
|
+
}, "Cannot send message to channel");
|
|
10324
|
+
}
|
|
8787
10325
|
const messageId = newMessage.id;
|
|
10326
|
+
const channel = message.channel;
|
|
10327
|
+
const typingController = createTypingController(channel);
|
|
10328
|
+
const clientUserId = this.client.user?.id;
|
|
10329
|
+
const useReactions = shouldShowStatusReaction(this.statusReactionScope, message, clientUserId);
|
|
10330
|
+
const statusReactions = useReactions ? createStatusReactionController(message) : null;
|
|
10331
|
+
const draftStream = this.draftStreamingEnabled ? createDraftStreamController({
|
|
10332
|
+
log: (entry) => this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, entry),
|
|
10333
|
+
warn: (entry) => this.runtime.logger.warn({ src: "plugin:discord", agentId: this.runtime.agentId }, entry)
|
|
10334
|
+
}) : null;
|
|
10335
|
+
let typingStarted = false;
|
|
10336
|
+
let responseEmitted = false;
|
|
10337
|
+
const finalizePendingDraft = async () => {
|
|
10338
|
+
if (draftStream?.isStarted() && !draftStream.isDone()) {
|
|
10339
|
+
await draftStream.finalize("");
|
|
10340
|
+
}
|
|
10341
|
+
};
|
|
10342
|
+
const abortPendingDraft = async () => {
|
|
10343
|
+
if (draftStream?.isStarted() && !draftStream.isDone()) {
|
|
10344
|
+
await draftStream.abort("An error occurred while generating the response.");
|
|
10345
|
+
}
|
|
10346
|
+
};
|
|
10347
|
+
if (draftStream) {
|
|
10348
|
+
await draftStream.start(channel, message.id);
|
|
10349
|
+
}
|
|
10350
|
+
statusReactions?.setQueued();
|
|
10351
|
+
statusReactions?.setThinking();
|
|
8788
10352
|
const callback = async (content) => {
|
|
8789
10353
|
try {
|
|
8790
10354
|
if (content.target && typeof content.target === "string" && content.target.toLowerCase() !== "discord") {
|
|
8791
10355
|
return [];
|
|
8792
10356
|
}
|
|
8793
|
-
if (!
|
|
8794
|
-
|
|
8795
|
-
|
|
10357
|
+
if (message.id && !content.inReplyTo) {
|
|
10358
|
+
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
10359
|
+
}
|
|
10360
|
+
if (typeof content.text === "string" && content.text.length > 0) {
|
|
10361
|
+
content.text = stripReasoningTags(content.text);
|
|
10362
|
+
}
|
|
10363
|
+
const textContent = normalizeDiscordMessageText(content.text);
|
|
10364
|
+
const hasText = textContent.trim().length > 0;
|
|
10365
|
+
const attachmentCount = Array.isArray(content.attachments) ? content.attachments.filter((media) => Boolean(media?.url)).length : 0;
|
|
10366
|
+
if (!hasText && attachmentCount === 0) {
|
|
10367
|
+
return [];
|
|
10368
|
+
}
|
|
10369
|
+
if (!typingStarted) {
|
|
10370
|
+
typingStarted = true;
|
|
10371
|
+
typingController.start();
|
|
10372
|
+
}
|
|
10373
|
+
if (hasText && content.inReplyTo) {
|
|
10374
|
+
const dedupKey = `${content.inReplyTo}::${textContent.replace(/\s+/g, " ").trim()}`;
|
|
10375
|
+
const callbackDedup = message;
|
|
10376
|
+
callbackDedup._miladySentReplyKeys ??= new Set;
|
|
10377
|
+
if (callbackDedup._miladySentReplyKeys.has(dedupKey)) {
|
|
10378
|
+
const err = new Error("Duplicate callback reply for same inbound message — runtime emitted identical text twice");
|
|
10379
|
+
this.runtime.logger.error({
|
|
10380
|
+
src: "plugin:discord",
|
|
10381
|
+
agentId: this.runtime.agentId,
|
|
10382
|
+
messageId: message.id,
|
|
10383
|
+
inReplyTo: content.inReplyTo,
|
|
10384
|
+
textPreview: textContent.replace(/\s+/g, " ").trim().slice(0, 200)
|
|
10385
|
+
}, err.message);
|
|
10386
|
+
throw err;
|
|
10387
|
+
}
|
|
10388
|
+
callbackDedup._miladySentReplyKeys.add(dedupKey);
|
|
10389
|
+
}
|
|
10390
|
+
const files = [];
|
|
10391
|
+
if (content.attachments && content.attachments.length > 0) {
|
|
10392
|
+
for (const media of content.attachments) {
|
|
10393
|
+
if (media.url) {
|
|
10394
|
+
const fileName = getAttachmentFileName(media);
|
|
10395
|
+
files.push(new AttachmentBuilder(media.url, { name: fileName }));
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
let messages = [];
|
|
10400
|
+
if (draftStream?.isStarted() && !draftStream.isDone()) {
|
|
10401
|
+
if (hasText || files.length === 0) {
|
|
10402
|
+
messages = await draftStream.finalize(textContent);
|
|
10403
|
+
} else {
|
|
10404
|
+
await finalizePendingDraft();
|
|
10405
|
+
}
|
|
10406
|
+
if (files.length > 0) {
|
|
8796
10407
|
try {
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
}
|
|
8800
|
-
|
|
10408
|
+
const attachmentMessage = await channel.send({
|
|
10409
|
+
files
|
|
10410
|
+
});
|
|
10411
|
+
messages.push(attachmentMessage);
|
|
10412
|
+
} catch (error) {
|
|
8801
10413
|
this.runtime.logger.warn({
|
|
8802
10414
|
src: "plugin:discord",
|
|
8803
10415
|
agentId: this.runtime.agentId,
|
|
8804
|
-
error:
|
|
8805
|
-
}, "
|
|
10416
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10417
|
+
}, "Failed to send Discord attachments after draft finalize");
|
|
8806
10418
|
}
|
|
8807
|
-
}
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
if (message.id && !content.inReplyTo) {
|
|
8812
|
-
content.inReplyTo = createUniqueUuid4(this.runtime, message.id);
|
|
8813
|
-
}
|
|
8814
|
-
let messages = [];
|
|
8815
|
-
if (content && content.channelType === "DM") {
|
|
8816
|
-
const u = await this.client.users.fetch(message.author.id);
|
|
8817
|
-
if (!u) {
|
|
10419
|
+
}
|
|
10420
|
+
} else if (content && content.channelType === "DM") {
|
|
10421
|
+
const user = await this.client.users.fetch(message.author.id);
|
|
10422
|
+
if (!user) {
|
|
8818
10423
|
this.runtime.logger.warn({
|
|
8819
10424
|
src: "plugin:discord",
|
|
8820
10425
|
agentId: this.runtime.agentId,
|
|
@@ -8822,41 +10427,21 @@ class MessageManager {
|
|
|
8822
10427
|
}, "User not found for DM");
|
|
8823
10428
|
return [];
|
|
8824
10429
|
}
|
|
8825
|
-
const
|
|
8826
|
-
if (content.attachments && content.attachments.length > 0) {
|
|
8827
|
-
for (const media of content.attachments) {
|
|
8828
|
-
if (media.url) {
|
|
8829
|
-
const fileName = getAttachmentFileName(media);
|
|
8830
|
-
files.push(new AttachmentBuilder(media.url, { name: fileName }));
|
|
8831
|
-
}
|
|
8832
|
-
}
|
|
8833
|
-
}
|
|
8834
|
-
const textContent = content.text ?? "";
|
|
8835
|
-
const hasText = textContent.trim().length > 0;
|
|
8836
|
-
if (!hasText && files.length === 0) {
|
|
8837
|
-
this.runtime.logger.warn({ src: "plugin:discord", agentId: this.runtime.agentId }, "Skipping DM response: no text or attachments");
|
|
8838
|
-
return [];
|
|
8839
|
-
}
|
|
8840
|
-
const dmMessage = await u.send({
|
|
10430
|
+
const dmMessage = await user.send({
|
|
8841
10431
|
content: textContent,
|
|
8842
10432
|
files: files.length > 0 ? files : undefined
|
|
8843
10433
|
});
|
|
8844
10434
|
messages = [dmMessage];
|
|
8845
10435
|
} else {
|
|
8846
|
-
const files = [];
|
|
8847
|
-
if (content.attachments && content.attachments.length > 0) {
|
|
8848
|
-
for (const media of content.attachments) {
|
|
8849
|
-
if (media.url) {
|
|
8850
|
-
const fileName = getAttachmentFileName(media);
|
|
8851
|
-
files.push(new AttachmentBuilder(media.url, { name: fileName }));
|
|
8852
|
-
}
|
|
8853
|
-
}
|
|
8854
|
-
}
|
|
8855
10436
|
if (!message.id) {
|
|
8856
10437
|
this.runtime.logger.warn({ src: "plugin:discord", agentId: this.runtime.agentId }, "Cannot send message: message.id is missing");
|
|
8857
10438
|
return [];
|
|
8858
10439
|
}
|
|
8859
|
-
messages = await sendMessageInChunks(channel,
|
|
10440
|
+
messages = await sendMessageInChunks(channel, textContent, message.id, files, undefined, this.runtime);
|
|
10441
|
+
}
|
|
10442
|
+
const attemptedSend = hasText || attachmentCount > 0;
|
|
10443
|
+
if (attemptedSend && messages.length === 0) {
|
|
10444
|
+
throw new Error("Discord response callback completed without sending any messages");
|
|
8860
10445
|
}
|
|
8861
10446
|
const memories = [];
|
|
8862
10447
|
for (const m of messages) {
|
|
@@ -8868,7 +10453,8 @@ class MessageManager {
|
|
|
8868
10453
|
agentId: this.runtime.agentId,
|
|
8869
10454
|
content: {
|
|
8870
10455
|
...content,
|
|
8871
|
-
|
|
10456
|
+
source: "discord",
|
|
10457
|
+
text: m.content || textContent || " ",
|
|
8872
10458
|
actions,
|
|
8873
10459
|
inReplyTo: messageId,
|
|
8874
10460
|
url: m.url,
|
|
@@ -8883,9 +10469,28 @@ class MessageManager {
|
|
|
8883
10469
|
for (const m of memories) {
|
|
8884
10470
|
await this.runtime.createMemory(m, "messages");
|
|
8885
10471
|
}
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
10472
|
+
responseEmitted = memories.length > 0;
|
|
10473
|
+
typingController.stop();
|
|
10474
|
+
statusReactions?.setDone();
|
|
10475
|
+
if (hasText) {
|
|
10476
|
+
const replyPreview = textContent.replace(/\s+/g, " ").slice(0, 200);
|
|
10477
|
+
const callbackState = message;
|
|
10478
|
+
callbackState._miladyReplyCount = (callbackState._miladyReplyCount ?? 0) + 1;
|
|
10479
|
+
if (callbackState._miladyReplyCount === 1) {
|
|
10480
|
+
callbackState._miladyFirstReplyPreview = replyPreview;
|
|
10481
|
+
} else {
|
|
10482
|
+
this.runtime.logger.warn({
|
|
10483
|
+
src: "plugin:discord",
|
|
10484
|
+
agentId: this.runtime.agentId,
|
|
10485
|
+
messageId: message.id,
|
|
10486
|
+
channelId: message.channel.id,
|
|
10487
|
+
replyCount: callbackState._miladyReplyCount,
|
|
10488
|
+
firstPreview: callbackState._miladyFirstReplyPreview,
|
|
10489
|
+
currentPreview: replyPreview,
|
|
10490
|
+
action: typeof content?.action === "string" ? String(content.action) : undefined,
|
|
10491
|
+
source: typeof content.source === "string" ? content.source : undefined
|
|
10492
|
+
}, "Multiple Discord replies emitted for one inbound message");
|
|
10493
|
+
}
|
|
8889
10494
|
}
|
|
8890
10495
|
return memories;
|
|
8891
10496
|
} catch (error) {
|
|
@@ -8894,23 +10499,27 @@ class MessageManager {
|
|
|
8894
10499
|
agentId: this.runtime.agentId,
|
|
8895
10500
|
error: error instanceof Error ? error.message : String(error)
|
|
8896
10501
|
}, "Error handling message callback");
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
return [];
|
|
10502
|
+
typingController.stop();
|
|
10503
|
+
statusReactions?.setError();
|
|
10504
|
+
await abortPendingDraft();
|
|
10505
|
+
throw error;
|
|
8902
10506
|
}
|
|
8903
10507
|
};
|
|
8904
10508
|
const messagingAPI = getMessagingAPI(this.runtime);
|
|
8905
10509
|
const messageService = getMessageService(this.runtime);
|
|
8906
|
-
if (
|
|
8907
|
-
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using
|
|
10510
|
+
if (messageService) {
|
|
10511
|
+
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using messageService API");
|
|
10512
|
+
await messageService.handleMessage(this.runtime, newMessage, callback);
|
|
10513
|
+
} else if (messagingAPI?.handleMessage) {
|
|
10514
|
+
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using messaging API handleMessage");
|
|
10515
|
+
await messagingAPI.handleMessage(this.runtime.agentId, newMessage, {
|
|
10516
|
+
onResponse: callback
|
|
10517
|
+
});
|
|
10518
|
+
} else if (messagingAPI?.sendMessage) {
|
|
10519
|
+
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using messaging API sendMessage");
|
|
8908
10520
|
await messagingAPI.sendMessage(this.runtime.agentId, newMessage, {
|
|
8909
10521
|
onResponse: callback
|
|
8910
10522
|
});
|
|
8911
|
-
} else if (messageService) {
|
|
8912
|
-
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using messageService API");
|
|
8913
|
-
await messageService.handleMessage(this.runtime, newMessage, callback);
|
|
8914
10523
|
} else {
|
|
8915
10524
|
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Using event-based message handling");
|
|
8916
10525
|
await this.runtime.emitEvent([EventType.MESSAGE_RECEIVED], {
|
|
@@ -8920,13 +10529,11 @@ class MessageManager {
|
|
|
8920
10529
|
source: "discord"
|
|
8921
10530
|
});
|
|
8922
10531
|
}
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
}
|
|
8929
|
-
}, 30000);
|
|
10532
|
+
if (!responseEmitted) {
|
|
10533
|
+
typingController.stop();
|
|
10534
|
+
statusReactions?.setDone();
|
|
10535
|
+
await finalizePendingDraft();
|
|
10536
|
+
}
|
|
8930
10537
|
} catch (error) {
|
|
8931
10538
|
this.runtime.logger.error({
|
|
8932
10539
|
src: "plugin:discord",
|
|
@@ -8947,30 +10554,6 @@ Embed #${parseInt(i, 10) + 1}:
|
|
|
8947
10554
|
processedContent += ` Title:${embed.title ?? "(none)"}
|
|
8948
10555
|
`;
|
|
8949
10556
|
processedContent += ` Description:${embed.description ?? "(none)"}
|
|
8950
|
-
`;
|
|
8951
|
-
}
|
|
8952
|
-
}
|
|
8953
|
-
if (message.reference) {
|
|
8954
|
-
let messageId;
|
|
8955
|
-
if (message.reference.messageId) {
|
|
8956
|
-
messageId = createUniqueUuid4(this.runtime, message.reference.messageId);
|
|
8957
|
-
} else {
|
|
8958
|
-
try {
|
|
8959
|
-
const refMsg = await message.fetchReference();
|
|
8960
|
-
messageId = createUniqueUuid4(this.runtime, refMsg.id);
|
|
8961
|
-
} catch {}
|
|
8962
|
-
}
|
|
8963
|
-
if (messageId) {
|
|
8964
|
-
processedContent += `
|
|
8965
|
-
Referencing MessageID ${messageId} (discord: ${message.reference.messageId})`;
|
|
8966
|
-
if (message.reference.channelId !== message.channel.id) {
|
|
8967
|
-
const roomId = createUniqueUuid4(this.runtime, message.reference.channelId);
|
|
8968
|
-
processedContent += ` in channel ${roomId}`;
|
|
8969
|
-
}
|
|
8970
|
-
if (message.reference.guildId && message.guild && message.reference.guildId !== message.guild.id) {
|
|
8971
|
-
processedContent += ` in guild ${message.reference.guildId}`;
|
|
8972
|
-
}
|
|
8973
|
-
processedContent += `
|
|
8974
10557
|
`;
|
|
8975
10558
|
}
|
|
8976
10559
|
}
|
|
@@ -9028,7 +10611,7 @@ Referencing MessageID ${messageId} (discord: ${message.reference.messageId})`;
|
|
|
9028
10611
|
} else {
|
|
9029
10612
|
const browserService = this.runtime.getService(ServiceType4.BROWSER);
|
|
9030
10613
|
if (!browserService) {
|
|
9031
|
-
this.runtime.logger.
|
|
10614
|
+
this.runtime.logger.debug({ src: "plugin:discord", agentId: this.runtime.agentId }, "Skipping URL enrichment because browser service is unavailable");
|
|
9032
10615
|
continue;
|
|
9033
10616
|
}
|
|
9034
10617
|
try {
|
|
@@ -9121,74 +10704,723 @@ function getState(perm, allow, deny) {
|
|
|
9121
10704
|
if (deny.includes(perm)) {
|
|
9122
10705
|
return "DENY";
|
|
9123
10706
|
}
|
|
9124
|
-
return "NEUTRAL";
|
|
9125
|
-
}
|
|
9126
|
-
function overwriteToChanges(ow, isDelete = false) {
|
|
9127
|
-
const changes = [];
|
|
9128
|
-
for (const p of ow.allow.toArray()) {
|
|
9129
|
-
changes.push({
|
|
9130
|
-
permission: p,
|
|
9131
|
-
oldState: isDelete ? "ALLOW" : "NEUTRAL",
|
|
9132
|
-
newState: isDelete ? "NEUTRAL" : "ALLOW"
|
|
10707
|
+
return "NEUTRAL";
|
|
10708
|
+
}
|
|
10709
|
+
function overwriteToChanges(ow, isDelete = false) {
|
|
10710
|
+
const changes = [];
|
|
10711
|
+
for (const p of ow.allow.toArray()) {
|
|
10712
|
+
changes.push({
|
|
10713
|
+
permission: p,
|
|
10714
|
+
oldState: isDelete ? "ALLOW" : "NEUTRAL",
|
|
10715
|
+
newState: isDelete ? "NEUTRAL" : "ALLOW"
|
|
10716
|
+
});
|
|
10717
|
+
}
|
|
10718
|
+
for (const p of ow.deny.toArray()) {
|
|
10719
|
+
changes.push({
|
|
10720
|
+
permission: p,
|
|
10721
|
+
oldState: isDelete ? "DENY" : "NEUTRAL",
|
|
10722
|
+
newState: isDelete ? "NEUTRAL" : "DENY"
|
|
10723
|
+
});
|
|
10724
|
+
}
|
|
10725
|
+
return changes;
|
|
10726
|
+
}
|
|
10727
|
+
function diffOverwrites(oldOw, newOw) {
|
|
10728
|
+
if (!oldOw && !newOw) {
|
|
10729
|
+
return { changes: [], action: "UPDATE" };
|
|
10730
|
+
}
|
|
10731
|
+
if (!oldOw && newOw) {
|
|
10732
|
+
return { changes: overwriteToChanges(newOw, false), action: "CREATE" };
|
|
10733
|
+
}
|
|
10734
|
+
if (oldOw && !newOw) {
|
|
10735
|
+
return { changes: overwriteToChanges(oldOw, true), action: "DELETE" };
|
|
10736
|
+
}
|
|
10737
|
+
const changes = [];
|
|
10738
|
+
const oldAllow = oldOw?.allow.toArray();
|
|
10739
|
+
const oldDeny = oldOw?.deny.toArray();
|
|
10740
|
+
const newAllow = newOw?.allow.toArray();
|
|
10741
|
+
const newDeny = newOw?.deny.toArray();
|
|
10742
|
+
const allPerms = new Set([...oldAllow, ...oldDeny, ...newAllow, ...newDeny]);
|
|
10743
|
+
for (const perm of allPerms) {
|
|
10744
|
+
const oldState = getState(perm, oldAllow, oldDeny);
|
|
10745
|
+
const newState = getState(perm, newAllow, newDeny);
|
|
10746
|
+
if (oldState !== newState) {
|
|
10747
|
+
changes.push({ permission: perm, oldState, newState });
|
|
10748
|
+
}
|
|
10749
|
+
}
|
|
10750
|
+
return { changes, action: "UPDATE" };
|
|
10751
|
+
}
|
|
10752
|
+
function diffRolePermissions(oldRole, newRole) {
|
|
10753
|
+
const oldPerms = oldRole.permissions.toArray();
|
|
10754
|
+
const newPerms = newRole.permissions.toArray();
|
|
10755
|
+
const changes = [];
|
|
10756
|
+
for (const p of newPerms) {
|
|
10757
|
+
if (!oldPerms.includes(p)) {
|
|
10758
|
+
changes.push({ permission: p, oldState: "NEUTRAL", newState: "ALLOW" });
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10761
|
+
for (const p of oldPerms) {
|
|
10762
|
+
if (!newPerms.includes(p)) {
|
|
10763
|
+
changes.push({ permission: p, oldState: "ALLOW", newState: "NEUTRAL" });
|
|
10764
|
+
}
|
|
10765
|
+
}
|
|
10766
|
+
return changes;
|
|
10767
|
+
}
|
|
10768
|
+
function diffMemberRoles(oldMember, newMember) {
|
|
10769
|
+
const oldRoles = oldMember.roles.cache;
|
|
10770
|
+
const newRoles = newMember.roles.cache;
|
|
10771
|
+
return {
|
|
10772
|
+
added: [...newRoles.filter((r) => !oldRoles.has(r.id)).values()],
|
|
10773
|
+
removed: [...oldRoles.filter((r) => !newRoles.has(r.id)).values()]
|
|
10774
|
+
};
|
|
10775
|
+
}
|
|
10776
|
+
|
|
10777
|
+
// profileSync.ts
|
|
10778
|
+
import { createHash } from "node:crypto";
|
|
10779
|
+
import fs5 from "node:fs/promises";
|
|
10780
|
+
import os3 from "node:os";
|
|
10781
|
+
import path3 from "node:path";
|
|
10782
|
+
var MAX_PROFILE_AVATAR_BYTES = 8 * 1024 * 1024;
|
|
10783
|
+
var PROFILE_SYNC_STATE_FILE = "discord-profile-sync.v1.json";
|
|
10784
|
+
var DEFAULT_DISCORD_PROFILE_AVATAR = "/avatars/eliza.png";
|
|
10785
|
+
function resolveUserPath(input) {
|
|
10786
|
+
const trimmed = input.trim();
|
|
10787
|
+
if (!trimmed) {
|
|
10788
|
+
return trimmed;
|
|
10789
|
+
}
|
|
10790
|
+
if (trimmed.startsWith("~")) {
|
|
10791
|
+
return path3.resolve(trimmed.replace(/^~(?=$|[\\/])/, os3.homedir()));
|
|
10792
|
+
}
|
|
10793
|
+
return path3.resolve(trimmed);
|
|
10794
|
+
}
|
|
10795
|
+
function resolveStateDirFromEnv(env = process.env) {
|
|
10796
|
+
const override = env.MILADY_STATE_DIR?.trim() || env.ELIZA_STATE_DIR?.trim();
|
|
10797
|
+
if (override) {
|
|
10798
|
+
return resolveUserPath(override);
|
|
10799
|
+
}
|
|
10800
|
+
const namespace = env.MILADY_NAMESPACE?.trim() || env.ELIZA_NAMESPACE?.trim() || "milady";
|
|
10801
|
+
return path3.join(os3.homedir(), `.${namespace}`);
|
|
10802
|
+
}
|
|
10803
|
+
function resolveProfileSyncStatePath(env = process.env) {
|
|
10804
|
+
return path3.join(resolveStateDirFromEnv(env), "cache", PROFILE_SYNC_STATE_FILE);
|
|
10805
|
+
}
|
|
10806
|
+
async function readPersistedProfileSyncState(env = process.env) {
|
|
10807
|
+
try {
|
|
10808
|
+
const raw = await fs5.readFile(resolveProfileSyncStatePath(env), "utf8");
|
|
10809
|
+
const parsed = JSON.parse(raw);
|
|
10810
|
+
return {
|
|
10811
|
+
...typeof parsed.avatarHash === "string" ? { avatarHash: parsed.avatarHash } : {},
|
|
10812
|
+
...typeof parsed.username === "string" ? { username: parsed.username } : {}
|
|
10813
|
+
};
|
|
10814
|
+
} catch {
|
|
10815
|
+
return {};
|
|
10816
|
+
}
|
|
10817
|
+
}
|
|
10818
|
+
async function writePersistedProfileSyncState(state, env = process.env) {
|
|
10819
|
+
const statePath = resolveProfileSyncStatePath(env);
|
|
10820
|
+
await fs5.mkdir(path3.dirname(statePath), { recursive: true });
|
|
10821
|
+
await fs5.writeFile(statePath, JSON.stringify(state, null, 2), {
|
|
10822
|
+
encoding: "utf8",
|
|
10823
|
+
mode: 384
|
|
10824
|
+
});
|
|
10825
|
+
}
|
|
10826
|
+
function normalizeDesiredDiscordName(runtime, settings) {
|
|
10827
|
+
const configured = settings.profileName?.trim();
|
|
10828
|
+
if (configured) {
|
|
10829
|
+
return configured;
|
|
10830
|
+
}
|
|
10831
|
+
const characterName = runtime.character.name?.trim();
|
|
10832
|
+
if (characterName) {
|
|
10833
|
+
return characterName;
|
|
10834
|
+
}
|
|
10835
|
+
const characterUserName = runtime.character.username?.trim();
|
|
10836
|
+
return characterUserName || undefined;
|
|
10837
|
+
}
|
|
10838
|
+
function readNestedOptionalString(value, pathSegments) {
|
|
10839
|
+
let cursor = value;
|
|
10840
|
+
for (const segment of pathSegments) {
|
|
10841
|
+
if (!cursor || typeof cursor !== "object") {
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
cursor = cursor[segment];
|
|
10845
|
+
}
|
|
10846
|
+
return typeof cursor === "string" && cursor.trim().length > 0 ? cursor.trim() : undefined;
|
|
10847
|
+
}
|
|
10848
|
+
function normalizeDesiredDiscordAvatarSource(runtime, settings) {
|
|
10849
|
+
const configured = settings.profileAvatar?.trim();
|
|
10850
|
+
if (configured) {
|
|
10851
|
+
return configured;
|
|
10852
|
+
}
|
|
10853
|
+
const character = runtime.character;
|
|
10854
|
+
const fromIdentity = readNestedOptionalString(character, ["identity", "avatar"]) ?? readNestedOptionalString(character, ["settings", "identity", "avatar"]);
|
|
10855
|
+
if (fromIdentity) {
|
|
10856
|
+
return fromIdentity;
|
|
10857
|
+
}
|
|
10858
|
+
const fromCharacter = readNestedOptionalString(character, ["avatar"]) ?? readNestedOptionalString(character, ["settings", "avatar"]);
|
|
10859
|
+
if (fromCharacter) {
|
|
10860
|
+
return fromCharacter;
|
|
10861
|
+
}
|
|
10862
|
+
return DEFAULT_DISCORD_PROFILE_AVATAR;
|
|
10863
|
+
}
|
|
10864
|
+
function extractDataUriPayload(source) {
|
|
10865
|
+
const match = source.match(/^data:image\/[^;]+;base64,([a-z0-9+/=]+)$/i);
|
|
10866
|
+
if (!match) {
|
|
10867
|
+
return null;
|
|
10868
|
+
}
|
|
10869
|
+
return Buffer.from(match[1], "base64");
|
|
10870
|
+
}
|
|
10871
|
+
function buildLocalAvatarPathCandidates(source) {
|
|
10872
|
+
const candidates = new Set;
|
|
10873
|
+
const trimmed = source.trim();
|
|
10874
|
+
if (!trimmed) {
|
|
10875
|
+
return [];
|
|
10876
|
+
}
|
|
10877
|
+
candidates.add(resolveUserPath(trimmed));
|
|
10878
|
+
const normalized = trimmed.replace(/\\/g, "/");
|
|
10879
|
+
const withoutLeadingSlash = normalized.replace(/^\/+/, "");
|
|
10880
|
+
if (!withoutLeadingSlash) {
|
|
10881
|
+
return [...candidates];
|
|
10882
|
+
}
|
|
10883
|
+
const repoRoot = process.cwd();
|
|
10884
|
+
const publicRoots = [
|
|
10885
|
+
path3.join(repoRoot, "cloud", "public"),
|
|
10886
|
+
path3.join(repoRoot, "apps", "web", "public"),
|
|
10887
|
+
path3.join(repoRoot, "public")
|
|
10888
|
+
];
|
|
10889
|
+
for (const publicRoot of publicRoots) {
|
|
10890
|
+
candidates.add(path3.join(publicRoot, withoutLeadingSlash));
|
|
10891
|
+
if (!withoutLeadingSlash.startsWith("avatars/")) {
|
|
10892
|
+
candidates.add(path3.join(publicRoot, "avatars", withoutLeadingSlash));
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
return [...candidates];
|
|
10896
|
+
}
|
|
10897
|
+
async function readAvatarBytesFromLocalCandidates(source) {
|
|
10898
|
+
let lastError = null;
|
|
10899
|
+
for (const candidate of buildLocalAvatarPathCandidates(source)) {
|
|
10900
|
+
try {
|
|
10901
|
+
return await fs5.readFile(candidate);
|
|
10902
|
+
} catch (error) {
|
|
10903
|
+
lastError = error;
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
if (lastError instanceof Error) {
|
|
10907
|
+
throw lastError;
|
|
10908
|
+
}
|
|
10909
|
+
throw new Error(`Unable to resolve Discord profile avatar source: ${source}`);
|
|
10910
|
+
}
|
|
10911
|
+
async function loadDiscordProfileAvatarBytes(source, runtime) {
|
|
10912
|
+
const trimmed = source.trim();
|
|
10913
|
+
if (!trimmed) {
|
|
10914
|
+
return null;
|
|
10915
|
+
}
|
|
10916
|
+
let bytes = extractDataUriPayload(trimmed);
|
|
10917
|
+
if (!bytes) {
|
|
10918
|
+
let remoteUrl = null;
|
|
10919
|
+
try {
|
|
10920
|
+
const parsedUrl = new URL(trimmed);
|
|
10921
|
+
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
|
10922
|
+
remoteUrl = parsedUrl;
|
|
10923
|
+
}
|
|
10924
|
+
} catch {}
|
|
10925
|
+
if (remoteUrl) {
|
|
10926
|
+
const fetchImpl = runtime.fetch ?? globalThis.fetch;
|
|
10927
|
+
if (typeof fetchImpl !== "function") {
|
|
10928
|
+
return null;
|
|
10929
|
+
}
|
|
10930
|
+
const response = await fetchImpl(trimmed, {
|
|
10931
|
+
headers: { Accept: "image/*" }
|
|
10932
|
+
});
|
|
10933
|
+
if (!response.ok) {
|
|
10934
|
+
throw new Error(`HTTP ${response.status}`);
|
|
10935
|
+
}
|
|
10936
|
+
const contentType = response.headers.get("content-type");
|
|
10937
|
+
if (!contentType?.toLowerCase().startsWith("image/")) {
|
|
10938
|
+
throw new Error(`Expected image content-type, got ${contentType ?? "unknown"}`);
|
|
10939
|
+
}
|
|
10940
|
+
bytes = Buffer.from(await response.arrayBuffer());
|
|
10941
|
+
} else {
|
|
10942
|
+
bytes = await readAvatarBytesFromLocalCandidates(trimmed);
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
if (!bytes || bytes.length === 0) {
|
|
10946
|
+
return null;
|
|
10947
|
+
}
|
|
10948
|
+
if (bytes.length > MAX_PROFILE_AVATAR_BYTES) {
|
|
10949
|
+
throw new Error(`Discord profile avatar exceeds ${MAX_PROFILE_AVATAR_BYTES} bytes`);
|
|
10950
|
+
}
|
|
10951
|
+
return {
|
|
10952
|
+
bytes,
|
|
10953
|
+
hash: createHash("sha256").update(bytes).digest("hex")
|
|
10954
|
+
};
|
|
10955
|
+
}
|
|
10956
|
+
async function syncDiscordClientProfile(runtime, clientUser, settings) {
|
|
10957
|
+
if (settings.syncProfile === false) {
|
|
10958
|
+
return;
|
|
10959
|
+
}
|
|
10960
|
+
const desiredName = normalizeDesiredDiscordName(runtime, settings);
|
|
10961
|
+
const desiredAvatarSource = normalizeDesiredDiscordAvatarSource(runtime, settings);
|
|
10962
|
+
if (!desiredName && !desiredAvatarSource) {
|
|
10963
|
+
return;
|
|
10964
|
+
}
|
|
10965
|
+
const persisted = await readPersistedProfileSyncState();
|
|
10966
|
+
const nextState = { ...persisted };
|
|
10967
|
+
let stateChanged = false;
|
|
10968
|
+
if (desiredName) {
|
|
10969
|
+
if (persisted.username !== desiredName) {
|
|
10970
|
+
if (clientUser.username !== desiredName) {
|
|
10971
|
+
if (typeof clientUser.setUsername === "function") {
|
|
10972
|
+
await clientUser.setUsername(desiredName);
|
|
10973
|
+
runtime.logger.info({
|
|
10974
|
+
src: "plugin:discord",
|
|
10975
|
+
agentId: runtime.agentId,
|
|
10976
|
+
discordProfileName: desiredName
|
|
10977
|
+
}, "Synchronized Discord bot username from connector settings");
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
nextState.username = desiredName;
|
|
10981
|
+
stateChanged = true;
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
if (desiredAvatarSource) {
|
|
10985
|
+
const avatar = await loadDiscordProfileAvatarBytes(desiredAvatarSource, runtime);
|
|
10986
|
+
if (avatar && persisted.avatarHash !== avatar.hash) {
|
|
10987
|
+
if (typeof clientUser.setAvatar === "function") {
|
|
10988
|
+
await clientUser.setAvatar(avatar.bytes);
|
|
10989
|
+
runtime.logger.info({
|
|
10990
|
+
src: "plugin:discord",
|
|
10991
|
+
agentId: runtime.agentId
|
|
10992
|
+
}, "Synchronized Discord bot avatar from connector settings");
|
|
10993
|
+
}
|
|
10994
|
+
nextState.avatarHash = avatar.hash;
|
|
10995
|
+
stateChanged = true;
|
|
10996
|
+
}
|
|
10997
|
+
}
|
|
10998
|
+
if (stateChanged) {
|
|
10999
|
+
await writePersistedProfileSyncState(nextState);
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
|
|
11003
|
+
// slash-commands.ts
|
|
11004
|
+
import { createUniqueUuid as createUniqueUuid5 } from "@elizaos/core";
|
|
11005
|
+
import { ApplicationCommandOptionType } from "discord.js";
|
|
11006
|
+
var OPTION_TYPE_MAP = {
|
|
11007
|
+
string: ApplicationCommandOptionType.String,
|
|
11008
|
+
number: ApplicationCommandOptionType.Number,
|
|
11009
|
+
boolean: ApplicationCommandOptionType.Boolean,
|
|
11010
|
+
user: ApplicationCommandOptionType.User,
|
|
11011
|
+
channel: ApplicationCommandOptionType.Channel,
|
|
11012
|
+
role: ApplicationCommandOptionType.Role
|
|
11013
|
+
};
|
|
11014
|
+
var commands = new Map;
|
|
11015
|
+
var cooldowns = new Map;
|
|
11016
|
+
var FALLBACK_KNOWN_MODELS = [
|
|
11017
|
+
"gpt-4o",
|
|
11018
|
+
"gpt-4o-mini",
|
|
11019
|
+
"gpt-4",
|
|
11020
|
+
"gpt-3.5-turbo",
|
|
11021
|
+
"claude-sonnet-4-20250514",
|
|
11022
|
+
"claude-opus-4-20250514",
|
|
11023
|
+
"claude-3.5-haiku",
|
|
11024
|
+
"llama-3.1-70b",
|
|
11025
|
+
"llama-3.1-8b",
|
|
11026
|
+
"gemini-2.5-pro",
|
|
11027
|
+
"gemini-2.5-flash",
|
|
11028
|
+
"mistral-large",
|
|
11029
|
+
"mistral-medium"
|
|
11030
|
+
];
|
|
11031
|
+
function parseStringList(value) {
|
|
11032
|
+
if (Array.isArray(value)) {
|
|
11033
|
+
return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
11034
|
+
}
|
|
11035
|
+
if (typeof value !== "string") {
|
|
11036
|
+
return [];
|
|
11037
|
+
}
|
|
11038
|
+
const trimmed = value.trim();
|
|
11039
|
+
if (!trimmed) {
|
|
11040
|
+
return [];
|
|
11041
|
+
}
|
|
11042
|
+
try {
|
|
11043
|
+
const parsed = JSON.parse(trimmed);
|
|
11044
|
+
if (Array.isArray(parsed)) {
|
|
11045
|
+
return parseStringList(parsed);
|
|
11046
|
+
}
|
|
11047
|
+
} catch {}
|
|
11048
|
+
return trimmed.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
11049
|
+
}
|
|
11050
|
+
function getKnownModels(runtime) {
|
|
11051
|
+
const configured = parseStringList(runtime.getSetting("DISCORD_KNOWN_MODELS")) ?? parseStringList(runtime.getSetting("KNOWN_MODELS"));
|
|
11052
|
+
return configured.length > 0 ? configured : [...FALLBACK_KNOWN_MODELS];
|
|
11053
|
+
}
|
|
11054
|
+
var helpCommand = {
|
|
11055
|
+
name: "help",
|
|
11056
|
+
description: "Show available commands and usage information",
|
|
11057
|
+
ephemeral: true,
|
|
11058
|
+
async execute(interaction) {
|
|
11059
|
+
const lines = [`**Available Commands**
|
|
11060
|
+
`];
|
|
11061
|
+
for (const [name, command] of commands) {
|
|
11062
|
+
const options = command.options ? command.options.map((option) => option.required ? `<${option.name}>` : `[${option.name}]`).join(" ") : "";
|
|
11063
|
+
lines.push(`/${name}${options ? ` ${options}` : ""} - ${command.description}`);
|
|
11064
|
+
}
|
|
11065
|
+
await interaction.reply({ content: lines.join(`
|
|
11066
|
+
`), ephemeral: true });
|
|
11067
|
+
}
|
|
11068
|
+
};
|
|
11069
|
+
var statusCommand = {
|
|
11070
|
+
name: "status",
|
|
11071
|
+
description: "Show the bot's current status and uptime",
|
|
11072
|
+
ephemeral: true,
|
|
11073
|
+
async execute(interaction, runtime) {
|
|
11074
|
+
const uptimeMs = process.uptime() * 1000;
|
|
11075
|
+
const hours = Math.floor(uptimeMs / 3600000);
|
|
11076
|
+
const minutes = Math.floor(uptimeMs % 3600000 / 60000);
|
|
11077
|
+
const seconds = Math.floor(uptimeMs % 60000 / 1000);
|
|
11078
|
+
const memoryUsage = process.memoryUsage();
|
|
11079
|
+
const heapMb = (memoryUsage.heapUsed / 1024 / 1024).toFixed(1);
|
|
11080
|
+
const rssMb = (memoryUsage.rss / 1024 / 1024).toFixed(1);
|
|
11081
|
+
await interaction.reply({
|
|
11082
|
+
content: [
|
|
11083
|
+
"**Bot Status**",
|
|
11084
|
+
`- Agent: **${runtime.character?.name ?? "Unknown"}**`,
|
|
11085
|
+
`- Uptime: **${hours}h ${minutes}m ${seconds}s**`,
|
|
11086
|
+
`- Memory: **${heapMb} MB** heap / **${rssMb} MB** RSS`,
|
|
11087
|
+
`- Guilds: **${interaction.client.guilds.cache.size}**`,
|
|
11088
|
+
`- Node: **${process.version}**`,
|
|
11089
|
+
`- Platform: **${process.platform}**`
|
|
11090
|
+
].join(`
|
|
11091
|
+
`),
|
|
11092
|
+
ephemeral: true
|
|
11093
|
+
});
|
|
11094
|
+
}
|
|
11095
|
+
};
|
|
11096
|
+
var searchCommand = {
|
|
11097
|
+
name: "search",
|
|
11098
|
+
description: "Search conversation history in this channel",
|
|
11099
|
+
options: [
|
|
11100
|
+
{
|
|
11101
|
+
name: "query",
|
|
11102
|
+
description: "The search term or phrase",
|
|
11103
|
+
type: "string",
|
|
11104
|
+
required: true
|
|
11105
|
+
},
|
|
11106
|
+
{
|
|
11107
|
+
name: "limit",
|
|
11108
|
+
description: "Maximum results to return (default: 5)",
|
|
11109
|
+
type: "number"
|
|
11110
|
+
}
|
|
11111
|
+
],
|
|
11112
|
+
ephemeral: true,
|
|
11113
|
+
cooldown: 10,
|
|
11114
|
+
async execute(interaction, runtime) {
|
|
11115
|
+
const query = interaction.options.getString("query", true);
|
|
11116
|
+
const limit = interaction.options.getNumber("limit") || 5;
|
|
11117
|
+
await interaction.deferReply({ ephemeral: true });
|
|
11118
|
+
try {
|
|
11119
|
+
const roomId = createUniqueUuid5(runtime, interaction.channelId);
|
|
11120
|
+
const memories = await runtime.getMemories({
|
|
11121
|
+
tableName: "messages",
|
|
11122
|
+
roomId,
|
|
11123
|
+
count: 100
|
|
11124
|
+
});
|
|
11125
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
11126
|
+
const filteredMemories = memories.filter((memory) => (memory.content?.text ?? "").toLowerCase().includes(normalizedQuery));
|
|
11127
|
+
if (filteredMemories.length === 0) {
|
|
11128
|
+
await interaction.editReply({
|
|
11129
|
+
content: `No results found for **"${query}"**`
|
|
11130
|
+
});
|
|
11131
|
+
return;
|
|
11132
|
+
}
|
|
11133
|
+
const results = filteredMemories.slice(0, limit).map((memory, index) => {
|
|
11134
|
+
const text = memory.content?.text || "(no text)";
|
|
11135
|
+
const truncated = text.length > 120 ? `${text.slice(0, 120)}...` : text;
|
|
11136
|
+
const date = memory.createdAt ? new Date(memory.createdAt).toLocaleDateString() : "unknown date";
|
|
11137
|
+
return `**${index + 1}.** ${truncated}
|
|
11138
|
+
_${date}_`;
|
|
11139
|
+
});
|
|
11140
|
+
await interaction.editReply({
|
|
11141
|
+
content: `**Search results for "${query}"**
|
|
11142
|
+
|
|
11143
|
+
${results.join(`
|
|
11144
|
+
|
|
11145
|
+
`)}`
|
|
11146
|
+
});
|
|
11147
|
+
} catch (error) {
|
|
11148
|
+
await interaction.editReply({
|
|
11149
|
+
content: `Search failed: ${error instanceof Error ? error.message : String(error)}`
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
11153
|
+
};
|
|
11154
|
+
var clearCommand = {
|
|
11155
|
+
name: "clear",
|
|
11156
|
+
description: "Explain how context clearing works in this channel",
|
|
11157
|
+
ephemeral: true,
|
|
11158
|
+
async execute(interaction) {
|
|
11159
|
+
await interaction.reply({
|
|
11160
|
+
content: "Context clearing is not wired up for Discord yet. I can search recent messages with `/search`, but I won't pretend existing memory was deleted.",
|
|
11161
|
+
ephemeral: true
|
|
9133
11162
|
});
|
|
9134
11163
|
}
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
11164
|
+
};
|
|
11165
|
+
var settingsCommand = {
|
|
11166
|
+
name: "settings",
|
|
11167
|
+
description: "View the current Discord bot settings",
|
|
11168
|
+
options: [
|
|
11169
|
+
{
|
|
11170
|
+
name: "action",
|
|
11171
|
+
description: "What to do",
|
|
11172
|
+
type: "string",
|
|
11173
|
+
required: true,
|
|
11174
|
+
choices: [
|
|
11175
|
+
{ name: "View current settings", value: "view" },
|
|
11176
|
+
{ name: "Toggle response-only-on-mention", value: "toggle-mention" },
|
|
11177
|
+
{ name: "Toggle ignore-bots", value: "toggle-ignore-bots" }
|
|
11178
|
+
]
|
|
11179
|
+
}
|
|
11180
|
+
],
|
|
11181
|
+
ephemeral: true,
|
|
11182
|
+
async execute(interaction, runtime) {
|
|
11183
|
+
const action = interaction.options.getString("action", true);
|
|
11184
|
+
if (action === "view") {
|
|
11185
|
+
await interaction.reply({
|
|
11186
|
+
content: [
|
|
11187
|
+
"**Current Settings**",
|
|
11188
|
+
`- Respond only to mentions: **${runtime.getSetting("DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS") ?? "false"}**`,
|
|
11189
|
+
`- Ignore bot messages: **${runtime.getSetting("DISCORD_SHOULD_IGNORE_BOT_MESSAGES") ?? "true"}**`,
|
|
11190
|
+
`- Allowed channels: **${runtime.getSetting("CHANNEL_IDS") ?? "(all channels)"}**`,
|
|
11191
|
+
`- Agent name: **${runtime.character?.name ?? "Unknown"}**`
|
|
11192
|
+
].join(`
|
|
11193
|
+
`),
|
|
11194
|
+
ephemeral: true
|
|
11195
|
+
});
|
|
11196
|
+
return;
|
|
11197
|
+
}
|
|
11198
|
+
const content = action === "toggle-mention" ? "Respond-only-on-mention is controlled by `DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS`. Update the setting and restart to change it." : "Ignore-bots is controlled by `DISCORD_SHOULD_IGNORE_BOT_MESSAGES`. Update the setting and restart to change it.";
|
|
11199
|
+
await interaction.reply({ content, ephemeral: true });
|
|
11200
|
+
}
|
|
11201
|
+
};
|
|
11202
|
+
var setupCommand = {
|
|
11203
|
+
name: "setup",
|
|
11204
|
+
description: "Set up API credentials for third-party services",
|
|
11205
|
+
options: [
|
|
11206
|
+
{
|
|
11207
|
+
name: "service",
|
|
11208
|
+
description: "Service to configure (github, vercel, cloudflare, anthropic, openai, fal, custom)",
|
|
11209
|
+
type: "string",
|
|
11210
|
+
choices: [
|
|
11211
|
+
{ name: "GitHub", value: "github" },
|
|
11212
|
+
{ name: "Vercel", value: "vercel" },
|
|
11213
|
+
{ name: "Cloudflare", value: "cloudflare" },
|
|
11214
|
+
{ name: "Anthropic", value: "anthropic" },
|
|
11215
|
+
{ name: "OpenAI", value: "openai" },
|
|
11216
|
+
{ name: "fal.ai", value: "fal" },
|
|
11217
|
+
{ name: "Custom", value: "custom" }
|
|
11218
|
+
]
|
|
11219
|
+
}
|
|
11220
|
+
],
|
|
11221
|
+
ephemeral: true,
|
|
11222
|
+
async execute(interaction) {
|
|
11223
|
+
const service = interaction.options.getString("service");
|
|
11224
|
+
if (!service) {
|
|
11225
|
+
const services = listPresets().filter((presetName) => presetName !== "generic").map((presetName) => {
|
|
11226
|
+
const preset = getPreset(presetName);
|
|
11227
|
+
return `- **${preset?.displayName ?? presetName}** - \`/setup service:${presetName}\``;
|
|
11228
|
+
});
|
|
11229
|
+
await interaction.reply({
|
|
11230
|
+
content: [
|
|
11231
|
+
"**Credential Setup**",
|
|
11232
|
+
"Choose a service to configure:",
|
|
11233
|
+
"",
|
|
11234
|
+
...services,
|
|
11235
|
+
"- **Custom** - `/setup service:custom`",
|
|
11236
|
+
"",
|
|
11237
|
+
"I'll walk you through it in DMs to keep your keys safe."
|
|
11238
|
+
].join(`
|
|
11239
|
+
`),
|
|
11240
|
+
ephemeral: true
|
|
11241
|
+
});
|
|
11242
|
+
return;
|
|
11243
|
+
}
|
|
11244
|
+
await interaction.reply({
|
|
11245
|
+
content: `Starting **${service}** setup. Check your DMs - I'll walk you through it there to keep your keys private.`,
|
|
11246
|
+
ephemeral: true
|
|
9140
11247
|
});
|
|
11248
|
+
try {
|
|
11249
|
+
const dmChannel = await interaction.user.createDM();
|
|
11250
|
+
const presetKey = service === "custom" ? "generic" : service;
|
|
11251
|
+
const preset = getPreset(presetKey);
|
|
11252
|
+
if (!preset) {
|
|
11253
|
+
await dmChannel.send(`I don't have a preset for "${service}". Try \`/setup\` to see available services.`);
|
|
11254
|
+
return;
|
|
11255
|
+
}
|
|
11256
|
+
const field = preset.fields[0];
|
|
11257
|
+
const helpLine = preset.helpUrl ? `Here's where to get one: ${preset.helpUrl}` : "";
|
|
11258
|
+
await dmChannel.send([
|
|
11259
|
+
`Setting up **${preset.displayName}** credentials.`,
|
|
11260
|
+
preset.helpText,
|
|
11261
|
+
helpLine,
|
|
11262
|
+
"",
|
|
11263
|
+
`Please paste your **${field.label}** here. ${field.secret ? "I'll delete your message right after reading it." : ""}`,
|
|
11264
|
+
"",
|
|
11265
|
+
"(Type `cancel` to abort setup)"
|
|
11266
|
+
].filter(Boolean).join(`
|
|
11267
|
+
`));
|
|
11268
|
+
} catch {
|
|
11269
|
+
try {
|
|
11270
|
+
await interaction.followUp({
|
|
11271
|
+
content: "I couldn't send you a DM. Make sure your DMs are open and try again.",
|
|
11272
|
+
ephemeral: true
|
|
11273
|
+
});
|
|
11274
|
+
} catch {}
|
|
11275
|
+
}
|
|
9141
11276
|
}
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
11277
|
+
};
|
|
11278
|
+
var modelCommand = {
|
|
11279
|
+
name: "model",
|
|
11280
|
+
description: "View or change the active AI model",
|
|
11281
|
+
options: [
|
|
11282
|
+
{
|
|
11283
|
+
name: "name",
|
|
11284
|
+
description: "Model name to switch to (leave empty to view current)",
|
|
11285
|
+
type: "string",
|
|
11286
|
+
autocomplete: true
|
|
11287
|
+
}
|
|
11288
|
+
],
|
|
11289
|
+
ephemeral: true,
|
|
11290
|
+
async execute(interaction, runtime) {
|
|
11291
|
+
const modelName = interaction.options.getString("name");
|
|
11292
|
+
if (!modelName) {
|
|
11293
|
+
await interaction.reply({
|
|
11294
|
+
content: `**Current model:** \`${runtime.getSetting("MODEL") ?? runtime.getSetting("DEFAULT_MODEL") ?? "(not configured)"}\``,
|
|
11295
|
+
ephemeral: true
|
|
11296
|
+
});
|
|
11297
|
+
return;
|
|
11298
|
+
}
|
|
11299
|
+
await interaction.reply({
|
|
11300
|
+
content: `Model switching to \`${modelName}\` is noted. The runtime model is still controlled by configuration, so update the setting and restart to switch permanently.`,
|
|
11301
|
+
ephemeral: true
|
|
11302
|
+
});
|
|
11303
|
+
},
|
|
11304
|
+
async autocomplete(interaction) {
|
|
11305
|
+
const runtime = interaction.client.runtime;
|
|
11306
|
+
const models = runtime ? getKnownModels(runtime) : [...FALLBACK_KNOWN_MODELS];
|
|
11307
|
+
const focused = interaction.options.getFocused().toLowerCase();
|
|
11308
|
+
const filtered = models.filter((model) => model.toLowerCase().includes(focused)).slice(0, 25);
|
|
11309
|
+
await interaction.respond(filtered.map((model) => ({ name: model, value: model })));
|
|
9147
11310
|
}
|
|
9148
|
-
|
|
9149
|
-
|
|
11311
|
+
};
|
|
11312
|
+
function registerBuiltins() {
|
|
11313
|
+
for (const command of [
|
|
11314
|
+
helpCommand,
|
|
11315
|
+
statusCommand,
|
|
11316
|
+
searchCommand,
|
|
11317
|
+
clearCommand,
|
|
11318
|
+
settingsCommand,
|
|
11319
|
+
modelCommand,
|
|
11320
|
+
setupCommand
|
|
11321
|
+
]) {
|
|
11322
|
+
commands.set(command.name, command);
|
|
9150
11323
|
}
|
|
9151
|
-
|
|
9152
|
-
|
|
11324
|
+
}
|
|
11325
|
+
registerBuiltins();
|
|
11326
|
+
function toDiscordSlashCommand(command) {
|
|
11327
|
+
const options = command.options?.map((option) => ({
|
|
11328
|
+
name: option.name,
|
|
11329
|
+
description: option.description,
|
|
11330
|
+
type: OPTION_TYPE_MAP[option.type] ?? ApplicationCommandOptionType.String,
|
|
11331
|
+
required: option.required ?? false,
|
|
11332
|
+
...option.choices ? { choices: option.choices } : {},
|
|
11333
|
+
...option.autocomplete ? { autocomplete: option.autocomplete } : {}
|
|
11334
|
+
}));
|
|
11335
|
+
return {
|
|
11336
|
+
name: command.name,
|
|
11337
|
+
description: command.description,
|
|
11338
|
+
options
|
|
11339
|
+
};
|
|
11340
|
+
}
|
|
11341
|
+
async function registerSlashCommands(runtime) {
|
|
11342
|
+
const registered = [...commands.values()].map(toDiscordSlashCommand);
|
|
11343
|
+
runtime.logger.info({
|
|
11344
|
+
src: "slash-commands",
|
|
11345
|
+
count: registered.length,
|
|
11346
|
+
names: [...commands.keys()]
|
|
11347
|
+
}, "Registering built-in slash commands");
|
|
11348
|
+
await runtime.emitEvent(["DISCORD_REGISTER_COMMANDS"], {
|
|
11349
|
+
runtime,
|
|
11350
|
+
source: "discord",
|
|
11351
|
+
commands: registered
|
|
11352
|
+
});
|
|
11353
|
+
}
|
|
11354
|
+
async function handleSlashCommand(interaction, runtime) {
|
|
11355
|
+
const command = commands.get(interaction.commandName);
|
|
11356
|
+
if (!command) {
|
|
11357
|
+
return;
|
|
9153
11358
|
}
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
for (const perm of allPerms) {
|
|
9161
|
-
const oldState = getState(perm, oldAllow, oldDeny);
|
|
9162
|
-
const newState = getState(perm, newAllow, newDeny);
|
|
9163
|
-
if (oldState !== newState) {
|
|
9164
|
-
changes.push({ permission: perm, oldState, newState });
|
|
11359
|
+
if (command.cooldown && command.cooldown > 0) {
|
|
11360
|
+
const userId = interaction.user.id;
|
|
11361
|
+
let commandCooldowns = cooldowns.get(command.name);
|
|
11362
|
+
if (!commandCooldowns) {
|
|
11363
|
+
commandCooldowns = new Map;
|
|
11364
|
+
cooldowns.set(command.name, commandCooldowns);
|
|
9165
11365
|
}
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
changes.push({ permission: p, oldState: "NEUTRAL", newState: "ALLOW" });
|
|
11366
|
+
const lastUsed = commandCooldowns.get(userId);
|
|
11367
|
+
const now = Date.now();
|
|
11368
|
+
if (lastUsed && now - lastUsed < command.cooldown * 1000) {
|
|
11369
|
+
const remaining = Math.ceil((command.cooldown * 1000 - (now - lastUsed)) / 1000);
|
|
11370
|
+
await interaction.reply({
|
|
11371
|
+
content: `Please wait **${remaining}s** before using \`/${command.name}\` again.`,
|
|
11372
|
+
ephemeral: true
|
|
11373
|
+
});
|
|
11374
|
+
return;
|
|
9176
11375
|
}
|
|
11376
|
+
commandCooldowns.set(userId, now);
|
|
11377
|
+
setTimeout(() => {
|
|
11378
|
+
if (commandCooldowns?.get(userId) === now) {
|
|
11379
|
+
commandCooldowns.delete(userId);
|
|
11380
|
+
}
|
|
11381
|
+
}, command.cooldown * 1000);
|
|
9177
11382
|
}
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
11383
|
+
if (command.ownerOnly) {
|
|
11384
|
+
const guild = interaction.guild;
|
|
11385
|
+
if (guild && interaction.user.id !== guild.ownerId) {
|
|
11386
|
+
await interaction.reply({
|
|
11387
|
+
content: "This command can only be used by the server owner.",
|
|
11388
|
+
ephemeral: true
|
|
11389
|
+
});
|
|
11390
|
+
return;
|
|
9181
11391
|
}
|
|
9182
11392
|
}
|
|
9183
|
-
|
|
11393
|
+
try {
|
|
11394
|
+
await command.execute(interaction, runtime);
|
|
11395
|
+
} catch (error) {
|
|
11396
|
+
const content = `An error occurred while running \`/${command.name}\`: ${error instanceof Error ? error.message : String(error)}`;
|
|
11397
|
+
runtime.logger.error({
|
|
11398
|
+
src: "slash-commands",
|
|
11399
|
+
commandName: command.name,
|
|
11400
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11401
|
+
}, "Error executing slash command");
|
|
11402
|
+
try {
|
|
11403
|
+
if (interaction.deferred) {
|
|
11404
|
+
await interaction.editReply({ content });
|
|
11405
|
+
} else if (!interaction.replied) {
|
|
11406
|
+
await interaction.reply({ content, ephemeral: true });
|
|
11407
|
+
}
|
|
11408
|
+
} catch {}
|
|
11409
|
+
}
|
|
9184
11410
|
}
|
|
9185
|
-
function
|
|
9186
|
-
const
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
11411
|
+
async function handleAutocomplete(interaction) {
|
|
11412
|
+
const command = commands.get(interaction.commandName);
|
|
11413
|
+
if (!command?.autocomplete) {
|
|
11414
|
+
await interaction.respond([]);
|
|
11415
|
+
return;
|
|
11416
|
+
}
|
|
11417
|
+
try {
|
|
11418
|
+
await command.autocomplete(interaction);
|
|
11419
|
+
} catch {
|
|
11420
|
+
try {
|
|
11421
|
+
await interaction.respond([]);
|
|
11422
|
+
} catch {}
|
|
11423
|
+
}
|
|
9192
11424
|
}
|
|
9193
11425
|
|
|
9194
11426
|
// voice.ts
|
|
@@ -9205,15 +11437,15 @@ import {
|
|
|
9205
11437
|
VoiceConnectionStatus
|
|
9206
11438
|
} from "@discordjs/voice";
|
|
9207
11439
|
import {
|
|
9208
|
-
ChannelType as
|
|
9209
|
-
createUniqueUuid as
|
|
11440
|
+
ChannelType as ChannelType7,
|
|
11441
|
+
createUniqueUuid as createUniqueUuid6,
|
|
9210
11442
|
EventType as EventType2,
|
|
9211
|
-
logger as
|
|
11443
|
+
logger as logger4,
|
|
9212
11444
|
ModelType as ModelType20,
|
|
9213
|
-
stringToUuid as
|
|
11445
|
+
stringToUuid as stringToUuid3
|
|
9214
11446
|
} from "@elizaos/core";
|
|
9215
11447
|
import {
|
|
9216
|
-
ChannelType as
|
|
11448
|
+
ChannelType as DiscordChannelType5
|
|
9217
11449
|
} from "discord.js";
|
|
9218
11450
|
import prism from "prism-media";
|
|
9219
11451
|
var DECODE_FRAME_SIZE = 1024;
|
|
@@ -9222,16 +11454,16 @@ function createOpusDecoder(options) {
|
|
|
9222
11454
|
try {
|
|
9223
11455
|
return new prism.opus.Decoder(options);
|
|
9224
11456
|
} catch (error) {
|
|
9225
|
-
|
|
11457
|
+
logger4.warn({
|
|
9226
11458
|
src: "plugin:discord:service:voice",
|
|
9227
11459
|
error: error instanceof Error ? error.message : String(error)
|
|
9228
11460
|
}, "Failed to create opus decoder");
|
|
9229
11461
|
try {
|
|
9230
11462
|
const { generateDependencyReport } = __require("@discordjs/voice");
|
|
9231
11463
|
const report = generateDependencyReport();
|
|
9232
|
-
|
|
11464
|
+
logger4.debug({ src: "plugin:discord:service:voice", report }, "Voice dependency report");
|
|
9233
11465
|
} catch (reportError) {
|
|
9234
|
-
|
|
11466
|
+
logger4.warn({
|
|
9235
11467
|
src: "plugin:discord:service:voice",
|
|
9236
11468
|
error: reportError instanceof Error ? reportError.message : String(reportError)
|
|
9237
11469
|
}, "Could not generate dependency report");
|
|
@@ -9278,7 +11510,7 @@ class AudioMonitor {
|
|
|
9278
11510
|
}
|
|
9279
11511
|
});
|
|
9280
11512
|
this.readable.on("end", () => {
|
|
9281
|
-
|
|
11513
|
+
logger4.debug({ src: "plugin:discord:service:voice" }, "AudioMonitor ended");
|
|
9282
11514
|
this.ended = true;
|
|
9283
11515
|
if (this.lastFlagged < 0) {
|
|
9284
11516
|
return;
|
|
@@ -9290,7 +11522,7 @@ class AudioMonitor {
|
|
|
9290
11522
|
if (this.ended) {
|
|
9291
11523
|
return;
|
|
9292
11524
|
}
|
|
9293
|
-
|
|
11525
|
+
logger4.debug({ src: "plugin:discord:service:voice" }, "Speaking stopped");
|
|
9294
11526
|
if (this.lastFlagged < 0) {
|
|
9295
11527
|
return;
|
|
9296
11528
|
}
|
|
@@ -9301,7 +11533,7 @@ class AudioMonitor {
|
|
|
9301
11533
|
return;
|
|
9302
11534
|
}
|
|
9303
11535
|
onStart();
|
|
9304
|
-
|
|
11536
|
+
logger4.debug({ src: "plugin:discord:service:voice" }, "Speaking started");
|
|
9305
11537
|
this.reset();
|
|
9306
11538
|
});
|
|
9307
11539
|
}
|
|
@@ -9361,9 +11593,9 @@ class VoiceManager extends EventEmitter {
|
|
|
9361
11593
|
}
|
|
9362
11594
|
async getChannelType(channel) {
|
|
9363
11595
|
switch (channel.type) {
|
|
9364
|
-
case
|
|
9365
|
-
case
|
|
9366
|
-
return
|
|
11596
|
+
case DiscordChannelType5.GuildVoice:
|
|
11597
|
+
case DiscordChannelType5.GuildStageVoice:
|
|
11598
|
+
return ChannelType7.VOICE_GROUP;
|
|
9367
11599
|
default:
|
|
9368
11600
|
this.runtime.logger.error({
|
|
9369
11601
|
src: "plugin:discord:service:voice",
|
|
@@ -9383,6 +11615,33 @@ class VoiceManager extends EventEmitter {
|
|
|
9383
11615
|
ready: this.ready
|
|
9384
11616
|
}, "VoiceManager ready status changed");
|
|
9385
11617
|
}
|
|
11618
|
+
stop() {
|
|
11619
|
+
if (this.transcriptionTimeout) {
|
|
11620
|
+
clearTimeout(this.transcriptionTimeout);
|
|
11621
|
+
this.transcriptionTimeout = null;
|
|
11622
|
+
}
|
|
11623
|
+
for (const memberId of [...this.activeMonitors.keys()]) {
|
|
11624
|
+
this.stopMonitoringMember(memberId);
|
|
11625
|
+
}
|
|
11626
|
+
for (const connection of new Set(this.connections.values())) {
|
|
11627
|
+
try {
|
|
11628
|
+
connection.destroy();
|
|
11629
|
+
} catch (error) {
|
|
11630
|
+
this.runtime.logger.warn({
|
|
11631
|
+
src: "plugin:discord:service:voice",
|
|
11632
|
+
agentId: this.runtime.agentId,
|
|
11633
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11634
|
+
}, "Failed to destroy Discord voice connection during shutdown");
|
|
11635
|
+
}
|
|
11636
|
+
}
|
|
11637
|
+
this.connections.clear();
|
|
11638
|
+
this.streams.clear();
|
|
11639
|
+
this.userStates.clear();
|
|
11640
|
+
this.processingVoice = false;
|
|
11641
|
+
this.cleanupAudioPlayer(this.activeAudioPlayer);
|
|
11642
|
+
this.removeAllListeners();
|
|
11643
|
+
this.ready = false;
|
|
11644
|
+
}
|
|
9386
11645
|
isReady() {
|
|
9387
11646
|
return this.ready;
|
|
9388
11647
|
}
|
|
@@ -9839,23 +12098,24 @@ class VoiceManager extends EventEmitter {
|
|
|
9839
12098
|
if (!message || message.trim() === "" || message.length < 3) {
|
|
9840
12099
|
return { text: "", actions: ["IGNORE"] };
|
|
9841
12100
|
}
|
|
9842
|
-
const roomId =
|
|
9843
|
-
const uniqueEntityId =
|
|
12101
|
+
const roomId = createUniqueUuid6(this.runtime, channelId);
|
|
12102
|
+
const uniqueEntityId = createUniqueUuid6(this.runtime, entityId);
|
|
9844
12103
|
const type = await this.getChannelType(channel);
|
|
9845
12104
|
await this.runtime.ensureConnection({
|
|
9846
12105
|
entityId: uniqueEntityId,
|
|
9847
12106
|
roomId,
|
|
12107
|
+
roomName: channel.name,
|
|
9848
12108
|
userName,
|
|
9849
12109
|
name,
|
|
9850
12110
|
source: "discord",
|
|
9851
12111
|
channelId,
|
|
9852
|
-
messageServerId:
|
|
12112
|
+
messageServerId: stringToUuid3(channel.guild.id),
|
|
9853
12113
|
type,
|
|
9854
|
-
worldId:
|
|
12114
|
+
worldId: createUniqueUuid6(this.runtime, channel.guild.id),
|
|
9855
12115
|
worldName: channel.guild.name
|
|
9856
12116
|
});
|
|
9857
12117
|
const memory = {
|
|
9858
|
-
id:
|
|
12118
|
+
id: createUniqueUuid6(this.runtime, `${channelId}-voice-message-${Date.now()}`),
|
|
9859
12119
|
agentId: this.runtime.agentId,
|
|
9860
12120
|
entityId: uniqueEntityId,
|
|
9861
12121
|
roomId,
|
|
@@ -9872,12 +12132,14 @@ class VoiceManager extends EventEmitter {
|
|
|
9872
12132
|
};
|
|
9873
12133
|
const callback = async (content, _actionName) => {
|
|
9874
12134
|
try {
|
|
12135
|
+
const responseText = normalizeDiscordMessageText(content.text);
|
|
9875
12136
|
const responseMemory = {
|
|
9876
|
-
id:
|
|
12137
|
+
id: createUniqueUuid6(this.runtime, `${memory.id}-voice-response-${Date.now()}`),
|
|
9877
12138
|
entityId: this.runtime.agentId,
|
|
9878
12139
|
agentId: this.runtime.agentId,
|
|
9879
12140
|
content: {
|
|
9880
12141
|
...content,
|
|
12142
|
+
text: responseText || undefined,
|
|
9881
12143
|
name: this.runtime.character.name,
|
|
9882
12144
|
inReplyTo: memory.id,
|
|
9883
12145
|
isVoiceMessage: true,
|
|
@@ -9889,8 +12151,8 @@ class VoiceManager extends EventEmitter {
|
|
|
9889
12151
|
const responseMemoryContentText = responseMemory.content.text;
|
|
9890
12152
|
if (responseMemoryContentText?.trim()) {
|
|
9891
12153
|
await this.runtime.createMemory(responseMemory, "messages");
|
|
9892
|
-
if (
|
|
9893
|
-
const responseStream = await this.runtime.useModel(ModelType20.TEXT_TO_SPEECH,
|
|
12154
|
+
if (responseText) {
|
|
12155
|
+
const responseStream = await this.runtime.useModel(ModelType20.TEXT_TO_SPEECH, responseText);
|
|
9894
12156
|
if (responseStream) {
|
|
9895
12157
|
const buffer = Buffer.isBuffer(responseStream) ? responseStream : Buffer.from(responseStream);
|
|
9896
12158
|
const readable = Readable.from(buffer);
|
|
@@ -9954,7 +12216,7 @@ class VoiceManager extends EventEmitter {
|
|
|
9954
12216
|
}
|
|
9955
12217
|
}
|
|
9956
12218
|
if (!chosenChannel) {
|
|
9957
|
-
const channels = (await guild.channels.fetch()).filter((channel) => channel && channel.type ===
|
|
12219
|
+
const channels = (await guild.channels.fetch()).filter((channel) => channel && channel.type === DiscordChannelType5.GuildVoice);
|
|
9958
12220
|
for (const [, channel] of channels) {
|
|
9959
12221
|
const voiceChannel = channel;
|
|
9960
12222
|
if (voiceChannel.members.size > 0 && (chosenChannel === null || voiceChannel.members.size > chosenChannel.members.size)) {
|
|
@@ -10048,7 +12310,7 @@ class VoiceManager extends EventEmitter {
|
|
|
10048
12310
|
await interaction.editReply("Could not find guild.");
|
|
10049
12311
|
return;
|
|
10050
12312
|
}
|
|
10051
|
-
const voiceChannel = interaction.guild.channels.cache.find((channel) => channel.id === channelId && channel.type ===
|
|
12313
|
+
const voiceChannel = interaction.guild.channels.cache.find((channel) => channel.id === channelId && channel.type === DiscordChannelType5.GuildVoice);
|
|
10052
12314
|
if (!voiceChannel) {
|
|
10053
12315
|
await interaction.editReply("Voice channel not found!");
|
|
10054
12316
|
return;
|
|
@@ -10095,6 +12357,23 @@ class VoiceManager extends EventEmitter {
|
|
|
10095
12357
|
}
|
|
10096
12358
|
|
|
10097
12359
|
// service.ts
|
|
12360
|
+
var DISCORD_SNOWFLAKE_PATTERN2 = /^\d{15,20}$/;
|
|
12361
|
+
function normalizeDiscordTargetUserId(value) {
|
|
12362
|
+
if (typeof value !== "string") {
|
|
12363
|
+
return null;
|
|
12364
|
+
}
|
|
12365
|
+
const trimmed = value.trim();
|
|
12366
|
+
return DISCORD_SNOWFLAKE_PATTERN2.test(trimmed) ? trimmed : null;
|
|
12367
|
+
}
|
|
12368
|
+
function extractDiscordUserIdFromMetadata(metadata) {
|
|
12369
|
+
if (!metadata || typeof metadata !== "object") {
|
|
12370
|
+
return null;
|
|
12371
|
+
}
|
|
12372
|
+
const record = metadata;
|
|
12373
|
+
const discord = record.discord && typeof record.discord === "object" ? record.discord : null;
|
|
12374
|
+
return normalizeDiscordTargetUserId(discord?.userId) ?? normalizeDiscordTargetUserId(discord?.id) ?? normalizeDiscordTargetUserId(record.originalId);
|
|
12375
|
+
}
|
|
12376
|
+
|
|
10098
12377
|
class DiscordService extends Service {
|
|
10099
12378
|
static serviceType = DISCORD_SERVICE_NAME;
|
|
10100
12379
|
capabilityDescription = "The agent is able to send and receive messages on discord";
|
|
@@ -10102,6 +12381,9 @@ class DiscordService extends Service {
|
|
|
10102
12381
|
character;
|
|
10103
12382
|
messageManager;
|
|
10104
12383
|
voiceManager;
|
|
12384
|
+
messageDebouncer;
|
|
12385
|
+
channelDebouncer;
|
|
12386
|
+
_loginFailed = false;
|
|
10105
12387
|
discordSettings;
|
|
10106
12388
|
userSelections = new Map;
|
|
10107
12389
|
timeouts = [];
|
|
@@ -10111,6 +12393,78 @@ class DiscordService extends Service {
|
|
|
10111
12393
|
allowAllSlashCommands = new Set;
|
|
10112
12394
|
allowedChannelIds;
|
|
10113
12395
|
dynamicChannelIds = new Set;
|
|
12396
|
+
ownerDiscordUserIds = new Set;
|
|
12397
|
+
resolveDiscordEntityId(userId) {
|
|
12398
|
+
return resolveDiscordRuntimeEntityId(this.runtime, userId, this.ownerDiscordUserIds);
|
|
12399
|
+
}
|
|
12400
|
+
async refreshOwnerDiscordUserIds(client) {
|
|
12401
|
+
const explicitSetting = this.runtime.getSetting?.("MILADY_DISCORD_OWNER_USER_IDS_JSON");
|
|
12402
|
+
const hasExplicitSetting = explicitSetting !== undefined && explicitSetting !== null && !(typeof explicitSetting === "string" && explicitSetting.trim() === "");
|
|
12403
|
+
let ownerIds;
|
|
12404
|
+
if (hasExplicitSetting) {
|
|
12405
|
+
ownerIds = parseDiscordOwnerUserIds(Array.isArray(explicitSetting) ? explicitSetting : typeof explicitSetting === "string" ? explicitSetting : [String(explicitSetting)]);
|
|
12406
|
+
} else {
|
|
12407
|
+
const application = client.application && typeof client.application.fetch === "function" ? await client.application.fetch() : client.application;
|
|
12408
|
+
ownerIds = [...new Set(extractDiscordOwnerUserIds(application))];
|
|
12409
|
+
}
|
|
12410
|
+
this.ownerDiscordUserIds = new Set(ownerIds);
|
|
12411
|
+
if (ownerIds.length === 0) {
|
|
12412
|
+
return;
|
|
12413
|
+
}
|
|
12414
|
+
const existingWhitelist = getConnectorAdminWhitelist(this.runtime);
|
|
12415
|
+
const nextDiscordAdmins = [
|
|
12416
|
+
...new Set([...existingWhitelist.discord ?? [], ...ownerIds])
|
|
12417
|
+
];
|
|
12418
|
+
setConnectorAdminWhitelist(this.runtime, {
|
|
12419
|
+
...existingWhitelist,
|
|
12420
|
+
discord: nextDiscordAdmins
|
|
12421
|
+
});
|
|
12422
|
+
this.runtime.logger.info({
|
|
12423
|
+
src: "plugin:discord",
|
|
12424
|
+
agentId: this.runtime.agentId,
|
|
12425
|
+
ownerDiscordUserIds: ownerIds
|
|
12426
|
+
}, "Resolved Discord owner identities for canonical Milady owner mapping");
|
|
12427
|
+
}
|
|
12428
|
+
async resolveDiscordTargetUserId(targetEntityId) {
|
|
12429
|
+
const directId = normalizeDiscordTargetUserId(targetEntityId);
|
|
12430
|
+
if (directId) {
|
|
12431
|
+
return directId;
|
|
12432
|
+
}
|
|
12433
|
+
if (targetEntityId === resolveMiladyOwnerEntityId(this.runtime)) {
|
|
12434
|
+
const knownOwnerUserId = this.ownerDiscordUserIds.values().next().value;
|
|
12435
|
+
if (typeof knownOwnerUserId === "string" && knownOwnerUserId.length > 0) {
|
|
12436
|
+
return knownOwnerUserId;
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
const directEntity = this.runtime.getEntityById ? await this.runtime.getEntityById(targetEntityId) : null;
|
|
12440
|
+
const directMetadataUserId = extractDiscordUserIdFromMetadata(directEntity?.metadata);
|
|
12441
|
+
if (directMetadataUserId) {
|
|
12442
|
+
return directMetadataUserId;
|
|
12443
|
+
}
|
|
12444
|
+
if (typeof this.runtime.getRelationships !== "function") {
|
|
12445
|
+
return null;
|
|
12446
|
+
}
|
|
12447
|
+
const identityLinks = await this.runtime.getRelationships({
|
|
12448
|
+
entityIds: [targetEntityId],
|
|
12449
|
+
tags: ["identity_link"]
|
|
12450
|
+
});
|
|
12451
|
+
for (const relationship of identityLinks) {
|
|
12452
|
+
const metadata = relationship.metadata && typeof relationship.metadata === "object" ? relationship.metadata : null;
|
|
12453
|
+
if (metadata?.status !== "confirmed") {
|
|
12454
|
+
continue;
|
|
12455
|
+
}
|
|
12456
|
+
const linkedEntityId = relationship.sourceEntityId === targetEntityId ? relationship.targetEntityId : relationship.targetEntityId === targetEntityId ? relationship.sourceEntityId : null;
|
|
12457
|
+
if (!linkedEntityId || linkedEntityId === targetEntityId) {
|
|
12458
|
+
continue;
|
|
12459
|
+
}
|
|
12460
|
+
const linkedEntity = this.runtime.getEntityById ? await this.runtime.getEntityById(linkedEntityId) : null;
|
|
12461
|
+
const linkedMetadataUserId = extractDiscordUserIdFromMetadata(linkedEntity?.metadata);
|
|
12462
|
+
if (linkedMetadataUserId) {
|
|
12463
|
+
return linkedMetadataUserId;
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12466
|
+
return null;
|
|
12467
|
+
}
|
|
10114
12468
|
constructor(runtime) {
|
|
10115
12469
|
super(runtime);
|
|
10116
12470
|
this.discordSettings = getDiscordSettings(runtime);
|
|
@@ -10179,13 +12533,26 @@ class DiscordService extends Service {
|
|
|
10179
12533
|
reject(error);
|
|
10180
12534
|
});
|
|
10181
12535
|
});
|
|
10182
|
-
this.clientReadyPromise.catch((
|
|
12536
|
+
this.clientReadyPromise.catch((error) => {
|
|
12537
|
+
this.runtime.logger.error({
|
|
12538
|
+
src: "plugin:discord",
|
|
12539
|
+
agentId: this.runtime.agentId,
|
|
12540
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12541
|
+
}, "Discord client ready promise rejected");
|
|
12542
|
+
this._loginFailed = true;
|
|
12543
|
+
});
|
|
10183
12544
|
this.setupEventListeners();
|
|
10184
12545
|
} catch (error) {
|
|
10185
12546
|
runtime.logger.error(`Error initializing Discord client: ${error instanceof Error ? error.message : String(error)}`);
|
|
10186
12547
|
this.client = null;
|
|
10187
12548
|
}
|
|
10188
12549
|
}
|
|
12550
|
+
isHealthy() {
|
|
12551
|
+
if (this._loginFailed || !this.client) {
|
|
12552
|
+
return false;
|
|
12553
|
+
}
|
|
12554
|
+
return this.client.isReady();
|
|
12555
|
+
}
|
|
10189
12556
|
static async start(runtime) {
|
|
10190
12557
|
const service = new DiscordService(runtime);
|
|
10191
12558
|
return service;
|
|
@@ -10196,22 +12563,31 @@ class DiscordService extends Service {
|
|
|
10196
12563
|
throw new Error("Discord client is not ready.");
|
|
10197
12564
|
}
|
|
10198
12565
|
const client = this.client;
|
|
10199
|
-
if (target.channelId && this.allowedChannelIds && !this.isChannelAllowed(target.channelId)) {
|
|
10200
|
-
runtime.logger.warn(`Channel ${target.channelId} not in allowed list, skipping send`);
|
|
10201
|
-
return;
|
|
10202
|
-
}
|
|
10203
12566
|
let targetChannel = null;
|
|
12567
|
+
let resolvedChannelId = null;
|
|
10204
12568
|
try {
|
|
10205
12569
|
if (target.channelId) {
|
|
12570
|
+
resolvedChannelId = target.channelId;
|
|
10206
12571
|
targetChannel = await client.channels.fetch(target.channelId);
|
|
12572
|
+
} else if (target.roomId) {
|
|
12573
|
+
const room = typeof runtime.getRoom === "function" ? await runtime.getRoom(target.roomId) : null;
|
|
12574
|
+
const roomChannelId = room?.channelId && typeof room.channelId === "string" ? room.channelId : null;
|
|
12575
|
+
if (!roomChannelId) {
|
|
12576
|
+
throw new Error(`Could not resolve Discord channel ID for room ${target.roomId}`);
|
|
12577
|
+
}
|
|
12578
|
+
resolvedChannelId = roomChannelId;
|
|
12579
|
+
targetChannel = await client.channels.fetch(roomChannelId);
|
|
10207
12580
|
} else if (target.entityId) {
|
|
10208
|
-
const discordUserId = target.entityId;
|
|
12581
|
+
const discordUserId = await this.resolveDiscordTargetUserId(target.entityId);
|
|
12582
|
+
if (!discordUserId) {
|
|
12583
|
+
throw new Error(`Could not resolve Discord user ID for runtime entity ${target.entityId}`);
|
|
12584
|
+
}
|
|
10209
12585
|
const user = await client.users.fetch(discordUserId);
|
|
10210
12586
|
if (user) {
|
|
10211
12587
|
targetChannel = user.dmChannel ?? await user.createDM();
|
|
10212
12588
|
}
|
|
10213
12589
|
} else {
|
|
10214
|
-
throw new Error("Discord SendHandler requires channelId or entityId.");
|
|
12590
|
+
throw new Error("Discord SendHandler requires channelId, roomId, or entityId.");
|
|
10215
12591
|
}
|
|
10216
12592
|
if (!targetChannel) {
|
|
10217
12593
|
const targetStr = JSON.stringify(target, (_key, value) => {
|
|
@@ -10222,6 +12598,12 @@ class DiscordService extends Service {
|
|
|
10222
12598
|
});
|
|
10223
12599
|
throw new Error(`Could not find target Discord channel/DM for target: ${targetStr}`);
|
|
10224
12600
|
}
|
|
12601
|
+
const allowedByParentThread = typeof targetChannel.isThread === "function" && targetChannel.isThread() && "parentId" in targetChannel && typeof targetChannel.parentId === "string" && targetChannel.parentId.length > 0 && this.isChannelAllowed(targetChannel.parentId);
|
|
12602
|
+
if (this.allowedChannelIds && !this.isChannelAllowed(targetChannel.id) && !allowedByParentThread) {
|
|
12603
|
+
const resolvedFromText = resolvedChannelId && resolvedChannelId !== targetChannel.id ? ` (resolved from ${resolvedChannelId})` : "";
|
|
12604
|
+
runtime.logger.warn(`Channel ${targetChannel.id}${resolvedFromText} not in allowed list, skipping send`);
|
|
12605
|
+
return;
|
|
12606
|
+
}
|
|
10225
12607
|
if (targetChannel.isTextBased() && !targetChannel.isVoiceBased()) {
|
|
10226
12608
|
if ("send" in targetChannel && typeof targetChannel.send === "function") {
|
|
10227
12609
|
const files = [];
|
|
@@ -10234,11 +12616,12 @@ class DiscordService extends Service {
|
|
|
10234
12616
|
}
|
|
10235
12617
|
}
|
|
10236
12618
|
const sentMessages = [];
|
|
10237
|
-
const roomId =
|
|
12619
|
+
const roomId = createUniqueUuid7(runtime, targetChannel.id);
|
|
10238
12620
|
const channelType = await this.getChannelType(targetChannel);
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
12621
|
+
const textContent = normalizeDiscordMessageText(content.text);
|
|
12622
|
+
if (textContent || files.length > 0) {
|
|
12623
|
+
if (textContent) {
|
|
12624
|
+
const chunks = splitMessage(textContent, MAX_MESSAGE_LENGTH);
|
|
10242
12625
|
if (chunks.length > 1) {
|
|
10243
12626
|
for (let i = 0;i < chunks.length - 1; i++) {
|
|
10244
12627
|
const sent2 = await targetChannel.send(chunks[i]);
|
|
@@ -10267,17 +12650,18 @@ class DiscordService extends Service {
|
|
|
10267
12650
|
}
|
|
10268
12651
|
const targetChannelGuild = "guild" in targetChannel ? targetChannel.guild : null;
|
|
10269
12652
|
const serverId = targetChannelGuild?.id ? targetChannelGuild.id : targetChannel.id;
|
|
10270
|
-
const worldId =
|
|
12653
|
+
const worldId = createUniqueUuid7(runtime, serverId);
|
|
10271
12654
|
const worldName = targetChannelGuild?.name ? targetChannelGuild.name : undefined;
|
|
10272
12655
|
const clientUser = client.user;
|
|
10273
12656
|
await this.runtime.ensureConnection({
|
|
10274
12657
|
entityId: runtime.agentId,
|
|
10275
12658
|
roomId,
|
|
12659
|
+
roomName: "name" in targetChannel && typeof targetChannel.name === "string" ? targetChannel.name : clientUser?.displayName || clientUser?.username || undefined,
|
|
10276
12660
|
userName: clientUser?.username ? clientUser.username : undefined,
|
|
10277
12661
|
name: clientUser?.displayName || clientUser?.username || undefined,
|
|
10278
12662
|
source: "discord",
|
|
10279
12663
|
channelId: targetChannel.id,
|
|
10280
|
-
messageServerId:
|
|
12664
|
+
messageServerId: stringToUuid4(serverId),
|
|
10281
12665
|
type: channelType,
|
|
10282
12666
|
worldId,
|
|
10283
12667
|
worldName
|
|
@@ -10286,12 +12670,12 @@ class DiscordService extends Service {
|
|
|
10286
12670
|
try {
|
|
10287
12671
|
const hasAttachments = sentMsg.attachments.size > 0;
|
|
10288
12672
|
const memory = {
|
|
10289
|
-
id:
|
|
12673
|
+
id: createUniqueUuid7(runtime, sentMsg.id),
|
|
10290
12674
|
entityId: runtime.agentId,
|
|
10291
12675
|
agentId: runtime.agentId,
|
|
10292
12676
|
roomId,
|
|
10293
12677
|
content: {
|
|
10294
|
-
text: sentMsg.content ||
|
|
12678
|
+
text: sentMsg.content || textContent || " ",
|
|
10295
12679
|
url: sentMsg.url,
|
|
10296
12680
|
channelType,
|
|
10297
12681
|
...hasAttachments && content.attachments ? { attachments: content.attachments } : {},
|
|
@@ -10329,6 +12713,64 @@ class DiscordService extends Service {
|
|
|
10329
12713
|
}
|
|
10330
12714
|
const listenCidsRaw = this.runtime.getSetting("DISCORD_LISTEN_CHANNEL_IDS");
|
|
10331
12715
|
const listenCids = Array.isArray(listenCidsRaw) ? listenCidsRaw : listenCidsRaw && typeof listenCidsRaw === "string" && listenCidsRaw.trim() ? listenCidsRaw.trim().split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
12716
|
+
const debounceMsSetting = this.runtime.getSetting("DISCORD_DEBOUNCE_MS");
|
|
12717
|
+
const debounceMs = typeof debounceMsSetting === "number" ? debounceMsSetting : typeof debounceMsSetting === "string" && debounceMsSetting.trim() ? Number.parseInt(debounceMsSetting, 10) || 400 : 400;
|
|
12718
|
+
this.messageDebouncer = createMessageDebouncer((messages) => {
|
|
12719
|
+
if (!this.messageManager || messages.length === 0) {
|
|
12720
|
+
return;
|
|
12721
|
+
}
|
|
12722
|
+
if (messages.length === 1) {
|
|
12723
|
+
this.messageManager.handleMessage(messages[0]);
|
|
12724
|
+
return;
|
|
12725
|
+
}
|
|
12726
|
+
const anchor = messages[0];
|
|
12727
|
+
const combinedText = messages.map((message) => message.content).join(`
|
|
12728
|
+
`);
|
|
12729
|
+
const combined = Object.create(anchor, {
|
|
12730
|
+
content: { value: combinedText, writable: true, enumerable: true }
|
|
12731
|
+
});
|
|
12732
|
+
this.messageManager.handleMessage(combined);
|
|
12733
|
+
}, debounceMs);
|
|
12734
|
+
const channelDebounceMsSetting = this.runtime.getSetting("DISCORD_CHANNEL_DEBOUNCE_MS");
|
|
12735
|
+
const channelDebounceMs = typeof channelDebounceMsSetting === "number" ? channelDebounceMsSetting : typeof channelDebounceMsSetting === "string" && channelDebounceMsSetting.trim() ? Number.parseInt(channelDebounceMsSetting, 10) || 3000 : 3000;
|
|
12736
|
+
const responseCooldownMsSetting = this.runtime.getSetting("DISCORD_RESPONSE_COOLDOWN_MS");
|
|
12737
|
+
const responseCooldownMs = typeof responseCooldownMsSetting === "number" ? responseCooldownMsSetting : typeof responseCooldownMsSetting === "string" && responseCooldownMsSetting.trim() ? Number.parseInt(responseCooldownMsSetting, 10) || 30000 : 30000;
|
|
12738
|
+
this.channelDebouncer = createChannelDebouncer((messages) => {
|
|
12739
|
+
if (!this.messageManager || messages.length === 0) {
|
|
12740
|
+
return;
|
|
12741
|
+
}
|
|
12742
|
+
const clientUser = this.client?.user;
|
|
12743
|
+
const botId = clientUser?.id;
|
|
12744
|
+
const agentName = this.character?.name?.toLowerCase();
|
|
12745
|
+
let anchor;
|
|
12746
|
+
if (botId) {
|
|
12747
|
+
anchor = messages.find((message) => message.mentions?.users?.has(botId) || Boolean(agentName && agentName.length >= 2 && message.content?.toLowerCase().includes(agentName)));
|
|
12748
|
+
if (!anchor) {
|
|
12749
|
+
anchor = messages.find((message) => Boolean(message.reference?.messageId) && message.mentions?.repliedUser?.id === botId);
|
|
12750
|
+
}
|
|
12751
|
+
}
|
|
12752
|
+
anchor ??= messages[messages.length - 1];
|
|
12753
|
+
if (messages.length === 1) {
|
|
12754
|
+
this.messageManager.handleMessage(anchor);
|
|
12755
|
+
} else {
|
|
12756
|
+
const contextLines = messages.filter((message) => message.id !== anchor?.id).map((message) => `${message.member?.displayName ?? message.author.globalName ?? message.author.displayName ?? message.author.username}: ${message.content}`);
|
|
12757
|
+
const combinedText = contextLines.length > 0 ? `[Recent channel context]
|
|
12758
|
+
${contextLines.join(`
|
|
12759
|
+
`)}
|
|
12760
|
+
|
|
12761
|
+
${anchor.content || ""}` : anchor.content || "";
|
|
12762
|
+
const combined = Object.create(anchor, {
|
|
12763
|
+
content: { value: combinedText, writable: true, enumerable: true }
|
|
12764
|
+
});
|
|
12765
|
+
this.messageManager.handleMessage(combined);
|
|
12766
|
+
}
|
|
12767
|
+
this.channelDebouncer?.markResponded(messages[0].channel.id);
|
|
12768
|
+
}, {
|
|
12769
|
+
debounceMs: channelDebounceMs,
|
|
12770
|
+
responseCooldownMs,
|
|
12771
|
+
getBotUserId: () => this.client?.user?.id,
|
|
12772
|
+
botName: this.character?.name
|
|
12773
|
+
});
|
|
10332
12774
|
this.client.on("messageCreate", async (message) => {
|
|
10333
12775
|
const clientUser = this.client?.user;
|
|
10334
12776
|
if (clientUser && message.author.id === clientUser.id || message.author.bot && this.discordSettings.shouldIgnoreBotMessages) {
|
|
@@ -10394,8 +12836,23 @@ class DiscordService extends Service {
|
|
|
10394
12836
|
}
|
|
10395
12837
|
}
|
|
10396
12838
|
try {
|
|
10397
|
-
if (this.messageManager) {
|
|
10398
|
-
|
|
12839
|
+
if (!this.messageManager) {
|
|
12840
|
+
return;
|
|
12841
|
+
}
|
|
12842
|
+
const channelType = message.channel.type;
|
|
12843
|
+
const isDm = channelType === DiscordChannelType6.DM || channelType === DiscordChannelType6.GroupDM;
|
|
12844
|
+
if (isDm) {
|
|
12845
|
+
if (this.messageDebouncer) {
|
|
12846
|
+
this.messageDebouncer.enqueue(message);
|
|
12847
|
+
} else {
|
|
12848
|
+
await this.messageManager.handleMessage(message);
|
|
12849
|
+
}
|
|
12850
|
+
} else if (this.channelDebouncer) {
|
|
12851
|
+
this.channelDebouncer.enqueue(message);
|
|
12852
|
+
} else if (this.messageDebouncer) {
|
|
12853
|
+
this.messageDebouncer.enqueue(message);
|
|
12854
|
+
} else {
|
|
12855
|
+
await this.messageManager.handleMessage(message);
|
|
10399
12856
|
}
|
|
10400
12857
|
} catch (error) {
|
|
10401
12858
|
this.runtime.logger.error({
|
|
@@ -10464,6 +12921,18 @@ class DiscordService extends Service {
|
|
|
10464
12921
|
}
|
|
10465
12922
|
});
|
|
10466
12923
|
this.client.on("interactionCreate", async (interaction) => {
|
|
12924
|
+
if (interaction.isAutocomplete()) {
|
|
12925
|
+
try {
|
|
12926
|
+
await handleAutocomplete(interaction);
|
|
12927
|
+
} catch (error) {
|
|
12928
|
+
this.runtime.logger.error({
|
|
12929
|
+
src: "plugin:discord",
|
|
12930
|
+
agentId: this.runtime.agentId,
|
|
12931
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12932
|
+
}, "Error handling Discord autocomplete interaction");
|
|
12933
|
+
}
|
|
12934
|
+
return;
|
|
12935
|
+
}
|
|
10467
12936
|
const isSlashCommand = interaction.isCommand();
|
|
10468
12937
|
const isModalSubmit = interaction.isModalSubmit();
|
|
10469
12938
|
const isComponent = interaction.isMessageComponent();
|
|
@@ -10571,6 +13040,9 @@ class DiscordService extends Service {
|
|
|
10571
13040
|
}
|
|
10572
13041
|
try {
|
|
10573
13042
|
await this.handleInteractionCreate(interaction);
|
|
13043
|
+
if (interaction.isChatInputCommand()) {
|
|
13044
|
+
await handleSlashCommand(interaction, this.runtime);
|
|
13045
|
+
}
|
|
10574
13046
|
} catch (error) {
|
|
10575
13047
|
this.runtime.logger.error({
|
|
10576
13048
|
src: "plugin:discord",
|
|
@@ -10588,7 +13060,7 @@ class DiscordService extends Service {
|
|
|
10588
13060
|
}
|
|
10589
13061
|
});
|
|
10590
13062
|
const auditLogSetting = this.runtime.getSetting("DISCORD_AUDIT_LOG_ENABLED");
|
|
10591
|
-
const isAuditLogEnabled = auditLogSetting
|
|
13063
|
+
const isAuditLogEnabled = auditLogSetting === "true" || auditLogSetting === true || auditLogSetting === "1" || auditLogSetting === 1;
|
|
10592
13064
|
if (isAuditLogEnabled) {
|
|
10593
13065
|
this.client.on("channelUpdate", async (oldChannel, newChannel) => {
|
|
10594
13066
|
try {
|
|
@@ -10785,8 +13257,8 @@ class DiscordService extends Service {
|
|
|
10785
13257
|
this.runtime.logger.info(`New member joined: ${member.user.username} (${member.id})`);
|
|
10786
13258
|
const guild = member.guild;
|
|
10787
13259
|
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
10788
|
-
const worldId =
|
|
10789
|
-
const entityId =
|
|
13260
|
+
const worldId = createUniqueUuid7(this.runtime, guild.id);
|
|
13261
|
+
const entityId = this.resolveDiscordEntityId(member.id);
|
|
10790
13262
|
this.runtime.emitEvent(["DISCORD_USER_JOINED" /* ENTITY_JOINED */], {
|
|
10791
13263
|
runtime: this.runtime,
|
|
10792
13264
|
entityId,
|
|
@@ -10803,7 +13275,7 @@ class DiscordService extends Service {
|
|
|
10803
13275
|
member
|
|
10804
13276
|
});
|
|
10805
13277
|
}
|
|
10806
|
-
async registerSlashCommands(
|
|
13278
|
+
async registerSlashCommands(commands2) {
|
|
10807
13279
|
await this.clientReadyPromise;
|
|
10808
13280
|
const sanitizeCommandForLogging = (cmd) => {
|
|
10809
13281
|
const sanitized = {
|
|
@@ -10823,11 +13295,11 @@ class DiscordService extends Service {
|
|
|
10823
13295
|
}
|
|
10824
13296
|
return sanitized;
|
|
10825
13297
|
};
|
|
10826
|
-
const sanitizedCommands =
|
|
13298
|
+
const sanitizedCommands = commands2.map(sanitizeCommandForLogging);
|
|
10827
13299
|
this.runtime.logger.debug({
|
|
10828
13300
|
src: "plugin:discord",
|
|
10829
13301
|
agentId: this.runtime.agentId,
|
|
10830
|
-
commandCount:
|
|
13302
|
+
commandCount: commands2.length,
|
|
10831
13303
|
commands: sanitizedCommands
|
|
10832
13304
|
}, "Registering Discord commands");
|
|
10833
13305
|
const clientApplication = this.client?.application;
|
|
@@ -10835,11 +13307,11 @@ class DiscordService extends Service {
|
|
|
10835
13307
|
this.runtime.logger.warn({ src: "plugin:discord", agentId: this.runtime.agentId }, "Cannot register commands - Discord client application not available");
|
|
10836
13308
|
return;
|
|
10837
13309
|
}
|
|
10838
|
-
if (!Array.isArray(
|
|
13310
|
+
if (!Array.isArray(commands2) || commands2.length === 0) {
|
|
10839
13311
|
this.runtime.logger.warn({ src: "plugin:discord", agentId: this.runtime.agentId }, "Cannot register commands - no commands provided");
|
|
10840
13312
|
return;
|
|
10841
13313
|
}
|
|
10842
|
-
for (const cmd of
|
|
13314
|
+
for (const cmd of commands2) {
|
|
10843
13315
|
if (!cmd.name || !cmd.description) {
|
|
10844
13316
|
this.runtime.logger.warn({
|
|
10845
13317
|
src: "plugin:discord",
|
|
@@ -10858,7 +13330,7 @@ class DiscordService extends Service {
|
|
|
10858
13330
|
commandMap.set(cmd.name, cmd);
|
|
10859
13331
|
}
|
|
10860
13332
|
}
|
|
10861
|
-
for (const cmd of
|
|
13333
|
+
for (const cmd of commands2) {
|
|
10862
13334
|
if (cmd.name) {
|
|
10863
13335
|
commandMap.set(cmd.name, cmd);
|
|
10864
13336
|
}
|
|
@@ -10997,7 +13469,7 @@ class DiscordService extends Service {
|
|
|
10997
13469
|
this.runtime.logger.info({
|
|
10998
13470
|
src: "plugin:discord",
|
|
10999
13471
|
agentId: this.runtime.agentId,
|
|
11000
|
-
newCommands:
|
|
13472
|
+
newCommands: commands2.length,
|
|
11001
13473
|
totalCommands: this.slashCommands.length,
|
|
11002
13474
|
globalCommands: transformedGlobalCommands.length,
|
|
11003
13475
|
globalCommandsRegisteredForDMs: globalCommandsRegistered,
|
|
@@ -11086,7 +13558,7 @@ class DiscordService extends Service {
|
|
|
11086
13558
|
}, "Failed to register commands to newly joined guild");
|
|
11087
13559
|
}
|
|
11088
13560
|
}
|
|
11089
|
-
const worldId =
|
|
13561
|
+
const worldId = createUniqueUuid7(this.runtime, fullGuild.id);
|
|
11090
13562
|
const standardizedData = {
|
|
11091
13563
|
runtime: this.runtime,
|
|
11092
13564
|
rooms: await this.buildStandardizedRooms(fullGuild, worldId),
|
|
@@ -11113,11 +13585,11 @@ class DiscordService extends Service {
|
|
|
11113
13585
|
this.runtime.emitEvent([EventType3.WORLD_JOINED], standardizedData);
|
|
11114
13586
|
}
|
|
11115
13587
|
async handleInteractionCreate(interaction) {
|
|
11116
|
-
const entityId =
|
|
13588
|
+
const entityId = this.resolveDiscordEntityId(interaction.user.id);
|
|
11117
13589
|
const userName = interaction.user.bot ? `${interaction.user.username}#${interaction.user.discriminator}` : interaction.user.username;
|
|
11118
13590
|
const name = interaction.user.displayName;
|
|
11119
13591
|
const interactionChannelId = interaction.channel?.id;
|
|
11120
|
-
const roomId =
|
|
13592
|
+
const roomId = createUniqueUuid7(this.runtime, interactionChannelId || userName);
|
|
11121
13593
|
let type;
|
|
11122
13594
|
let serverId;
|
|
11123
13595
|
if (interaction.guild) {
|
|
@@ -11132,19 +13604,20 @@ class DiscordService extends Service {
|
|
|
11132
13604
|
}
|
|
11133
13605
|
serverId = guild.id;
|
|
11134
13606
|
} else {
|
|
11135
|
-
type =
|
|
13607
|
+
type = ChannelType8.DM;
|
|
11136
13608
|
serverId = interactionChannelId;
|
|
11137
13609
|
}
|
|
11138
13610
|
await this.runtime.ensureConnection({
|
|
11139
13611
|
entityId,
|
|
11140
13612
|
roomId,
|
|
13613
|
+
roomName: interaction.guild && interaction.channel && "name" in interaction.channel && typeof interaction.channel.name === "string" ? interaction.channel.name : name,
|
|
11141
13614
|
userName,
|
|
11142
13615
|
name,
|
|
11143
13616
|
source: "discord",
|
|
11144
13617
|
channelId: interactionChannelId,
|
|
11145
|
-
messageServerId: serverId ?
|
|
13618
|
+
messageServerId: serverId ? stringToUuid4(serverId) : undefined,
|
|
11146
13619
|
type,
|
|
11147
|
-
worldId:
|
|
13620
|
+
worldId: createUniqueUuid7(this.runtime, serverId ?? roomId),
|
|
11148
13621
|
worldName: interaction.guild?.name || undefined,
|
|
11149
13622
|
userId: interaction.user.id,
|
|
11150
13623
|
metadata: buildDiscordWorldMetadata(this.runtime, interaction.guild?.ownerId)
|
|
@@ -11333,23 +13806,23 @@ class DiscordService extends Service {
|
|
|
11333
13806
|
async buildStandardizedRooms(guild, _worldId) {
|
|
11334
13807
|
const rooms = [];
|
|
11335
13808
|
for (const [channelId, channel] of guild.channels.cache) {
|
|
11336
|
-
if (channel.type ===
|
|
11337
|
-
const roomId =
|
|
13809
|
+
if (channel.type === DiscordChannelType6.GuildText || channel.type === DiscordChannelType6.GuildVoice) {
|
|
13810
|
+
const roomId = createUniqueUuid7(this.runtime, channelId);
|
|
11338
13811
|
let channelType;
|
|
11339
13812
|
switch (channel.type) {
|
|
11340
|
-
case
|
|
11341
|
-
channelType =
|
|
13813
|
+
case DiscordChannelType6.GuildText:
|
|
13814
|
+
channelType = ChannelType8.GROUP;
|
|
11342
13815
|
break;
|
|
11343
|
-
case
|
|
11344
|
-
channelType =
|
|
13816
|
+
case DiscordChannelType6.GuildVoice:
|
|
13817
|
+
channelType = ChannelType8.VOICE_GROUP;
|
|
11345
13818
|
break;
|
|
11346
13819
|
default:
|
|
11347
|
-
channelType =
|
|
13820
|
+
channelType = ChannelType8.GROUP;
|
|
11348
13821
|
}
|
|
11349
13822
|
let participants = [];
|
|
11350
|
-
if (guild.memberCount < 1000 && channel.type ===
|
|
13823
|
+
if (guild.memberCount < 1000 && channel.type === DiscordChannelType6.GuildText) {
|
|
11351
13824
|
try {
|
|
11352
|
-
participants = Array.from(guild.members.cache.values()).filter((member) => channel.permissionsFor(member)?.has(PermissionsBitField5.Flags.ViewChannel)).map((member) =>
|
|
13825
|
+
participants = Array.from(guild.members.cache.values()).filter((member) => channel.permissionsFor(member)?.has(PermissionsBitField5.Flags.ViewChannel)).map((member) => this.resolveDiscordEntityId(member.id));
|
|
11353
13826
|
} catch (error) {
|
|
11354
13827
|
this.runtime.logger.warn({
|
|
11355
13828
|
src: "plugin:discord",
|
|
@@ -11390,14 +13863,14 @@ class DiscordService extends Service {
|
|
|
11390
13863
|
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
11391
13864
|
if (member.id !== botId) {
|
|
11392
13865
|
entities.push({
|
|
11393
|
-
id:
|
|
13866
|
+
id: this.resolveDiscordEntityId(member.id),
|
|
11394
13867
|
names: Array.from(new Set([
|
|
11395
13868
|
member.user.username,
|
|
11396
13869
|
member.displayName,
|
|
11397
13870
|
member.user.globalName
|
|
11398
13871
|
].filter(Boolean))),
|
|
11399
13872
|
agentId: this.runtime.agentId,
|
|
11400
|
-
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined)
|
|
13873
|
+
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined, member.user.displayAvatarURL())
|
|
11401
13874
|
});
|
|
11402
13875
|
}
|
|
11403
13876
|
}
|
|
@@ -11410,7 +13883,7 @@ class DiscordService extends Service {
|
|
|
11410
13883
|
const onlineMembers = await guild.members.fetch({ limit: 100 });
|
|
11411
13884
|
for (const [, member] of onlineMembers) {
|
|
11412
13885
|
if (member.id !== botId) {
|
|
11413
|
-
const entityId =
|
|
13886
|
+
const entityId = this.resolveDiscordEntityId(member.id);
|
|
11414
13887
|
if (!entities.some((u) => u.id === entityId)) {
|
|
11415
13888
|
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
11416
13889
|
entities.push({
|
|
@@ -11421,7 +13894,7 @@ class DiscordService extends Service {
|
|
|
11421
13894
|
member.user.globalName
|
|
11422
13895
|
].filter(Boolean))),
|
|
11423
13896
|
agentId: this.runtime.agentId,
|
|
11424
|
-
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined)
|
|
13897
|
+
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined, member.user.displayAvatarURL())
|
|
11425
13898
|
});
|
|
11426
13899
|
}
|
|
11427
13900
|
}
|
|
@@ -11445,14 +13918,14 @@ class DiscordService extends Service {
|
|
|
11445
13918
|
if (member.id !== botId) {
|
|
11446
13919
|
const tag = member.user.bot ? `${member.user.username}#${member.user.discriminator}` : member.user.username;
|
|
11447
13920
|
entities.push({
|
|
11448
|
-
id:
|
|
13921
|
+
id: this.resolveDiscordEntityId(member.id),
|
|
11449
13922
|
names: Array.from(new Set([
|
|
11450
13923
|
member.user.username,
|
|
11451
13924
|
member.displayName,
|
|
11452
13925
|
member.user.globalName
|
|
11453
13926
|
].filter(Boolean))),
|
|
11454
13927
|
agentId: this.runtime.agentId,
|
|
11455
|
-
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined)
|
|
13928
|
+
metadata: buildDiscordEntityMetadata(member.id, tag, member.displayName || member.user.username, member.user.globalName ?? undefined, member.user.displayAvatarURL())
|
|
11456
13929
|
});
|
|
11457
13930
|
}
|
|
11458
13931
|
}
|
|
@@ -11469,13 +13942,30 @@ class DiscordService extends Service {
|
|
|
11469
13942
|
}
|
|
11470
13943
|
async onReady(readyClient) {
|
|
11471
13944
|
this.runtime.logger.success("Discord client ready");
|
|
13945
|
+
const discordApiToken = this.runtime.getSetting("DISCORD_API_TOKEN");
|
|
13946
|
+
if (typeof discordApiToken === "string" && discordApiToken.trim().length > 0 && typeof readyClient.rest?.setToken === "function") {
|
|
13947
|
+
readyClient.rest.setToken(discordApiToken.trim());
|
|
13948
|
+
}
|
|
13949
|
+
await this.refreshOwnerDiscordUserIds(readyClient);
|
|
11472
13950
|
this.slashCommands = [];
|
|
11473
13951
|
this.runtime.registerEvent("DISCORD_REGISTER_COMMANDS", async (params) => {
|
|
11474
13952
|
await this.registerSlashCommands(params.commands);
|
|
11475
13953
|
});
|
|
13954
|
+
await registerSlashCommands(this.runtime);
|
|
11476
13955
|
const auditLogSettingForInvite = this.runtime.getSetting("DISCORD_AUDIT_LOG_ENABLED");
|
|
11477
|
-
const isAuditLogEnabledForInvite = auditLogSettingForInvite
|
|
13956
|
+
const isAuditLogEnabledForInvite = auditLogSettingForInvite === "true" || auditLogSettingForInvite === true || auditLogSettingForInvite === "1" || auditLogSettingForInvite === 1;
|
|
11478
13957
|
const readyClientUser = readyClient.user;
|
|
13958
|
+
if (readyClientUser) {
|
|
13959
|
+
try {
|
|
13960
|
+
await syncDiscordClientProfile(this.runtime, readyClientUser, this.discordSettings);
|
|
13961
|
+
} catch (error) {
|
|
13962
|
+
this.runtime.logger.warn({
|
|
13963
|
+
src: "plugin:discord",
|
|
13964
|
+
agentId: this.runtime.agentId,
|
|
13965
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13966
|
+
}, "Failed to synchronize Discord bot profile from connector settings");
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
11479
13969
|
const inviteUrl = readyClientUser?.id ? generateInviteUrl(readyClientUser.id, "MODERATOR_VOICE") : undefined;
|
|
11480
13970
|
if (isAuditLogEnabledForInvite) {
|
|
11481
13971
|
this.runtime.logger.info({ src: "plugin:discord", agentId: this.runtime.agentId }, "Audit log tracking enabled - ensure bot has ViewAuditLog permission in server settings");
|
|
@@ -11498,7 +13988,7 @@ class DiscordService extends Service {
|
|
|
11498
13988
|
try {
|
|
11499
13989
|
const fullGuild = await guild.fetch();
|
|
11500
13990
|
this.runtime.logger.info(`Discord server connected: ${fullGuild.name} (${fullGuild.id})`);
|
|
11501
|
-
const worldId =
|
|
13991
|
+
const worldId = createUniqueUuid7(this.runtime, fullGuild.id);
|
|
11502
13992
|
const standardizedData = {
|
|
11503
13993
|
name: fullGuild.name,
|
|
11504
13994
|
runtime: this.runtime,
|
|
@@ -11535,7 +14025,7 @@ class DiscordService extends Service {
|
|
|
11535
14025
|
this.timeouts.push(timeoutId);
|
|
11536
14026
|
}
|
|
11537
14027
|
const auditLogEnabled = this.runtime.getSetting("DISCORD_AUDIT_LOG_ENABLED");
|
|
11538
|
-
if (auditLogEnabled
|
|
14028
|
+
if (auditLogEnabled === "true" || auditLogEnabled === true || auditLogEnabled === "1" || auditLogEnabled === 1) {
|
|
11539
14029
|
try {
|
|
11540
14030
|
const testGuild = guilds.first();
|
|
11541
14031
|
if (testGuild) {
|
|
@@ -11544,11 +14034,15 @@ class DiscordService extends Service {
|
|
|
11544
14034
|
this.runtime.logger.debug("Audit log access verified for permission tracking");
|
|
11545
14035
|
}
|
|
11546
14036
|
} catch (err) {
|
|
11547
|
-
|
|
14037
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
14038
|
+
const errorCode = typeof err === "object" && err !== null && "code" in err && typeof err.code !== "undefined" ? String(err.code) : "";
|
|
14039
|
+
const missingAuditLogPermission = errorCode === "50013" || errorMessage.includes("Missing Permissions");
|
|
14040
|
+
const logMethod = missingAuditLogPermission ? this.runtime.logger.info : this.runtime.logger.warn;
|
|
14041
|
+
logMethod.call(this.runtime.logger, {
|
|
11548
14042
|
src: "plugin:discord",
|
|
11549
14043
|
agentId: this.runtime.agentId,
|
|
11550
|
-
error:
|
|
11551
|
-
}, "Cannot access audit logs - permission change alerts will not include executor info");
|
|
14044
|
+
error: errorMessage
|
|
14045
|
+
}, missingAuditLogPermission ? "Audit log access unavailable - permission change alerts will not include executor info" : "Cannot access audit logs - permission change alerts will not include executor info");
|
|
11552
14046
|
}
|
|
11553
14047
|
}
|
|
11554
14048
|
if (this.client) {
|
|
@@ -11574,7 +14068,7 @@ class DiscordService extends Service {
|
|
|
11574
14068
|
this.runtime.logger.error({ src: "plugin:discord", agentId: this.runtime.agentId, channelId }, "Channel not found");
|
|
11575
14069
|
return [];
|
|
11576
14070
|
}
|
|
11577
|
-
if (channel.type !==
|
|
14071
|
+
if (channel.type !== DiscordChannelType6.GuildText) {
|
|
11578
14072
|
this.runtime.logger.error({ src: "plugin:discord", agentId: this.runtime.agentId, channelId }, "Channel is not a text channel");
|
|
11579
14073
|
return [];
|
|
11580
14074
|
}
|
|
@@ -11706,9 +14200,9 @@ class DiscordService extends Service {
|
|
|
11706
14200
|
}
|
|
11707
14201
|
}
|
|
11708
14202
|
const timestamp = Date.now();
|
|
11709
|
-
const roomId =
|
|
11710
|
-
const entityId =
|
|
11711
|
-
const reactionUUID =
|
|
14203
|
+
const roomId = createUniqueUuid7(this.runtime, reaction.message.channel.id);
|
|
14204
|
+
const entityId = this.resolveDiscordEntityId(user.id);
|
|
14205
|
+
const reactionUUID = createUniqueUuid7(this.runtime, `${reaction.message.id}-${user.id}-${emoji}-${timestamp}`);
|
|
11712
14206
|
if (!entityId || !roomId) {
|
|
11713
14207
|
this.runtime.logger.error({
|
|
11714
14208
|
src: "plugin:discord",
|
|
@@ -11728,18 +14222,19 @@ class DiscordService extends Service {
|
|
|
11728
14222
|
await this.runtime.ensureConnection({
|
|
11729
14223
|
entityId,
|
|
11730
14224
|
roomId,
|
|
14225
|
+
roomName: "name" in reaction.message.channel && typeof reaction.message.channel.name === "string" ? reaction.message.channel.name : name,
|
|
11731
14226
|
userName,
|
|
11732
|
-
worldId:
|
|
14227
|
+
worldId: createUniqueUuid7(this.runtime, reaction.message.guild?.id ?? roomId),
|
|
11733
14228
|
worldName: reaction.message.guild?.name || undefined,
|
|
11734
14229
|
name,
|
|
11735
14230
|
source: "discord",
|
|
11736
14231
|
channelId: reaction.message.channel.id,
|
|
11737
|
-
messageServerId: reaction.message.guild?.id ?
|
|
14232
|
+
messageServerId: reaction.message.guild?.id ? stringToUuid4(reaction.message.guild.id) : undefined,
|
|
11738
14233
|
type: channelType,
|
|
11739
14234
|
userId: user.id,
|
|
11740
14235
|
metadata: buildDiscordWorldMetadata(this.runtime, reaction.message.guild?.ownerId)
|
|
11741
14236
|
});
|
|
11742
|
-
const inReplyTo =
|
|
14237
|
+
const inReplyTo = createUniqueUuid7(this.runtime, reaction.message.id);
|
|
11743
14238
|
const memory = {
|
|
11744
14239
|
id: reactionUUID,
|
|
11745
14240
|
entityId,
|
|
@@ -11750,6 +14245,16 @@ class DiscordService extends Service {
|
|
|
11750
14245
|
inReplyTo,
|
|
11751
14246
|
channelType
|
|
11752
14247
|
},
|
|
14248
|
+
metadata: {
|
|
14249
|
+
entityName: name,
|
|
14250
|
+
entityUserName: userName,
|
|
14251
|
+
fromId: user.id,
|
|
14252
|
+
discordReaction: {
|
|
14253
|
+
action: type,
|
|
14254
|
+
emoji,
|
|
14255
|
+
targetMessageId: inReplyTo
|
|
14256
|
+
}
|
|
14257
|
+
},
|
|
11753
14258
|
roomId,
|
|
11754
14259
|
createdAt: timestamp
|
|
11755
14260
|
};
|
|
@@ -11758,7 +14263,11 @@ class DiscordService extends Service {
|
|
|
11758
14263
|
this.runtime.logger.error({ src: "plugin:discord", agentId: this.runtime.agentId }, "No channel found for reaction message");
|
|
11759
14264
|
return [];
|
|
11760
14265
|
}
|
|
11761
|
-
|
|
14266
|
+
const responseText = normalizeDiscordMessageText(content.text);
|
|
14267
|
+
if (!responseText.trim()) {
|
|
14268
|
+
return [];
|
|
14269
|
+
}
|
|
14270
|
+
await reaction.message.channel.send(responseText);
|
|
11762
14271
|
return [];
|
|
11763
14272
|
};
|
|
11764
14273
|
const events = type === "add" ? ["DISCORD_REACTION_RECEIVED" /* REACTION_RECEIVED */, EventType3.REACTION_RECEIVED] : ["DISCORD_REACTION_REMOVED" /* REACTION_REMOVED */];
|
|
@@ -11817,7 +14326,7 @@ class DiscordService extends Service {
|
|
|
11817
14326
|
}
|
|
11818
14327
|
async getSpiderState(channelId) {
|
|
11819
14328
|
try {
|
|
11820
|
-
const stateId =
|
|
14329
|
+
const stateId = createUniqueUuid7(this.runtime, `discord-spider-state-${channelId}`);
|
|
11821
14330
|
const stateMemory = await this.runtime.getMemoryById(stateId);
|
|
11822
14331
|
const stateMemoryContent = stateMemory?.content;
|
|
11823
14332
|
if (stateMemoryContent?.text) {
|
|
@@ -11842,8 +14351,8 @@ class DiscordService extends Service {
|
|
|
11842
14351
|
}
|
|
11843
14352
|
async saveSpiderState(state) {
|
|
11844
14353
|
try {
|
|
11845
|
-
const stateId =
|
|
11846
|
-
const roomId =
|
|
14354
|
+
const stateId = createUniqueUuid7(this.runtime, `discord-spider-state-${state.channelId}`);
|
|
14355
|
+
const roomId = createUniqueUuid7(this.runtime, state.channelId);
|
|
11847
14356
|
this.runtime.logger.debug(`[SpiderState] Saving channel=${state.channelId} stateId=${stateId}`);
|
|
11848
14357
|
let existing = null;
|
|
11849
14358
|
try {
|
|
@@ -11875,7 +14384,7 @@ class DiscordService extends Service {
|
|
|
11875
14384
|
}
|
|
11876
14385
|
}
|
|
11877
14386
|
} catch {}
|
|
11878
|
-
worldId =
|
|
14387
|
+
worldId = createUniqueUuid7(this.runtime, serverId ?? state.channelId);
|
|
11879
14388
|
const entityId = this.runtime.agentId;
|
|
11880
14389
|
try {
|
|
11881
14390
|
const entity = await this.runtime.getEntityById(entityId);
|
|
@@ -11899,7 +14408,7 @@ class DiscordService extends Service {
|
|
|
11899
14408
|
id: worldId,
|
|
11900
14409
|
name: serverId ? `Discord Server ${serverId}` : `Spider World ${state.channelId}`,
|
|
11901
14410
|
agentId: this.runtime.agentId,
|
|
11902
|
-
messageServerId:
|
|
14411
|
+
messageServerId: stringToUuid4(serverId ?? state.channelId)
|
|
11903
14412
|
});
|
|
11904
14413
|
this.runtime.logger.debug(`[SpiderState] World ensured: ${worldId}`);
|
|
11905
14414
|
} catch (worldError) {
|
|
@@ -11911,9 +14420,9 @@ class DiscordService extends Service {
|
|
|
11911
14420
|
id: roomId,
|
|
11912
14421
|
name: channelName,
|
|
11913
14422
|
source: "discord",
|
|
11914
|
-
type:
|
|
14423
|
+
type: ChannelType8.GROUP,
|
|
11915
14424
|
channelId: state.channelId,
|
|
11916
|
-
messageServerId:
|
|
14425
|
+
messageServerId: stringToUuid4(serverId ?? state.channelId),
|
|
11917
14426
|
worldId
|
|
11918
14427
|
});
|
|
11919
14428
|
this.runtime.logger.debug(`[SpiderState] Room ensured: ${roomId}`);
|
|
@@ -11997,24 +14506,24 @@ class DiscordService extends Service {
|
|
|
11997
14506
|
}
|
|
11998
14507
|
const channel = fetchedChannel;
|
|
11999
14508
|
const serverId = "guild" in channel && channel.guild ? channel.guild.id : ("guildId" in channel) && channel.guildId ? channel.guildId : channel.id;
|
|
12000
|
-
const worldId = serverId ?
|
|
14509
|
+
const worldId = serverId ? createUniqueUuid7(this.runtime, serverId) : this.runtime.agentId;
|
|
12001
14510
|
await this.runtime.ensureWorldExists({
|
|
12002
14511
|
id: worldId,
|
|
12003
14512
|
agentId: this.runtime.agentId,
|
|
12004
|
-
messageServerId:
|
|
14513
|
+
messageServerId: stringToUuid4(serverId),
|
|
12005
14514
|
name: (() => {
|
|
12006
14515
|
const channelGuild = "guild" in channel ? channel.guild : null;
|
|
12007
14516
|
return channelGuild?.name || "Discord";
|
|
12008
14517
|
})()
|
|
12009
14518
|
});
|
|
12010
14519
|
await this.runtime.ensureRoomExists({
|
|
12011
|
-
id:
|
|
14520
|
+
id: createUniqueUuid7(this.runtime, channel.id),
|
|
12012
14521
|
agentId: this.runtime.agentId,
|
|
12013
14522
|
name: "name" in channel && channel.name || channel.id,
|
|
12014
14523
|
source: "discord",
|
|
12015
14524
|
type: await this.getChannelType(channel),
|
|
12016
14525
|
channelId: channel.id,
|
|
12017
|
-
messageServerId:
|
|
14526
|
+
messageServerId: stringToUuid4(serverId),
|
|
12018
14527
|
worldId
|
|
12019
14528
|
});
|
|
12020
14529
|
const spiderState = options.force ? null : await this.getSpiderState(channelId);
|
|
@@ -12371,13 +14880,13 @@ class DiscordService extends Service {
|
|
|
12371
14880
|
if (!message.author || !message.channel) {
|
|
12372
14881
|
return null;
|
|
12373
14882
|
}
|
|
12374
|
-
const entityId =
|
|
12375
|
-
const roomId =
|
|
14883
|
+
const entityId = this.resolveDiscordEntityId(message.author.id);
|
|
14884
|
+
const roomId = createUniqueUuid7(this.runtime, message.channel.id);
|
|
12376
14885
|
const channel = message.channel;
|
|
12377
14886
|
const channelType = await this.getChannelType(channel);
|
|
12378
14887
|
const channelGuild = "guild" in channel ? channel.guild : null;
|
|
12379
14888
|
const serverId = channelGuild?.id ? channelGuild.id : message.guild?.id ?? message.channel.id;
|
|
12380
|
-
const worldId = serverId ?
|
|
14889
|
+
const worldId = serverId ? createUniqueUuid7(this.runtime, serverId) : this.runtime.agentId;
|
|
12381
14890
|
let textContent;
|
|
12382
14891
|
let attachments;
|
|
12383
14892
|
const optionsProcessedContent = options?.processedContent;
|
|
@@ -12394,6 +14903,8 @@ class DiscordService extends Service {
|
|
|
12394
14903
|
const metadata = {
|
|
12395
14904
|
type: "custom",
|
|
12396
14905
|
entityName: (message.member && "displayName" in message.member && typeof message.member.displayName === "string" ? message.member.displayName : undefined) ?? ("globalName" in message.author && typeof message.author.globalName === "string" ? message.author.globalName : undefined) ?? message.author.username,
|
|
14906
|
+
entityUserName: message.author.username,
|
|
14907
|
+
entityAvatarUrl: message.author.displayAvatarURL(),
|
|
12397
14908
|
fromBot: message.author.bot,
|
|
12398
14909
|
fromId: message.author.id,
|
|
12399
14910
|
sourceId: entityId,
|
|
@@ -12407,7 +14918,7 @@ class DiscordService extends Service {
|
|
|
12407
14918
|
...options?.extraMetadata ? options.extraMetadata : {}
|
|
12408
14919
|
};
|
|
12409
14920
|
const memory = {
|
|
12410
|
-
id:
|
|
14921
|
+
id: createUniqueUuid7(this.runtime, message.id),
|
|
12411
14922
|
entityId,
|
|
12412
14923
|
agentId: this.runtime.agentId,
|
|
12413
14924
|
roomId,
|
|
@@ -12417,7 +14928,7 @@ class DiscordService extends Service {
|
|
|
12417
14928
|
source: "discord",
|
|
12418
14929
|
channelType,
|
|
12419
14930
|
url: message.url,
|
|
12420
|
-
inReplyTo: message.reference?.messageId ?
|
|
14931
|
+
inReplyTo: message.reference?.messageId ? createUniqueUuid7(this.runtime, message.reference.messageId) : undefined,
|
|
12421
14932
|
...options?.extraContent ? options.extraContent : {}
|
|
12422
14933
|
},
|
|
12423
14934
|
metadata,
|
|
@@ -12444,20 +14955,20 @@ class DiscordService extends Service {
|
|
|
12444
14955
|
const channelType = await this.getChannelType(firstMessage.channel);
|
|
12445
14956
|
const firstMessageChannelGuild = "guild" in firstMessage.channel ? firstMessage.channel.guild : null;
|
|
12446
14957
|
const serverId = firstMessageChannelGuild?.id ? firstMessageChannelGuild.id : firstMessage.guild?.id ?? firstMessage.channel.id;
|
|
12447
|
-
const worldId = serverId ?
|
|
14958
|
+
const worldId = serverId ? createUniqueUuid7(this.runtime, serverId) : this.runtime.agentId;
|
|
12448
14959
|
const entities = Array.from(uniqueAuthors.entries()).map(([authorId, message]) => {
|
|
12449
14960
|
const userName = message.author.username;
|
|
12450
14961
|
const name = (message.member && "displayName" in message.member && typeof message.member.displayName === "string" ? message.member.displayName : undefined) ?? ("globalName" in message.author && typeof message.author.globalName === "string" ? message.author.globalName : undefined) ?? userName;
|
|
12451
14962
|
return {
|
|
12452
|
-
id:
|
|
14963
|
+
id: this.resolveDiscordEntityId(authorId),
|
|
12453
14964
|
names: [userName, name].filter((n) => typeof n === "string" && n.length > 0),
|
|
12454
|
-
metadata: buildDiscordEntityMetadata(authorId, userName, name),
|
|
14965
|
+
metadata: buildDiscordEntityMetadata(authorId, userName, name, undefined, message.author.displayAvatarURL()),
|
|
12455
14966
|
agentId: this.runtime.agentId
|
|
12456
14967
|
};
|
|
12457
14968
|
});
|
|
12458
14969
|
const rooms = [
|
|
12459
14970
|
{
|
|
12460
|
-
id:
|
|
14971
|
+
id: createUniqueUuid7(this.runtime, firstMessage.channel.id),
|
|
12461
14972
|
channelId: firstMessage.channel.id,
|
|
12462
14973
|
type: channelType,
|
|
12463
14974
|
source: "discord"
|
|
@@ -12465,7 +14976,7 @@ class DiscordService extends Service {
|
|
|
12465
14976
|
];
|
|
12466
14977
|
const world = {
|
|
12467
14978
|
id: worldId,
|
|
12468
|
-
messageServerId:
|
|
14979
|
+
messageServerId: stringToUuid4(serverId),
|
|
12469
14980
|
name: firstMessage.guild?.name ?? `DM-${firstMessage.channel.id}`,
|
|
12470
14981
|
agentId: this.runtime.agentId,
|
|
12471
14982
|
metadata: buildDiscordWorldMetadata(this.runtime, firstMessageChannelGuild?.ownerId ?? firstMessage.guild?.ownerId ?? undefined)
|
|
@@ -12487,37 +14998,56 @@ class DiscordService extends Service {
|
|
|
12487
14998
|
this.runtime.logger.info("Stopping Discord service");
|
|
12488
14999
|
this.timeouts.forEach(clearTimeout);
|
|
12489
15000
|
this.timeouts = [];
|
|
15001
|
+
this.messageDebouncer?.destroy();
|
|
15002
|
+
this.channelDebouncer?.destroy();
|
|
15003
|
+
this.messageDebouncer = undefined;
|
|
15004
|
+
this.channelDebouncer = undefined;
|
|
15005
|
+
this.userSelections.clear();
|
|
15006
|
+
if (this.voiceManager) {
|
|
15007
|
+
try {
|
|
15008
|
+
this.voiceManager.stop();
|
|
15009
|
+
} catch (error) {
|
|
15010
|
+
this.runtime.logger.warn(`Discord voice cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
15011
|
+
}
|
|
15012
|
+
}
|
|
12490
15013
|
if (this.client) {
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
15014
|
+
try {
|
|
15015
|
+
await this.client.destroy();
|
|
15016
|
+
this.runtime.logger.info("Discord client destroyed");
|
|
15017
|
+
} catch (error) {
|
|
15018
|
+
this.runtime.logger.warn(`Discord client destroy failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
15019
|
+
} finally {
|
|
15020
|
+
this.client = null;
|
|
15021
|
+
}
|
|
12494
15022
|
}
|
|
12495
|
-
|
|
15023
|
+
this.clientReadyPromise = null;
|
|
15024
|
+
this.messageManager = undefined;
|
|
15025
|
+
this.voiceManager = undefined;
|
|
12496
15026
|
this.runtime.logger.info("Discord service stopped");
|
|
12497
15027
|
}
|
|
12498
15028
|
async getChannelType(channel) {
|
|
12499
15029
|
switch (channel.type) {
|
|
12500
|
-
case
|
|
12501
|
-
return
|
|
12502
|
-
case
|
|
12503
|
-
return
|
|
12504
|
-
case
|
|
12505
|
-
case
|
|
12506
|
-
case
|
|
12507
|
-
case
|
|
12508
|
-
case
|
|
12509
|
-
case
|
|
12510
|
-
return
|
|
12511
|
-
case
|
|
12512
|
-
case
|
|
12513
|
-
return
|
|
15030
|
+
case DiscordChannelType6.DM:
|
|
15031
|
+
return ChannelType8.DM;
|
|
15032
|
+
case DiscordChannelType6.GroupDM:
|
|
15033
|
+
return ChannelType8.DM;
|
|
15034
|
+
case DiscordChannelType6.GuildText:
|
|
15035
|
+
case DiscordChannelType6.GuildNews:
|
|
15036
|
+
case DiscordChannelType6.PublicThread:
|
|
15037
|
+
case DiscordChannelType6.PrivateThread:
|
|
15038
|
+
case DiscordChannelType6.AnnouncementThread:
|
|
15039
|
+
case DiscordChannelType6.GuildForum:
|
|
15040
|
+
return ChannelType8.GROUP;
|
|
15041
|
+
case DiscordChannelType6.GuildVoice:
|
|
15042
|
+
case DiscordChannelType6.GuildStageVoice:
|
|
15043
|
+
return ChannelType8.VOICE_GROUP;
|
|
12514
15044
|
default:
|
|
12515
15045
|
this.runtime.logger.debug({
|
|
12516
15046
|
src: "plugin:discord",
|
|
12517
15047
|
agentId: this.runtime.agentId,
|
|
12518
15048
|
channelType: channel.type
|
|
12519
15049
|
}, "Unknown channel type, defaulting to GROUP");
|
|
12520
|
-
return
|
|
15050
|
+
return ChannelType8.GROUP;
|
|
12521
15051
|
}
|
|
12522
15052
|
}
|
|
12523
15053
|
}
|
|
@@ -12532,12 +15062,12 @@ import {
|
|
|
12532
15062
|
VoiceConnectionStatus as VoiceConnectionStatus2
|
|
12533
15063
|
} from "@discordjs/voice";
|
|
12534
15064
|
import {
|
|
12535
|
-
logger as
|
|
15065
|
+
logger as logger5,
|
|
12536
15066
|
ModelType as ModelType21
|
|
12537
15067
|
} from "@elizaos/core";
|
|
12538
15068
|
import {
|
|
12539
15069
|
AttachmentBuilder as AttachmentBuilder3,
|
|
12540
|
-
ChannelType as
|
|
15070
|
+
ChannelType as ChannelType9,
|
|
12541
15071
|
Events as Events2
|
|
12542
15072
|
} from "discord.js";
|
|
12543
15073
|
var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
|
|
@@ -12582,9 +15112,9 @@ class DiscordTestSuite {
|
|
|
12582
15112
|
}
|
|
12583
15113
|
const discordClient = this.discordClient.client;
|
|
12584
15114
|
if (discordClient?.isReady()) {
|
|
12585
|
-
|
|
15115
|
+
logger5.success("DiscordService is already ready.");
|
|
12586
15116
|
} else {
|
|
12587
|
-
|
|
15117
|
+
logger5.info("Waiting for DiscordService to be ready...");
|
|
12588
15118
|
if (!discordClient) {
|
|
12589
15119
|
throw new Error("Discord client instance is missing within the service.");
|
|
12590
15120
|
}
|
|
@@ -12618,14 +15148,14 @@ class DiscordTestSuite {
|
|
|
12618
15148
|
guild: channel.guild,
|
|
12619
15149
|
deferReply: async () => {},
|
|
12620
15150
|
editReply: async (message) => {
|
|
12621
|
-
|
|
15151
|
+
logger5.info(`JoinChannel Slash Command Response: ${message}`);
|
|
12622
15152
|
}
|
|
12623
15153
|
};
|
|
12624
15154
|
if (!this.discordClient.voiceManager) {
|
|
12625
15155
|
throw new Error("VoiceManager is not available on the Discord client.");
|
|
12626
15156
|
}
|
|
12627
15157
|
await this.discordClient.voiceManager.handleJoinChannelCommand(fakeJoinInteraction);
|
|
12628
|
-
|
|
15158
|
+
logger5.success("Join voice slash command test completed successfully.");
|
|
12629
15159
|
} catch (error) {
|
|
12630
15160
|
throw new Error(`Error in join voice slash commands test: ${error}`);
|
|
12631
15161
|
}
|
|
@@ -12643,14 +15173,14 @@ class DiscordTestSuite {
|
|
|
12643
15173
|
const fakeLeaveInteraction = {
|
|
12644
15174
|
guildId: channel.guildId,
|
|
12645
15175
|
reply: async (message) => {
|
|
12646
|
-
|
|
15176
|
+
logger5.info(`LeaveChannel Slash Command Response: ${message}`);
|
|
12647
15177
|
}
|
|
12648
15178
|
};
|
|
12649
15179
|
if (!this.discordClient.voiceManager) {
|
|
12650
15180
|
throw new Error("VoiceManager is not available on the Discord client.");
|
|
12651
15181
|
}
|
|
12652
15182
|
await this.discordClient.voiceManager.handleLeaveChannelCommand(fakeLeaveInteraction);
|
|
12653
|
-
|
|
15183
|
+
logger5.success("Leave voice slash command test completed successfully.");
|
|
12654
15184
|
} catch (error) {
|
|
12655
15185
|
throw new Error(`Error in leave voice slash commands test: ${error}`);
|
|
12656
15186
|
}
|
|
@@ -12662,7 +15192,7 @@ class DiscordTestSuite {
|
|
|
12662
15192
|
try {
|
|
12663
15193
|
await this.waitForVoiceManagerReady(this.discordClient);
|
|
12664
15194
|
const channel = await this.getTestChannel(runtime);
|
|
12665
|
-
if (!channel || channel.type !==
|
|
15195
|
+
if (!channel || channel.type !== ChannelType9.GuildVoice) {
|
|
12666
15196
|
throw new Error("Invalid voice channel.");
|
|
12667
15197
|
}
|
|
12668
15198
|
if (!this.discordClient.voiceManager) {
|
|
@@ -12680,7 +15210,7 @@ class DiscordTestSuite {
|
|
|
12680
15210
|
}
|
|
12681
15211
|
try {
|
|
12682
15212
|
await entersState2(connection, VoiceConnectionStatus2.Ready, 1e4);
|
|
12683
|
-
|
|
15213
|
+
logger5.success(`Voice connection is ready in guild: ${guildId}`);
|
|
12684
15214
|
} catch (error) {
|
|
12685
15215
|
throw new Error(`Voice connection failed to become ready: ${error}`);
|
|
12686
15216
|
}
|
|
@@ -12774,10 +15304,10 @@ class DiscordTestSuite {
|
|
|
12774
15304
|
const audioResource = createAudioResource2(responseStream);
|
|
12775
15305
|
audioPlayer.play(audioResource);
|
|
12776
15306
|
connection.subscribe(audioPlayer);
|
|
12777
|
-
|
|
15307
|
+
logger5.success("TTS playback started successfully.");
|
|
12778
15308
|
await new Promise((resolve, reject) => {
|
|
12779
15309
|
audioPlayer.once(AudioPlayerStatus.Idle, () => {
|
|
12780
|
-
|
|
15310
|
+
logger5.info("TTS playback finished.");
|
|
12781
15311
|
resolve();
|
|
12782
15312
|
});
|
|
12783
15313
|
audioPlayer.once("error", (error) => {
|
|
@@ -13718,7 +16248,7 @@ var ActivityFlags = import_v10.default.ActivityFlags;
|
|
|
13718
16248
|
var ActivityPlatform = import_v10.default.ActivityPlatform;
|
|
13719
16249
|
var ActivityType = import_v10.default.ActivityType;
|
|
13720
16250
|
var AllowedMentionsTypes = import_v10.default.AllowedMentionsTypes;
|
|
13721
|
-
var
|
|
16251
|
+
var ApplicationCommandOptionType2 = import_v10.default.ApplicationCommandOptionType;
|
|
13722
16252
|
var ApplicationCommandPermissionType = import_v10.default.ApplicationCommandPermissionType;
|
|
13723
16253
|
var ApplicationCommandType = import_v10.default.ApplicationCommandType;
|
|
13724
16254
|
var ApplicationFlags = import_v10.default.ApplicationFlags;
|
|
@@ -13737,7 +16267,7 @@ var AutoModerationRuleTriggerType = import_v10.default.AutoModerationRuleTrigger
|
|
|
13737
16267
|
var ButtonStyle = import_v10.default.ButtonStyle;
|
|
13738
16268
|
var CDNRoutes = import_v10.default.CDNRoutes;
|
|
13739
16269
|
var ChannelFlags = import_v10.default.ChannelFlags;
|
|
13740
|
-
var
|
|
16270
|
+
var ChannelType10 = import_v10.default.ChannelType;
|
|
13741
16271
|
var ComponentType = import_v10.default.ComponentType;
|
|
13742
16272
|
var ConnectionService = import_v10.default.ConnectionService;
|
|
13743
16273
|
var ConnectionVisibility = import_v10.default.ConnectionVisibility;
|
|
@@ -13839,7 +16369,7 @@ function buildDiscordCommandOptions(args) {
|
|
|
13839
16369
|
return {
|
|
13840
16370
|
name: arg.name,
|
|
13841
16371
|
description: arg.description,
|
|
13842
|
-
type:
|
|
16372
|
+
type: ApplicationCommandOptionType2.Number,
|
|
13843
16373
|
required
|
|
13844
16374
|
};
|
|
13845
16375
|
}
|
|
@@ -13847,7 +16377,7 @@ function buildDiscordCommandOptions(args) {
|
|
|
13847
16377
|
return {
|
|
13848
16378
|
name: arg.name,
|
|
13849
16379
|
description: arg.description,
|
|
13850
|
-
type:
|
|
16380
|
+
type: ApplicationCommandOptionType2.Boolean,
|
|
13851
16381
|
required
|
|
13852
16382
|
};
|
|
13853
16383
|
}
|
|
@@ -13858,7 +16388,7 @@ function buildDiscordCommandOptions(args) {
|
|
|
13858
16388
|
return {
|
|
13859
16389
|
name: arg.name,
|
|
13860
16390
|
description: arg.description,
|
|
13861
|
-
type:
|
|
16391
|
+
type: ApplicationCommandOptionType2.String,
|
|
13862
16392
|
required,
|
|
13863
16393
|
choices
|
|
13864
16394
|
};
|
|
@@ -13876,7 +16406,7 @@ function buildDiscordSlashCommand(spec21) {
|
|
|
13876
16406
|
{
|
|
13877
16407
|
name: "input",
|
|
13878
16408
|
description: "Command input",
|
|
13879
|
-
type:
|
|
16409
|
+
type: ApplicationCommandOptionType2.String,
|
|
13880
16410
|
required: false
|
|
13881
16411
|
}
|
|
13882
16412
|
] : undefined);
|
|
@@ -14028,12 +16558,14 @@ var discordPlugin = {
|
|
|
14028
16558
|
unpinMessage_default,
|
|
14029
16559
|
serverInfo_default,
|
|
14030
16560
|
editMessage_default,
|
|
14031
|
-
deleteMessage_default
|
|
16561
|
+
deleteMessage_default,
|
|
16562
|
+
setup_credentials_default
|
|
14032
16563
|
],
|
|
14033
16564
|
providers: [channelStateProvider, voiceStateProvider, guildInfoProvider],
|
|
14034
16565
|
tests: [new DiscordTestSuite],
|
|
14035
16566
|
init: async (_config, runtime) => {
|
|
14036
16567
|
const token = runtime.getSetting("DISCORD_API_TOKEN");
|
|
16568
|
+
const botTokens = runtime.getSetting("DISCORD_BOT_TOKENS");
|
|
14037
16569
|
const applicationId = runtime.getSetting("DISCORD_APPLICATION_ID");
|
|
14038
16570
|
const voiceChannelId = runtime.getSetting("DISCORD_VOICE_CHANNEL_ID");
|
|
14039
16571
|
const channelIds = runtime.getSetting("CHANNEL_IDS");
|
|
@@ -14057,6 +16589,11 @@ var discordPlugin = {
|
|
|
14057
16589
|
name: "DISCORD_APPLICATION_ID",
|
|
14058
16590
|
value: applicationId
|
|
14059
16591
|
},
|
|
16592
|
+
{
|
|
16593
|
+
name: "DISCORD_BOT_TOKENS",
|
|
16594
|
+
value: botTokens,
|
|
16595
|
+
sensitive: true
|
|
16596
|
+
},
|
|
14060
16597
|
{
|
|
14061
16598
|
name: "DISCORD_VOICE_CHANNEL_ID",
|
|
14062
16599
|
value: voiceChannelId
|
|
@@ -14087,9 +16624,9 @@ var discordPlugin = {
|
|
|
14087
16624
|
],
|
|
14088
16625
|
runtime
|
|
14089
16626
|
});
|
|
14090
|
-
if (!token || token.trim() === "") {
|
|
14091
|
-
|
|
14092
|
-
|
|
16627
|
+
if ((!token || token.trim() === "") && (!botTokens || botTokens.trim() === "")) {
|
|
16628
|
+
logger6.warn("Discord bot token not provided - Discord plugin is loaded but will not be functional");
|
|
16629
|
+
logger6.warn("To enable Discord functionality, provide DISCORD_API_TOKEN or DISCORD_BOT_TOKENS in your .env file");
|
|
14093
16630
|
}
|
|
14094
16631
|
}
|
|
14095
16632
|
};
|
|
@@ -14171,5 +16708,5 @@ export {
|
|
|
14171
16708
|
COMMAND_ARG_CUSTOM_ID_KEY
|
|
14172
16709
|
};
|
|
14173
16710
|
|
|
14174
|
-
//# debugId=
|
|
16711
|
+
//# debugId=4CB6B945E40708D364756E2164756E21
|
|
14175
16712
|
//# sourceMappingURL=index.js.map
|