@poolzin/pool-bot 2026.2.0 → 2026.2.1
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/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +1 -0
- package/dist/agents/pi-embedded-runner/model.js +61 -2
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +50 -72
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +171 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +2 -2
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +48 -15
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Button, ChannelType, Command, Row, } from "@buape/carbon";
|
|
2
2
|
import { ApplicationCommandOptionType, ButtonStyle } from "discord-api-types/v10";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveHumanDelayConfig } from "../../agents/identity.js";
|
|
4
4
|
import { resolveChunkMode, resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
|
5
5
|
import { buildCommandTextFromArgs, findCommandByNativeName, listChatCommands, parseCommandArgs, resolveCommandArgChoices, resolveCommandArgMenu, serializeCommandArgs, } from "../../auto-reply/commands-registry.js";
|
|
6
6
|
import { dispatchReplyWithDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
|
@@ -11,8 +11,10 @@ import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
|
|
11
11
|
import { loadWebMedia } from "../../web/media.js";
|
|
12
12
|
import { chunkDiscordTextWithMode } from "../chunk.js";
|
|
13
13
|
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { createReplyPrefixContext } from "../../channels/reply-prefix.js";
|
|
15
|
+
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
|
|
16
|
+
import { allowListMatches, isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordOwnerAllowFrom, resolveDiscordUserAllowed, } from "./allow-list.js";
|
|
17
|
+
import { resolveDiscordSenderIdentity } from "./sender-identity.js";
|
|
16
18
|
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
|
17
19
|
import { resolveDiscordThreadParentInfo } from "./threading.js";
|
|
18
20
|
function buildDiscordCommandOptions(params) {
|
|
@@ -357,6 +359,7 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
357
359
|
const user = interaction.user;
|
|
358
360
|
if (!user)
|
|
359
361
|
return;
|
|
362
|
+
const sender = resolveDiscordSenderIdentity({ author: user, pluralkitInfo: null });
|
|
360
363
|
const channel = interaction.channel;
|
|
361
364
|
const channelType = channel?.type;
|
|
362
365
|
const isDirectMessage = channelType === ChannelType.DM;
|
|
@@ -370,12 +373,13 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
370
373
|
const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
|
|
371
374
|
"discord:",
|
|
372
375
|
"user:",
|
|
376
|
+
"pk:",
|
|
373
377
|
]);
|
|
374
378
|
const ownerOk = ownerAllowList && user
|
|
375
379
|
? allowListMatches(ownerAllowList, {
|
|
376
|
-
id:
|
|
377
|
-
name:
|
|
378
|
-
tag:
|
|
380
|
+
id: sender.id,
|
|
381
|
+
name: sender.name,
|
|
382
|
+
tag: sender.tag,
|
|
379
383
|
})
|
|
380
384
|
: false;
|
|
381
385
|
const guildInfo = resolveDiscordGuildEntry({
|
|
@@ -447,12 +451,12 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
447
451
|
if (dmPolicy !== "open") {
|
|
448
452
|
const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
|
|
449
453
|
const effectiveAllowFrom = [...(discordConfig?.dm?.allowFrom ?? []), ...storeAllowFrom];
|
|
450
|
-
const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:"]);
|
|
454
|
+
const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:", "pk:"]);
|
|
451
455
|
const permitted = allowList
|
|
452
456
|
? allowListMatches(allowList, {
|
|
453
|
-
id:
|
|
454
|
-
name:
|
|
455
|
-
tag:
|
|
457
|
+
id: sender.id,
|
|
458
|
+
name: sender.name,
|
|
459
|
+
tag: sender.tag,
|
|
456
460
|
})
|
|
457
461
|
: false;
|
|
458
462
|
if (!permitted) {
|
|
@@ -462,8 +466,8 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
462
466
|
channel: "discord",
|
|
463
467
|
id: user.id,
|
|
464
468
|
meta: {
|
|
465
|
-
tag:
|
|
466
|
-
name:
|
|
469
|
+
tag: sender.tag,
|
|
470
|
+
name: sender.name ?? undefined,
|
|
467
471
|
},
|
|
468
472
|
});
|
|
469
473
|
if (created) {
|
|
@@ -488,9 +492,9 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
488
492
|
const userOk = hasUserAllowlist
|
|
489
493
|
? resolveDiscordUserAllowed({
|
|
490
494
|
allowList: channelUsers,
|
|
491
|
-
userId:
|
|
492
|
-
userName:
|
|
493
|
-
userTag:
|
|
495
|
+
userId: sender.id,
|
|
496
|
+
userName: sender.name,
|
|
497
|
+
userTag: sender.tag,
|
|
494
498
|
})
|
|
495
499
|
: false;
|
|
496
500
|
const authorizers = useAccessGroups
|
|
@@ -555,10 +559,17 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
555
559
|
kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel",
|
|
556
560
|
id: isDirectMessage ? user.id : channelId,
|
|
557
561
|
},
|
|
562
|
+
parentPeer: threadParentId ? { kind: "channel", id: threadParentId } : undefined,
|
|
558
563
|
});
|
|
559
564
|
const conversationLabel = isDirectMessage ? (user.globalName ?? user.username) : channelId;
|
|
565
|
+
const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
|
|
566
|
+
channelConfig,
|
|
567
|
+
guildInfo,
|
|
568
|
+
sender: { id: sender.id, name: sender.name, tag: sender.tag },
|
|
569
|
+
});
|
|
560
570
|
const ctxPayload = finalizeInboundContext({
|
|
561
571
|
Body: prompt,
|
|
572
|
+
BodyForAgent: prompt,
|
|
562
573
|
RawBody: prompt,
|
|
563
574
|
CommandBody: prompt,
|
|
564
575
|
CommandArgs: commandArgs,
|
|
@@ -576,19 +587,25 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
576
587
|
GroupSubject: isGuild ? interaction.guild?.name : undefined,
|
|
577
588
|
GroupSystemPrompt: isGuild
|
|
578
589
|
? (() => {
|
|
579
|
-
const
|
|
580
|
-
const channelDescription = channelTopic?.trim();
|
|
581
|
-
const systemPromptParts = [
|
|
582
|
-
channelDescription ? `Channel topic: ${channelDescription}` : null,
|
|
583
|
-
channelConfig?.systemPrompt?.trim() || null,
|
|
584
|
-
].filter((entry) => Boolean(entry));
|
|
590
|
+
const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter((entry) => Boolean(entry));
|
|
585
591
|
return systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
|
586
592
|
})()
|
|
587
593
|
: undefined,
|
|
594
|
+
UntrustedContext: isGuild
|
|
595
|
+
? (() => {
|
|
596
|
+
const channelTopic = channel && "topic" in channel ? (channel.topic ?? undefined) : undefined;
|
|
597
|
+
const untrustedChannelMetadata = buildUntrustedChannelMetadata({
|
|
598
|
+
source: "discord",
|
|
599
|
+
label: "Discord channel topic",
|
|
600
|
+
entries: [channelTopic],
|
|
601
|
+
});
|
|
602
|
+
return untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined;
|
|
603
|
+
})()
|
|
604
|
+
: undefined,
|
|
588
605
|
SenderName: user.globalName ?? user.username,
|
|
589
606
|
SenderId: user.id,
|
|
590
607
|
SenderUsername: user.username,
|
|
591
|
-
SenderTag:
|
|
608
|
+
SenderTag: sender.tag,
|
|
592
609
|
Provider: "discord",
|
|
593
610
|
Surface: "discord",
|
|
594
611
|
WasMentioned: true,
|
|
@@ -596,13 +613,16 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
596
613
|
Timestamp: Date.now(),
|
|
597
614
|
CommandAuthorized: commandAuthorized,
|
|
598
615
|
CommandSource: "native",
|
|
616
|
+
OwnerAllowFrom: ownerAllowFrom,
|
|
599
617
|
});
|
|
618
|
+
const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
|
|
600
619
|
let didReply = false;
|
|
601
620
|
await dispatchReplyWithDispatcher({
|
|
602
621
|
ctx: ctxPayload,
|
|
603
622
|
cfg,
|
|
604
623
|
dispatcherOptions: {
|
|
605
|
-
responsePrefix:
|
|
624
|
+
responsePrefix: prefixContext.responsePrefix,
|
|
625
|
+
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
606
626
|
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
|
607
627
|
deliver: async (payload) => {
|
|
608
628
|
try {
|
|
@@ -635,6 +655,7 @@ async function dispatchDiscordCommandInteraction(params) {
|
|
|
635
655
|
disableBlockStreaming: typeof discordConfig?.blockStreaming === "boolean"
|
|
636
656
|
? !discordConfig.blockStreaming
|
|
637
657
|
: undefined,
|
|
658
|
+
onModelSelected: prefixContext.onModelSelected,
|
|
638
659
|
},
|
|
639
660
|
});
|
|
640
661
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { formatDiscordUserTag } from "./format.js";
|
|
2
|
+
export function resolveDiscordWebhookId(message) {
|
|
3
|
+
const candidate = message.webhookId ?? message.webhook_id;
|
|
4
|
+
return typeof candidate === "string" && candidate.trim() ? candidate.trim() : null;
|
|
5
|
+
}
|
|
6
|
+
export function resolveDiscordSenderIdentity(params) {
|
|
7
|
+
const pkInfo = params.pluralkitInfo ?? null;
|
|
8
|
+
const pkMember = pkInfo?.member ?? undefined;
|
|
9
|
+
const pkSystem = pkInfo?.system ?? undefined;
|
|
10
|
+
const memberId = pkMember?.id?.trim();
|
|
11
|
+
const memberNameRaw = pkMember?.display_name ?? pkMember?.name ?? "";
|
|
12
|
+
const memberName = memberNameRaw?.trim();
|
|
13
|
+
if (memberId && memberName) {
|
|
14
|
+
const systemName = pkSystem?.name?.trim();
|
|
15
|
+
const label = systemName ? `${memberName} (PK:${systemName})` : `${memberName} (PK)`;
|
|
16
|
+
return {
|
|
17
|
+
id: memberId,
|
|
18
|
+
name: memberName,
|
|
19
|
+
tag: pkMember?.name?.trim() || undefined,
|
|
20
|
+
label,
|
|
21
|
+
isPluralKit: true,
|
|
22
|
+
pluralkit: {
|
|
23
|
+
memberId,
|
|
24
|
+
memberName,
|
|
25
|
+
systemId: pkSystem?.id?.trim() || undefined,
|
|
26
|
+
systemName,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const senderTag = formatDiscordUserTag(params.author);
|
|
31
|
+
const senderDisplay = params.member?.nickname ?? params.author.globalName ?? params.author.username;
|
|
32
|
+
const senderLabel = senderDisplay && senderTag && senderDisplay !== senderTag
|
|
33
|
+
? `${senderDisplay} (${senderTag})`
|
|
34
|
+
: (senderDisplay ?? senderTag ?? params.author.id);
|
|
35
|
+
return {
|
|
36
|
+
id: params.author.id,
|
|
37
|
+
name: params.author.username ?? undefined,
|
|
38
|
+
tag: senderTag,
|
|
39
|
+
label: senderLabel,
|
|
40
|
+
isPluralKit: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function resolveDiscordSenderLabel(params) {
|
|
44
|
+
return resolveDiscordSenderIdentity(params).label;
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveFetch } from "../infra/fetch.js";
|
|
2
|
+
const PLURALKIT_API_BASE = "https://api.pluralkit.me/v2";
|
|
3
|
+
export async function fetchPluralKitMessageInfo(params) {
|
|
4
|
+
if (!params.config?.enabled) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const fetchImpl = resolveFetch(params.fetcher);
|
|
8
|
+
if (!fetchImpl) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const headers = {};
|
|
12
|
+
if (params.config.token?.trim()) {
|
|
13
|
+
headers.Authorization = params.config.token.trim();
|
|
14
|
+
}
|
|
15
|
+
const res = await fetchImpl(`${PLURALKIT_API_BASE}/messages/${params.messageId}`, {
|
|
16
|
+
headers,
|
|
17
|
+
});
|
|
18
|
+
if (res.status === 404) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text().catch(() => "");
|
|
23
|
+
const detail = text.trim() ? `: ${text.trim()}` : "";
|
|
24
|
+
throw new Error(`PluralKit API failed (${res.status})${detail}`);
|
|
25
|
+
}
|
|
26
|
+
return (await res.json());
|
|
27
|
+
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { Routes } from "discord-api-types/v10";
|
|
1
|
+
import { ChannelType, Routes } from "discord-api-types/v10";
|
|
2
2
|
import { resolveChunkMode } from "../auto-reply/chunk.js";
|
|
3
3
|
import { loadConfig } from "../config/config.js";
|
|
4
4
|
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
|
5
5
|
import { recordChannelActivity } from "../infra/channel-activity.js";
|
|
6
6
|
import { convertMarkdownTables } from "../markdown/tables.js";
|
|
7
7
|
import { resolveDiscordAccount } from "./accounts.js";
|
|
8
|
-
import { buildDiscordSendError, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds,
|
|
8
|
+
import { buildDiscordSendError, buildDiscordTextChunks, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseAndResolveRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, } from "./send.shared.js";
|
|
9
|
+
/** Discord thread names are capped at 100 characters. */
|
|
10
|
+
const DISCORD_THREAD_NAME_LIMIT = 100;
|
|
11
|
+
/** Derive a thread title from the first non-empty line of the message text. */
|
|
12
|
+
function deriveForumThreadName(text) {
|
|
13
|
+
const firstLine = text
|
|
14
|
+
.split("\n")
|
|
15
|
+
.find((l) => l.trim())
|
|
16
|
+
?.trim() ?? "";
|
|
17
|
+
return firstLine.slice(0, DISCORD_THREAD_NAME_LIMIT) || new Date().toISOString().slice(0, 16);
|
|
18
|
+
}
|
|
19
|
+
/** Forum/Media channels cannot receive regular messages; detect them here. */
|
|
20
|
+
function isForumLikeType(channelType) {
|
|
21
|
+
return channelType === ChannelType.GuildForum || channelType === ChannelType.GuildMedia;
|
|
22
|
+
}
|
|
9
23
|
export async function sendMessageDiscord(to, text, opts = {}) {
|
|
10
24
|
const cfg = loadConfig();
|
|
11
25
|
const accountInfo = resolveDiscordAccount({
|
|
@@ -20,8 +34,81 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
20
34
|
const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId);
|
|
21
35
|
const textWithTables = convertMarkdownTables(text ?? "", tableMode);
|
|
22
36
|
const { token, rest, request } = createDiscordClient(opts, cfg);
|
|
23
|
-
const recipient =
|
|
37
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
24
38
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
39
|
+
// Forum/Media channels reject POST /messages; auto-create a thread post instead.
|
|
40
|
+
let channelType;
|
|
41
|
+
try {
|
|
42
|
+
const channel = (await rest.get(Routes.channel(channelId)));
|
|
43
|
+
channelType = channel?.type;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// If we can't fetch the channel, fall through to the normal send path.
|
|
47
|
+
}
|
|
48
|
+
if (isForumLikeType(channelType)) {
|
|
49
|
+
const threadName = deriveForumThreadName(textWithTables);
|
|
50
|
+
const chunks = buildDiscordTextChunks(textWithTables, {
|
|
51
|
+
maxLinesPerMessage: accountInfo.config.maxLinesPerMessage,
|
|
52
|
+
chunkMode,
|
|
53
|
+
});
|
|
54
|
+
const starterContent = chunks[0]?.trim() ? chunks[0] : threadName;
|
|
55
|
+
const starterEmbeds = opts.embeds?.length ? opts.embeds : undefined;
|
|
56
|
+
let threadRes;
|
|
57
|
+
try {
|
|
58
|
+
threadRes = (await request(() => rest.post(Routes.threads(channelId), {
|
|
59
|
+
body: {
|
|
60
|
+
name: threadName,
|
|
61
|
+
message: {
|
|
62
|
+
content: starterContent,
|
|
63
|
+
...(starterEmbeds ? { embeds: starterEmbeds } : {}),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}), "forum-thread"));
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw await buildDiscordSendError(err, {
|
|
70
|
+
channelId,
|
|
71
|
+
rest,
|
|
72
|
+
token,
|
|
73
|
+
hasMedia: Boolean(opts.mediaUrl),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const threadId = threadRes.id;
|
|
77
|
+
const messageId = threadRes.message?.id ?? threadId;
|
|
78
|
+
const resultChannelId = threadRes.message?.channel_id ?? threadId;
|
|
79
|
+
const remainingChunks = chunks.slice(1);
|
|
80
|
+
try {
|
|
81
|
+
if (opts.mediaUrl) {
|
|
82
|
+
const [mediaCaption, ...afterMediaChunks] = remainingChunks;
|
|
83
|
+
await sendDiscordMedia(rest, threadId, mediaCaption ?? "", opts.mediaUrl, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
84
|
+
for (const chunk of afterMediaChunks) {
|
|
85
|
+
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
for (const chunk of remainingChunks) {
|
|
90
|
+
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw await buildDiscordSendError(err, {
|
|
96
|
+
channelId: threadId,
|
|
97
|
+
rest,
|
|
98
|
+
token,
|
|
99
|
+
hasMedia: Boolean(opts.mediaUrl),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
recordChannelActivity({
|
|
103
|
+
channel: "discord",
|
|
104
|
+
accountId: accountInfo.accountId,
|
|
105
|
+
direction: "outbound",
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
messageId: messageId ? String(messageId) : "unknown",
|
|
109
|
+
channelId: String(resultChannelId ?? channelId),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
25
112
|
let result;
|
|
26
113
|
try {
|
|
27
114
|
if (opts.mediaUrl) {
|
|
@@ -52,7 +139,7 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
52
139
|
export async function sendStickerDiscord(to, stickerIds, opts = {}) {
|
|
53
140
|
const cfg = loadConfig();
|
|
54
141
|
const { rest, request } = createDiscordClient(opts, cfg);
|
|
55
|
-
const recipient =
|
|
142
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
56
143
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
57
144
|
const content = opts.content?.trim();
|
|
58
145
|
const stickers = normalizeStickerIds(stickerIds);
|
|
@@ -70,7 +157,7 @@ export async function sendStickerDiscord(to, stickerIds, opts = {}) {
|
|
|
70
157
|
export async function sendPollDiscord(to, poll, opts = {}) {
|
|
71
158
|
const cfg = loadConfig();
|
|
72
159
|
const { rest, request } = createDiscordClient(opts, cfg);
|
|
73
|
-
const recipient =
|
|
160
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
74
161
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
75
162
|
const content = opts.content?.trim();
|
|
76
163
|
const payload = normalizeDiscordPollInput(poll);
|
|
@@ -9,7 +9,7 @@ import { resolveDiscordAccount } from "./accounts.js";
|
|
|
9
9
|
import { chunkDiscordTextWithMode } from "./chunk.js";
|
|
10
10
|
import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js";
|
|
11
11
|
import { DiscordSendError } from "./send.types.js";
|
|
12
|
-
import { parseDiscordTarget } from "./targets.js";
|
|
12
|
+
import { parseDiscordTarget, resolveDiscordTarget } from "./targets.js";
|
|
13
13
|
import { normalizeDiscordToken } from "./token.js";
|
|
14
14
|
const DISCORD_TEXT_LIMIT = 2000;
|
|
15
15
|
const DISCORD_MAX_STICKERS = 3;
|
|
@@ -19,8 +19,9 @@ const DISCORD_MISSING_PERMISSIONS = 50013;
|
|
|
19
19
|
const DISCORD_CANNOT_DM = 50007;
|
|
20
20
|
function resolveToken(params) {
|
|
21
21
|
const explicit = normalizeDiscordToken(params.explicit);
|
|
22
|
-
if (explicit)
|
|
22
|
+
if (explicit) {
|
|
23
23
|
return explicit;
|
|
24
|
+
}
|
|
24
25
|
const fallback = normalizeDiscordToken(params.fallbackToken);
|
|
25
26
|
if (!fallback) {
|
|
26
27
|
throw new Error(`Discord bot token missing for account "${params.accountId}" (set discord.accounts.${params.accountId}.token or DISCORD_BOT_TOKEN for default).`);
|
|
@@ -68,6 +69,37 @@ function parseRecipient(raw) {
|
|
|
68
69
|
}
|
|
69
70
|
return { kind: target.kind, id: target.id };
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse and resolve Discord recipient, including username lookup.
|
|
74
|
+
* This enables sending DMs by username (e.g., "john.doe") by querying
|
|
75
|
+
* the Discord directory to resolve usernames to user IDs.
|
|
76
|
+
*
|
|
77
|
+
* @param raw - The recipient string (username, ID, or known format)
|
|
78
|
+
* @param accountId - Discord account ID to use for directory lookup
|
|
79
|
+
* @returns Parsed DiscordRecipient with resolved user ID if applicable
|
|
80
|
+
*/
|
|
81
|
+
export async function parseAndResolveRecipient(raw, accountId) {
|
|
82
|
+
const cfg = loadConfig();
|
|
83
|
+
const accountInfo = resolveDiscordAccount({ cfg, accountId });
|
|
84
|
+
// First try to resolve using directory lookup (handles usernames)
|
|
85
|
+
const trimmed = raw.trim();
|
|
86
|
+
const parseOptions = {
|
|
87
|
+
ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
|
|
88
|
+
};
|
|
89
|
+
const resolved = await resolveDiscordTarget(raw, {
|
|
90
|
+
cfg,
|
|
91
|
+
accountId: accountInfo.accountId,
|
|
92
|
+
}, parseOptions);
|
|
93
|
+
if (resolved) {
|
|
94
|
+
return { kind: resolved.kind, id: resolved.id };
|
|
95
|
+
}
|
|
96
|
+
// Fallback to standard parsing (for channels, etc.)
|
|
97
|
+
const parsed = parseDiscordTarget(raw, parseOptions);
|
|
98
|
+
if (!parsed) {
|
|
99
|
+
throw new Error("Recipient is required for Discord sends");
|
|
100
|
+
}
|
|
101
|
+
return { kind: parsed.kind, id: parsed.id };
|
|
102
|
+
}
|
|
71
103
|
function normalizeStickerIds(raw) {
|
|
72
104
|
const ids = raw.map((entry) => entry.trim()).filter(Boolean);
|
|
73
105
|
if (ids.length === 0) {
|
|
@@ -102,29 +134,33 @@ function normalizeDiscordPollInput(input) {
|
|
|
102
134
|
};
|
|
103
135
|
}
|
|
104
136
|
function getDiscordErrorCode(err) {
|
|
105
|
-
if (!err || typeof err !== "object")
|
|
137
|
+
if (!err || typeof err !== "object") {
|
|
106
138
|
return undefined;
|
|
139
|
+
}
|
|
107
140
|
const candidate = "code" in err && err.code !== undefined
|
|
108
141
|
? err.code
|
|
109
142
|
: "rawError" in err && err.rawError && typeof err.rawError === "object"
|
|
110
143
|
? err.rawError.code
|
|
111
144
|
: undefined;
|
|
112
|
-
if (typeof candidate === "number")
|
|
145
|
+
if (typeof candidate === "number") {
|
|
113
146
|
return candidate;
|
|
147
|
+
}
|
|
114
148
|
if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
|
|
115
149
|
return Number(candidate);
|
|
116
150
|
}
|
|
117
151
|
return undefined;
|
|
118
152
|
}
|
|
119
153
|
async function buildDiscordSendError(err, ctx) {
|
|
120
|
-
if (err instanceof DiscordSendError)
|
|
154
|
+
if (err instanceof DiscordSendError) {
|
|
121
155
|
return err;
|
|
156
|
+
}
|
|
122
157
|
const code = getDiscordErrorCode(err);
|
|
123
158
|
if (code === DISCORD_CANNOT_DM) {
|
|
124
159
|
return new DiscordSendError("discord dm failed: user blocks dms or privacy settings disallow it", { kind: "dm-blocked" });
|
|
125
160
|
}
|
|
126
|
-
if (code !== DISCORD_MISSING_PERMISSIONS)
|
|
161
|
+
if (code !== DISCORD_MISSING_PERMISSIONS) {
|
|
127
162
|
return err;
|
|
163
|
+
}
|
|
128
164
|
let missing = [];
|
|
129
165
|
try {
|
|
130
166
|
const permissions = await fetchChannelPermissionsDiscord(ctx.channelId, {
|
|
@@ -165,18 +201,26 @@ async function resolveChannelId(rest, recipient, request) {
|
|
|
165
201
|
}
|
|
166
202
|
return { channelId: dmChannel.id, dm: true };
|
|
167
203
|
}
|
|
204
|
+
export function buildDiscordTextChunks(text, opts = {}) {
|
|
205
|
+
if (!text) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
const chunks = chunkDiscordTextWithMode(text, {
|
|
209
|
+
maxChars: opts.maxChars ?? DISCORD_TEXT_LIMIT,
|
|
210
|
+
maxLines: opts.maxLinesPerMessage,
|
|
211
|
+
chunkMode: opts.chunkMode,
|
|
212
|
+
});
|
|
213
|
+
if (!chunks.length && text) {
|
|
214
|
+
chunks.push(text);
|
|
215
|
+
}
|
|
216
|
+
return chunks;
|
|
217
|
+
}
|
|
168
218
|
async function sendDiscordText(rest, channelId, text, replyTo, request, maxLinesPerMessage, embeds, chunkMode) {
|
|
169
219
|
if (!text.trim()) {
|
|
170
220
|
throw new Error("Message must be non-empty for Discord sends");
|
|
171
221
|
}
|
|
172
222
|
const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
|
|
173
|
-
const chunks =
|
|
174
|
-
maxChars: DISCORD_TEXT_LIMIT,
|
|
175
|
-
maxLines: maxLinesPerMessage,
|
|
176
|
-
chunkMode,
|
|
177
|
-
});
|
|
178
|
-
if (!chunks.length && text)
|
|
179
|
-
chunks.push(text);
|
|
223
|
+
const chunks = buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode });
|
|
180
224
|
if (chunks.length === 1) {
|
|
181
225
|
const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
182
226
|
body: {
|
|
@@ -206,15 +250,7 @@ async function sendDiscordText(rest, channelId, text, replyTo, request, maxLines
|
|
|
206
250
|
}
|
|
207
251
|
async function sendDiscordMedia(rest, channelId, text, mediaUrl, replyTo, request, maxLinesPerMessage, embeds, chunkMode) {
|
|
208
252
|
const media = await loadWebMedia(mediaUrl);
|
|
209
|
-
const chunks = text
|
|
210
|
-
? chunkDiscordTextWithMode(text, {
|
|
211
|
-
maxChars: DISCORD_TEXT_LIMIT,
|
|
212
|
-
maxLines: maxLinesPerMessage,
|
|
213
|
-
chunkMode,
|
|
214
|
-
})
|
|
215
|
-
: [];
|
|
216
|
-
if (!chunks.length && text)
|
|
217
|
-
chunks.push(text);
|
|
253
|
+
const chunks = text ? buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode }) : [];
|
|
218
254
|
const caption = chunks[0] ?? "";
|
|
219
255
|
const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
|
|
220
256
|
const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
@@ -231,8 +267,9 @@ async function sendDiscordMedia(rest, channelId, text, mediaUrl, replyTo, reques
|
|
|
231
267
|
},
|
|
232
268
|
}), "media"));
|
|
233
269
|
for (const chunk of chunks.slice(1)) {
|
|
234
|
-
if (!chunk.trim())
|
|
270
|
+
if (!chunk.trim()) {
|
|
235
271
|
continue;
|
|
272
|
+
}
|
|
236
273
|
await sendDiscordText(rest, channelId, chunk, undefined, request, maxLinesPerMessage, undefined, chunkMode);
|
|
237
274
|
}
|
|
238
275
|
return res;
|
package/dist/discord/targets.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { buildMessagingTarget, ensureTargetId, requireTargetKind, } from "../channels/targets.js";
|
|
2
|
+
import { listDiscordDirectoryPeersLive } from "./directory-live.js";
|
|
2
3
|
export function parseDiscordTarget(raw, options = {}) {
|
|
3
4
|
const trimmed = raw.trim();
|
|
4
|
-
if (!trimmed)
|
|
5
|
+
if (!trimmed) {
|
|
5
6
|
return undefined;
|
|
7
|
+
}
|
|
6
8
|
const mentionMatch = trimmed.match(/^<@!?(\d+)>$/);
|
|
7
9
|
if (mentionMatch) {
|
|
8
10
|
return buildMessagingTarget("user", mentionMatch[1], trimmed);
|
|
@@ -41,3 +43,84 @@ export function resolveDiscordChannelId(raw) {
|
|
|
41
43
|
const target = parseDiscordTarget(raw, { defaultKind: "channel" });
|
|
42
44
|
return requireTargetKind({ platform: "Discord", target, kind: "channel" });
|
|
43
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a Discord username to user ID using the directory lookup.
|
|
48
|
+
* This enables sending DMs by username instead of requiring explicit user IDs.
|
|
49
|
+
*
|
|
50
|
+
* @param raw - The username or raw target string (e.g., "john.doe")
|
|
51
|
+
* @param options - Directory configuration params (cfg, accountId, limit)
|
|
52
|
+
* @param parseOptions - Messaging target parsing options (defaults, ambiguity message)
|
|
53
|
+
* @returns Parsed MessagingTarget with user ID, or undefined if not found
|
|
54
|
+
*/
|
|
55
|
+
export async function resolveDiscordTarget(raw, options, parseOptions = {}) {
|
|
56
|
+
const trimmed = raw.trim();
|
|
57
|
+
if (!trimmed) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const likelyUsername = isLikelyUsername(trimmed);
|
|
61
|
+
const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername;
|
|
62
|
+
// Parse directly if it's already a known format. Use a safe parse so ambiguous
|
|
63
|
+
// numeric targets don't throw when we still want to attempt username lookup.
|
|
64
|
+
const directParse = safeParseDiscordTarget(trimmed, parseOptions);
|
|
65
|
+
if (directParse && directParse.kind !== "channel" && !likelyUsername) {
|
|
66
|
+
return directParse;
|
|
67
|
+
}
|
|
68
|
+
if (!shouldLookup) {
|
|
69
|
+
return directParse ?? parseDiscordTarget(trimmed, parseOptions);
|
|
70
|
+
}
|
|
71
|
+
// Try to resolve as a username via directory lookup
|
|
72
|
+
try {
|
|
73
|
+
const directoryEntries = await listDiscordDirectoryPeersLive({
|
|
74
|
+
...options,
|
|
75
|
+
query: trimmed,
|
|
76
|
+
limit: 1,
|
|
77
|
+
});
|
|
78
|
+
const match = directoryEntries[0];
|
|
79
|
+
if (match && match.kind === "user") {
|
|
80
|
+
// Extract user ID from the directory entry (format: "user:<id>")
|
|
81
|
+
const userId = match.id.replace(/^user:/, "");
|
|
82
|
+
return buildMessagingTarget("user", userId, trimmed);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Directory lookup failed - fall through to parse as-is
|
|
87
|
+
// This preserves existing behavior for channel names
|
|
88
|
+
}
|
|
89
|
+
// Fallback to original parsing (for channels, etc.)
|
|
90
|
+
return parseDiscordTarget(trimmed, parseOptions);
|
|
91
|
+
}
|
|
92
|
+
function safeParseDiscordTarget(input, options) {
|
|
93
|
+
try {
|
|
94
|
+
return parseDiscordTarget(input, options);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function isExplicitUserLookup(input, options) {
|
|
101
|
+
if (/^<@!?(\d+)>$/.test(input)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (/^(user:|discord:)/.test(input)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (input.startsWith("@")) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (/^\d+$/.test(input)) {
|
|
111
|
+
return options.defaultKind === "user";
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if a string looks like a Discord username (not a mention, prefix, or ID).
|
|
117
|
+
* Usernames typically don't start with special characters except underscore.
|
|
118
|
+
*/
|
|
119
|
+
function isLikelyUsername(input) {
|
|
120
|
+
// Skip if it's already a known format
|
|
121
|
+
if (/^(user:|channel:|discord:|@|<@!?)|[\d]+$/.test(input)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
// Likely a username if it doesn't match known patterns
|
|
125
|
+
return true;
|
|
126
|
+
}
|
package/dist/entry.js
CHANGED
|
@@ -3,32 +3,38 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
|
|
6
|
-
import { isTruthyEnvValue } from "./infra/env.js";
|
|
6
|
+
import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js";
|
|
7
7
|
import { installProcessWarningFilter } from "./infra/warnings.js";
|
|
8
8
|
import { attachChildProcessBridge } from "./process/child-process-bridge.js";
|
|
9
9
|
process.title = "poolbot";
|
|
10
10
|
installProcessWarningFilter();
|
|
11
|
+
normalizeEnv();
|
|
11
12
|
if (process.argv.includes("--no-color")) {
|
|
12
13
|
process.env.NO_COLOR = "1";
|
|
13
14
|
process.env.FORCE_COLOR = "0";
|
|
14
15
|
}
|
|
15
16
|
const EXPERIMENTAL_WARNING_FLAG = "--disable-warning=ExperimentalWarning";
|
|
16
|
-
function hasExperimentalWarningSuppressed(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function hasExperimentalWarningSuppressed() {
|
|
18
|
+
const nodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
19
|
+
if (nodeOptions.includes(EXPERIMENTAL_WARNING_FLAG) || nodeOptions.includes("--no-warnings"))
|
|
20
|
+
return true;
|
|
21
|
+
for (const arg of process.execArgv) {
|
|
22
|
+
if (arg === EXPERIMENTAL_WARNING_FLAG || arg === "--no-warnings")
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
20
26
|
}
|
|
21
27
|
function ensureExperimentalWarningSuppressed() {
|
|
22
28
|
if (isTruthyEnvValue(process.env.CLAWDBOT_NO_RESPAWN))
|
|
23
29
|
return false;
|
|
24
30
|
if (isTruthyEnvValue(process.env.CLAWDBOT_NODE_OPTIONS_READY))
|
|
25
31
|
return false;
|
|
26
|
-
|
|
27
|
-
if (hasExperimentalWarningSuppressed(nodeOptions))
|
|
32
|
+
if (hasExperimentalWarningSuppressed())
|
|
28
33
|
return false;
|
|
34
|
+
// Respawn guard (and keep recursion bounded if something goes wrong).
|
|
29
35
|
process.env.CLAWDBOT_NODE_OPTIONS_READY = "1";
|
|
30
|
-
|
|
31
|
-
const child = spawn(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
|
|
36
|
+
// Pass flag as a Node CLI option, not via NODE_OPTIONS (--disable-warning is disallowed in NODE_OPTIONS).
|
|
37
|
+
const child = spawn(process.execPath, [EXPERIMENTAL_WARNING_FLAG, ...process.execArgv, ...process.argv.slice(1)], {
|
|
32
38
|
stdio: "inherit",
|
|
33
39
|
env: process.env,
|
|
34
40
|
});
|