@poolzin/pool-bot 2026.2.20 → 2026.2.22
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/CHANGELOG.md +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-auth.js +12 -0
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-fallback.js +24 -0
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -80
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/provider/config-loader.js +76 -0
- package/dist/agents/provider/index.js +15 -0
- package/dist/agents/provider/integration.js +136 -0
- package/dist/agents/provider/models-dev.js +129 -0
- package/dist/agents/provider/rate-limits.js +458 -0
- package/dist/agents/provider/request-monitor.js +449 -0
- package/dist/agents/provider/session-binding.js +376 -0
- package/dist/agents/provider/token-pool.js +541 -0
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/plcode-controller/SKILL.md +156 -0
- package/skills/plcode-controller/assets/operator-prompts.md +65 -0
- package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
- package/skills/plcode-controller/references/failure-handling.md +60 -0
- package/skills/plcode-controller/references/model-selection.md +57 -0
- package/skills/plcode-controller/references/plan-vs-build.md +52 -0
- package/skills/plcode-controller/references/question-handling.md +40 -0
- package/skills/plcode-controller/references/session-management.md +63 -0
- package/skills/plcode-controller/references/workflow.md +35 -0
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -4,19 +4,53 @@ import { createReplyReferencePlanner } from "../../auto-reply/reply/reply-refere
|
|
|
4
4
|
import { logVerbose } from "../../globals.js";
|
|
5
5
|
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
|
6
6
|
import { truncateUtf16Safe } from "../../utils.js";
|
|
7
|
-
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
|
7
|
+
import { resolveDiscordChannelInfo, resolveDiscordMessageChannelId } from "./message-utils.js";
|
|
8
|
+
// Cache configuration: 5 minute TTL (thread starters rarely change), max 500 entries
|
|
9
|
+
const DISCORD_THREAD_STARTER_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
10
|
+
const DISCORD_THREAD_STARTER_CACHE_MAX = 500;
|
|
8
11
|
const DISCORD_THREAD_STARTER_CACHE = new Map();
|
|
9
12
|
export function __resetDiscordThreadStarterCacheForTest() {
|
|
10
13
|
DISCORD_THREAD_STARTER_CACHE.clear();
|
|
11
14
|
}
|
|
15
|
+
// Get cached entry with TTL check, refresh LRU position on hit
|
|
16
|
+
function getCachedThreadStarter(key, now) {
|
|
17
|
+
const entry = DISCORD_THREAD_STARTER_CACHE.get(key);
|
|
18
|
+
if (!entry) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// Check TTL expiry
|
|
22
|
+
if (now - entry.updatedAt > DISCORD_THREAD_STARTER_CACHE_TTL_MS) {
|
|
23
|
+
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
// Refresh LRU position by re-inserting (Map maintains insertion order)
|
|
27
|
+
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
|
28
|
+
DISCORD_THREAD_STARTER_CACHE.set(key, { ...entry, updatedAt: now });
|
|
29
|
+
return entry.value;
|
|
30
|
+
}
|
|
31
|
+
// Set cached entry with LRU eviction when max size exceeded
|
|
32
|
+
function setCachedThreadStarter(key, value, now) {
|
|
33
|
+
// Remove existing entry first (to update LRU position)
|
|
34
|
+
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
|
35
|
+
DISCORD_THREAD_STARTER_CACHE.set(key, { value, updatedAt: now });
|
|
36
|
+
// Evict oldest entries (first in Map) when over max size
|
|
37
|
+
while (DISCORD_THREAD_STARTER_CACHE.size > DISCORD_THREAD_STARTER_CACHE_MAX) {
|
|
38
|
+
const iter = DISCORD_THREAD_STARTER_CACHE.keys().next();
|
|
39
|
+
if (iter.done) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
DISCORD_THREAD_STARTER_CACHE.delete(iter.value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
12
45
|
function isDiscordThreadType(type) {
|
|
13
46
|
return (type === ChannelType.PublicThread ||
|
|
14
47
|
type === ChannelType.PrivateThread ||
|
|
15
48
|
type === ChannelType.AnnouncementThread);
|
|
16
49
|
}
|
|
17
50
|
export function resolveDiscordThreadChannel(params) {
|
|
18
|
-
if (!params.isGuildMessage)
|
|
51
|
+
if (!params.isGuildMessage) {
|
|
19
52
|
return null;
|
|
53
|
+
}
|
|
20
54
|
const { message, channelInfo } = params;
|
|
21
55
|
const channel = "channel" in message ? message.channel : undefined;
|
|
22
56
|
const isThreadChannel = channel &&
|
|
@@ -24,12 +58,21 @@ export function resolveDiscordThreadChannel(params) {
|
|
|
24
58
|
"isThread" in channel &&
|
|
25
59
|
typeof channel.isThread === "function" &&
|
|
26
60
|
channel.isThread();
|
|
27
|
-
if (isThreadChannel)
|
|
61
|
+
if (isThreadChannel) {
|
|
28
62
|
return channel;
|
|
29
|
-
|
|
63
|
+
}
|
|
64
|
+
if (!isDiscordThreadType(channelInfo?.type)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const messageChannelId = params.messageChannelId ||
|
|
68
|
+
resolveDiscordMessageChannelId({
|
|
69
|
+
message,
|
|
70
|
+
});
|
|
71
|
+
if (!messageChannelId) {
|
|
30
72
|
return null;
|
|
73
|
+
}
|
|
31
74
|
return {
|
|
32
|
-
id:
|
|
75
|
+
id: messageChannelId,
|
|
33
76
|
name: channelInfo?.name ?? undefined,
|
|
34
77
|
parentId: channelInfo?.parentId ?? undefined,
|
|
35
78
|
parent: undefined,
|
|
@@ -39,8 +82,9 @@ export function resolveDiscordThreadChannel(params) {
|
|
|
39
82
|
export async function resolveDiscordThreadParentInfo(params) {
|
|
40
83
|
const { threadChannel, channelInfo, client } = params;
|
|
41
84
|
const parentId = threadChannel.parentId ?? threadChannel.parent?.id ?? channelInfo?.parentId ?? undefined;
|
|
42
|
-
if (!parentId)
|
|
85
|
+
if (!parentId) {
|
|
43
86
|
return {};
|
|
87
|
+
}
|
|
44
88
|
let parentName = threadChannel.parent?.name;
|
|
45
89
|
const parentInfo = await resolveDiscordChannelInfo(client, parentId);
|
|
46
90
|
parentName = parentName ?? parentInfo?.name;
|
|
@@ -49,21 +93,26 @@ export async function resolveDiscordThreadParentInfo(params) {
|
|
|
49
93
|
}
|
|
50
94
|
export async function resolveDiscordThreadStarter(params) {
|
|
51
95
|
const cacheKey = params.channel.id;
|
|
52
|
-
const
|
|
53
|
-
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const cached = getCachedThreadStarter(cacheKey, now);
|
|
98
|
+
if (cached) {
|
|
54
99
|
return cached;
|
|
100
|
+
}
|
|
55
101
|
try {
|
|
56
102
|
const parentType = params.parentType;
|
|
57
103
|
const isForumParent = parentType === ChannelType.GuildForum || parentType === ChannelType.GuildMedia;
|
|
58
104
|
const messageChannelId = isForumParent ? params.channel.id : params.parentId;
|
|
59
|
-
if (!messageChannelId)
|
|
105
|
+
if (!messageChannelId) {
|
|
60
106
|
return null;
|
|
107
|
+
}
|
|
61
108
|
const starter = (await params.client.rest.get(Routes.channelMessage(messageChannelId, params.channel.id)));
|
|
62
|
-
if (!starter)
|
|
109
|
+
if (!starter) {
|
|
63
110
|
return null;
|
|
111
|
+
}
|
|
64
112
|
const text = starter.content?.trim() ?? starter.embeds?.[0]?.description?.trim() ?? "";
|
|
65
|
-
if (!text)
|
|
113
|
+
if (!text) {
|
|
66
114
|
return null;
|
|
115
|
+
}
|
|
67
116
|
const author = starter.member?.nick ??
|
|
68
117
|
starter.member?.displayName ??
|
|
69
118
|
(starter.author
|
|
@@ -77,7 +126,7 @@ export async function resolveDiscordThreadStarter(params) {
|
|
|
77
126
|
author,
|
|
78
127
|
timestamp: timestamp ?? undefined,
|
|
79
128
|
};
|
|
80
|
-
|
|
129
|
+
setCachedThreadStarter(cacheKey, payload, Date.now());
|
|
81
130
|
return payload;
|
|
82
131
|
}
|
|
83
132
|
catch {
|
|
@@ -85,13 +134,16 @@ export async function resolveDiscordThreadStarter(params) {
|
|
|
85
134
|
}
|
|
86
135
|
}
|
|
87
136
|
export function resolveDiscordReplyTarget(opts) {
|
|
88
|
-
if (opts.replyToMode === "off")
|
|
137
|
+
if (opts.replyToMode === "off") {
|
|
89
138
|
return undefined;
|
|
139
|
+
}
|
|
90
140
|
const replyToId = opts.replyToId?.trim();
|
|
91
|
-
if (!replyToId)
|
|
141
|
+
if (!replyToId) {
|
|
92
142
|
return undefined;
|
|
93
|
-
|
|
143
|
+
}
|
|
144
|
+
if (opts.replyToMode === "all") {
|
|
94
145
|
return replyToId;
|
|
146
|
+
}
|
|
95
147
|
return opts.hasReplied ? undefined : replyToId;
|
|
96
148
|
}
|
|
97
149
|
export function sanitizeDiscordThreadName(rawName, fallbackId) {
|
|
@@ -107,11 +159,13 @@ export function sanitizeDiscordThreadName(rawName, fallbackId) {
|
|
|
107
159
|
}
|
|
108
160
|
export function resolveDiscordAutoThreadContext(params) {
|
|
109
161
|
const createdThreadId = String(params.createdThreadId ?? "").trim();
|
|
110
|
-
if (!createdThreadId)
|
|
162
|
+
if (!createdThreadId) {
|
|
111
163
|
return null;
|
|
164
|
+
}
|
|
112
165
|
const messageChannelId = params.messageChannelId.trim();
|
|
113
|
-
if (!messageChannelId)
|
|
166
|
+
if (!messageChannelId) {
|
|
114
167
|
return null;
|
|
168
|
+
}
|
|
115
169
|
const threadSessionKey = buildAgentSessionKey({
|
|
116
170
|
agentId: params.agentId,
|
|
117
171
|
channel: params.channel,
|
|
@@ -132,13 +186,21 @@ export function resolveDiscordAutoThreadContext(params) {
|
|
|
132
186
|
};
|
|
133
187
|
}
|
|
134
188
|
export async function resolveDiscordAutoThreadReplyPlan(params) {
|
|
135
|
-
const
|
|
189
|
+
const messageChannelId = (params.messageChannelId ||
|
|
190
|
+
resolveDiscordMessageChannelId({
|
|
191
|
+
message: params.message,
|
|
192
|
+
})).trim();
|
|
193
|
+
// Prefer the resolved thread channel ID when available so replies stay in-thread.
|
|
194
|
+
const targetChannelId = params.threadChannel?.id ?? (messageChannelId || "unknown");
|
|
195
|
+
const originalReplyTarget = `channel:${targetChannelId}`;
|
|
136
196
|
const createdThreadId = await maybeCreateDiscordAutoThread({
|
|
137
197
|
client: params.client,
|
|
138
198
|
message: params.message,
|
|
199
|
+
messageChannelId: messageChannelId || undefined,
|
|
139
200
|
isGuildMessage: params.isGuildMessage,
|
|
140
201
|
channelConfig: params.channelConfig,
|
|
141
202
|
threadChannel: params.threadChannel,
|
|
203
|
+
channelType: params.channelType,
|
|
142
204
|
baseText: params.baseText,
|
|
143
205
|
combinedBody: params.combinedBody,
|
|
144
206
|
});
|
|
@@ -153,22 +215,39 @@ export async function resolveDiscordAutoThreadReplyPlan(params) {
|
|
|
153
215
|
? resolveDiscordAutoThreadContext({
|
|
154
216
|
agentId: params.agentId,
|
|
155
217
|
channel: params.channel,
|
|
156
|
-
messageChannelId
|
|
218
|
+
messageChannelId,
|
|
157
219
|
createdThreadId,
|
|
158
220
|
})
|
|
159
221
|
: null;
|
|
160
222
|
return { ...deliveryPlan, createdThreadId, autoThreadContext };
|
|
161
223
|
}
|
|
162
224
|
export async function maybeCreateDiscordAutoThread(params) {
|
|
163
|
-
if (!params.isGuildMessage)
|
|
225
|
+
if (!params.isGuildMessage) {
|
|
164
226
|
return undefined;
|
|
165
|
-
|
|
227
|
+
}
|
|
228
|
+
if (!params.channelConfig?.autoThread) {
|
|
166
229
|
return undefined;
|
|
167
|
-
|
|
230
|
+
}
|
|
231
|
+
if (params.threadChannel) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
// Avoid creating threads in channels that don't support it or are already forums
|
|
235
|
+
if (params.channelType === ChannelType.GuildForum ||
|
|
236
|
+
params.channelType === ChannelType.GuildMedia ||
|
|
237
|
+
params.channelType === ChannelType.GuildVoice ||
|
|
238
|
+
params.channelType === ChannelType.GuildStageVoice) {
|
|
168
239
|
return undefined;
|
|
240
|
+
}
|
|
241
|
+
const messageChannelId = (params.messageChannelId ||
|
|
242
|
+
resolveDiscordMessageChannelId({
|
|
243
|
+
message: params.message,
|
|
244
|
+
})).trim();
|
|
245
|
+
if (!messageChannelId) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
169
248
|
try {
|
|
170
249
|
const threadName = sanitizeDiscordThreadName(params.baseText || params.combinedBody || "Thread", params.message.id);
|
|
171
|
-
const created = (await params.client.rest.post(`${Routes.channelMessage(
|
|
250
|
+
const created = (await params.client.rest.post(`${Routes.channelMessage(messageChannelId, params.message.id)}/threads`, {
|
|
172
251
|
body: {
|
|
173
252
|
name: threadName,
|
|
174
253
|
auto_archive_duration: 60,
|
|
@@ -178,7 +257,20 @@ export async function maybeCreateDiscordAutoThread(params) {
|
|
|
178
257
|
return createdId || undefined;
|
|
179
258
|
}
|
|
180
259
|
catch (err) {
|
|
181
|
-
logVerbose(`discord: autoThread failed for ${
|
|
260
|
+
logVerbose(`discord: autoThread creation failed for ${messageChannelId}/${params.message.id}: ${String(err)}`);
|
|
261
|
+
// Race condition: another agent may have already created a thread on this
|
|
262
|
+
// message. Re-fetch the message to check for an existing thread.
|
|
263
|
+
try {
|
|
264
|
+
const msg = (await params.client.rest.get(Routes.channelMessage(messageChannelId, params.message.id)));
|
|
265
|
+
const existingThreadId = msg?.thread?.id ? String(msg.thread.id) : "";
|
|
266
|
+
if (existingThreadId) {
|
|
267
|
+
logVerbose(`discord: autoThread reusing existing thread ${existingThreadId} on ${messageChannelId}/${params.message.id}`);
|
|
268
|
+
return existingThreadId;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// If the refetch also fails, fall through to return undefined.
|
|
273
|
+
}
|
|
182
274
|
return undefined;
|
|
183
275
|
}
|
|
184
276
|
}
|
|
@@ -186,6 +278,7 @@ export function resolveDiscordReplyDeliveryPlan(params) {
|
|
|
186
278
|
const originalReplyTarget = params.replyTarget;
|
|
187
279
|
let deliverTarget = originalReplyTarget;
|
|
188
280
|
let replyTarget = originalReplyTarget;
|
|
281
|
+
// When a new thread was created, route to the new thread.
|
|
189
282
|
if (params.createdThreadId) {
|
|
190
283
|
deliverTarget = `channel:${params.createdThreadId}`;
|
|
191
284
|
replyTarget = deliverTarget;
|
package/dist/discord/send.js
CHANGED
|
@@ -2,6 +2,7 @@ export { createChannelDiscord, deleteChannelDiscord, editChannelDiscord, moveCha
|
|
|
2
2
|
export { listGuildEmojisDiscord, uploadEmojiDiscord, uploadStickerDiscord, } from "./send.emojis-stickers.js";
|
|
3
3
|
export { addRoleDiscord, banMemberDiscord, createScheduledEventDiscord, fetchChannelInfoDiscord, fetchMemberInfoDiscord, fetchRoleInfoDiscord, fetchVoiceStatusDiscord, kickMemberDiscord, listGuildChannelsDiscord, listScheduledEventsDiscord, removeRoleDiscord, timeoutMemberDiscord, } from "./send.guild.js";
|
|
4
4
|
export { createThreadDiscord, deleteMessageDiscord, editMessageDiscord, fetchMessageDiscord, listPinsDiscord, listThreadsDiscord, pinMessageDiscord, readMessagesDiscord, searchMessagesDiscord, unpinMessageDiscord, } from "./send.messages.js";
|
|
5
|
-
export { sendMessageDiscord, sendPollDiscord, sendStickerDiscord } from "./send.outbound.js";
|
|
5
|
+
export { sendMessageDiscord, sendPollDiscord, sendStickerDiscord, sendVoiceMessageDiscord, } from "./send.outbound.js";
|
|
6
|
+
export { sendDiscordComponentMessage } from "./send.components.js";
|
|
6
7
|
export { fetchChannelPermissionsDiscord, fetchReactionsDiscord, reactMessageDiscord, removeOwnReactionsDiscord, removeReactionDiscord, } from "./send.reactions.js";
|
|
7
8
|
export { DiscordSendError } from "./send.types.js";
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { serializePayload } from "@buape/carbon";
|
|
1
5
|
import { ChannelType, Routes } from "discord-api-types/v10";
|
|
2
6
|
import { resolveChunkMode } from "../auto-reply/chunk.js";
|
|
3
7
|
import { loadConfig } from "../config/config.js";
|
|
4
8
|
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
|
5
9
|
import { recordChannelActivity } from "../infra/channel-activity.js";
|
|
10
|
+
import { resolvePreferredPoolbotTmpDir } from "../infra/tmp-poolbot-dir.js";
|
|
6
11
|
import { convertMarkdownTables } from "../markdown/tables.js";
|
|
12
|
+
import { maxBytesForKind } from "../media/constants.js";
|
|
13
|
+
import { extensionForMime } from "../media/mime.js";
|
|
14
|
+
import { loadWebMediaRaw } from "../web/media.js";
|
|
7
15
|
import { resolveDiscordAccount } from "./accounts.js";
|
|
8
|
-
import { buildDiscordSendError, buildDiscordTextChunks, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseAndResolveRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, } from "./send.shared.js";
|
|
16
|
+
import { buildDiscordMessagePayload, buildDiscordSendError, buildDiscordTextChunks, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseAndResolveRecipient, resolveChannelId, resolveDiscordSendComponents, resolveDiscordSendEmbeds, sendDiscordMedia, sendDiscordText, stripUndefinedFields, SUPPRESS_NOTIFICATIONS_FLAG, } from "./send.shared.js";
|
|
17
|
+
import { ensureOggOpus, getVoiceMessageMetadata, sendDiscordVoiceMessage, } from "./voice-message.js";
|
|
9
18
|
/** Discord thread names are capped at 100 characters. */
|
|
10
19
|
const DISCORD_THREAD_NAME_LIMIT = 100;
|
|
11
20
|
/** Derive a thread title from the first non-empty line of the message text. */
|
|
@@ -52,16 +61,25 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
52
61
|
chunkMode,
|
|
53
62
|
});
|
|
54
63
|
const starterContent = chunks[0]?.trim() ? chunks[0] : threadName;
|
|
55
|
-
const
|
|
64
|
+
const starterComponents = resolveDiscordSendComponents({
|
|
65
|
+
components: opts.components,
|
|
66
|
+
text: starterContent,
|
|
67
|
+
isFirst: true,
|
|
68
|
+
});
|
|
69
|
+
const starterEmbeds = resolveDiscordSendEmbeds({ embeds: opts.embeds, isFirst: true });
|
|
70
|
+
const silentFlags = opts.silent ? 1 << 12 : undefined;
|
|
71
|
+
const starterPayload = buildDiscordMessagePayload({
|
|
72
|
+
text: starterContent,
|
|
73
|
+
components: starterComponents,
|
|
74
|
+
embeds: starterEmbeds,
|
|
75
|
+
flags: silentFlags,
|
|
76
|
+
});
|
|
56
77
|
let threadRes;
|
|
57
78
|
try {
|
|
58
79
|
threadRes = (await request(() => rest.post(Routes.threads(channelId), {
|
|
59
80
|
body: {
|
|
60
81
|
name: threadName,
|
|
61
|
-
message:
|
|
62
|
-
content: starterContent,
|
|
63
|
-
...(starterEmbeds ? { embeds: starterEmbeds } : {}),
|
|
64
|
-
},
|
|
82
|
+
message: stripUndefinedFields(serializePayload(starterPayload)),
|
|
65
83
|
},
|
|
66
84
|
}), "forum-thread"));
|
|
67
85
|
}
|
|
@@ -80,14 +98,14 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
80
98
|
try {
|
|
81
99
|
if (opts.mediaUrl) {
|
|
82
100
|
const [mediaCaption, ...afterMediaChunks] = remainingChunks;
|
|
83
|
-
await sendDiscordMedia(rest, threadId, mediaCaption ?? "", opts.mediaUrl, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
101
|
+
await sendDiscordMedia(rest, threadId, mediaCaption ?? "", opts.mediaUrl, opts.mediaLocalRoots, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, undefined, chunkMode, opts.silent);
|
|
84
102
|
for (const chunk of afterMediaChunks) {
|
|
85
|
-
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
103
|
+
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, undefined, chunkMode, opts.silent);
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
else {
|
|
89
107
|
for (const chunk of remainingChunks) {
|
|
90
|
-
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
|
|
108
|
+
await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, undefined, chunkMode, opts.silent);
|
|
91
109
|
}
|
|
92
110
|
}
|
|
93
111
|
}
|
|
@@ -112,10 +130,10 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
112
130
|
let result;
|
|
113
131
|
try {
|
|
114
132
|
if (opts.mediaUrl) {
|
|
115
|
-
result = await sendDiscordMedia(rest, channelId, textWithTables, opts.mediaUrl, opts.replyTo, request, accountInfo.config.maxLinesPerMessage, opts.embeds, chunkMode);
|
|
133
|
+
result = await sendDiscordMedia(rest, channelId, textWithTables, opts.mediaUrl, opts.mediaLocalRoots, opts.replyTo, request, accountInfo.config.maxLinesPerMessage, opts.components, opts.embeds, chunkMode, opts.silent);
|
|
116
134
|
}
|
|
117
135
|
else {
|
|
118
|
-
result = await sendDiscordText(rest, channelId, textWithTables, opts.replyTo, request, accountInfo.config.maxLinesPerMessage, opts.embeds, chunkMode);
|
|
136
|
+
result = await sendDiscordText(rest, channelId, textWithTables, opts.replyTo, request, accountInfo.config.maxLinesPerMessage, opts.components, opts.embeds, chunkMode, opts.silent);
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
catch (err) {
|
|
@@ -160,11 +178,16 @@ export async function sendPollDiscord(to, poll, opts = {}) {
|
|
|
160
178
|
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
161
179
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
162
180
|
const content = opts.content?.trim();
|
|
181
|
+
if (poll.durationSeconds !== undefined) {
|
|
182
|
+
throw new Error("Discord polls do not support durationSeconds; use durationHours");
|
|
183
|
+
}
|
|
163
184
|
const payload = normalizeDiscordPollInput(poll);
|
|
185
|
+
const flags = opts.silent ? SUPPRESS_NOTIFICATIONS_FLAG : undefined;
|
|
164
186
|
const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
165
187
|
body: {
|
|
166
188
|
content: content || undefined,
|
|
167
189
|
poll: payload,
|
|
190
|
+
...(flags ? { flags } : {}),
|
|
168
191
|
},
|
|
169
192
|
}), "poll"));
|
|
170
193
|
return {
|
|
@@ -172,3 +195,93 @@ export async function sendPollDiscord(to, poll, opts = {}) {
|
|
|
172
195
|
channelId: String(res.channel_id ?? channelId),
|
|
173
196
|
};
|
|
174
197
|
}
|
|
198
|
+
async function materializeVoiceMessageInput(mediaUrl) {
|
|
199
|
+
// Security: reuse the standard media loader so we apply SSRF guards + allowed-local-root checks.
|
|
200
|
+
// Then write to a private temp file so ffmpeg/ffprobe never sees the original URL/path string.
|
|
201
|
+
const media = await loadWebMediaRaw(mediaUrl, maxBytesForKind("audio"));
|
|
202
|
+
const extFromName = media.fileName ? path.extname(media.fileName) : "";
|
|
203
|
+
const extFromMime = media.contentType ? extensionForMime(media.contentType) : "";
|
|
204
|
+
const ext = extFromName || extFromMime || ".bin";
|
|
205
|
+
const tempDir = resolvePreferredPoolbotTmpDir();
|
|
206
|
+
const filePath = path.join(tempDir, `voice-src-${crypto.randomUUID()}${ext}`);
|
|
207
|
+
await fs.writeFile(filePath, media.buffer, { mode: 0o600 });
|
|
208
|
+
return { filePath };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Send a voice message to Discord.
|
|
212
|
+
*
|
|
213
|
+
* Voice messages are a special Discord feature that displays audio with a waveform
|
|
214
|
+
* visualization. They require OGG/Opus format and cannot include text content.
|
|
215
|
+
*
|
|
216
|
+
* @param to - Recipient (user ID for DM or channel ID)
|
|
217
|
+
* @param audioPath - Path to local audio file (will be converted to OGG/Opus if needed)
|
|
218
|
+
* @param opts - Send options
|
|
219
|
+
*/
|
|
220
|
+
export async function sendVoiceMessageDiscord(to, audioPath, opts = {}) {
|
|
221
|
+
const { filePath: localInputPath } = await materializeVoiceMessageInput(audioPath);
|
|
222
|
+
let oggPath = null;
|
|
223
|
+
let oggCleanup = false;
|
|
224
|
+
let token;
|
|
225
|
+
let rest;
|
|
226
|
+
let channelId;
|
|
227
|
+
try {
|
|
228
|
+
const cfg = loadConfig();
|
|
229
|
+
const accountInfo = resolveDiscordAccount({
|
|
230
|
+
cfg,
|
|
231
|
+
accountId: opts.accountId,
|
|
232
|
+
});
|
|
233
|
+
const client = createDiscordClient(opts, cfg);
|
|
234
|
+
token = client.token;
|
|
235
|
+
rest = client.rest;
|
|
236
|
+
const request = client.request;
|
|
237
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
238
|
+
channelId = (await resolveChannelId(rest, recipient, request)).channelId;
|
|
239
|
+
// Convert to OGG/Opus if needed
|
|
240
|
+
const ogg = await ensureOggOpus(localInputPath);
|
|
241
|
+
oggPath = ogg.path;
|
|
242
|
+
oggCleanup = ogg.cleanup;
|
|
243
|
+
// Get voice message metadata (duration and waveform)
|
|
244
|
+
const metadata = await getVoiceMessageMetadata(oggPath);
|
|
245
|
+
// Read the audio file
|
|
246
|
+
const audioBuffer = await fs.readFile(oggPath);
|
|
247
|
+
// Send the voice message
|
|
248
|
+
const result = await sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, opts.replyTo, request, opts.silent);
|
|
249
|
+
recordChannelActivity({
|
|
250
|
+
channel: "discord",
|
|
251
|
+
accountId: accountInfo.accountId,
|
|
252
|
+
direction: "outbound",
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
messageId: result.id ? String(result.id) : "unknown",
|
|
256
|
+
channelId: String(result.channel_id ?? channelId),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
if (channelId && rest && token) {
|
|
261
|
+
throw await buildDiscordSendError(err, {
|
|
262
|
+
channelId,
|
|
263
|
+
rest,
|
|
264
|
+
token,
|
|
265
|
+
hasMedia: true,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
// Clean up temporary OGG file if we created one
|
|
272
|
+
if (oggCleanup && oggPath) {
|
|
273
|
+
try {
|
|
274
|
+
await fs.unlink(oggPath);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Ignore cleanup errors
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await fs.unlink(localInputPath);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Ignore cleanup errors
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|