Bidirectional Discord integration for Paperclip: notifications, slash commands, and community intelligence
TypeScript
50
183 commits
updated Sep 10, 2026
Bidirectional Discord integration for Paperclip. Push agent notifications to Discord, receive slash commands, approve requests with interactive buttons, gather community intelligence, run multi-agent sessions in threads, process media attachments, register custom commands, and deploy proactive agent suggestions.
Built on the Paperclip plugin SDK and the domain event bridge (PR #909).
Multiple Paperclip users asked for notifications on the same day the plugin system shipped (2026-03-14):
"is there a way to have codex/claude check paperclip to see when tasks are done without me prompting it?" - @Choose Liberty, Discord #dev
"basically to have it 'let me know when its done'" - @Choose Liberty, Discord #dev
"can claude code check paperclip to see when tasks are done" - @Nascozz, Discord #dev
@dotta (maintainer) responded: "we're also adding issue-changed hooks for plugins so when that lands someone could [make notifications]." @Ryze said "Really excited by the plugins. I had developed a custom plugin bridge that I will now deprecate and migrate over to the new supported plugin system."
This is that plugin.
discord:{username})approvalsChannelId - Dedicated channel for approval notificationserrorsChannelId - Dedicated channel for agent errorsbdPipelineChannelId - Dedicated channel for agent run lifecycleescalationChannelId - Dedicated channel for agent escalationsdefaultChannelId when per-type channels aren't configured/clip status - Show active agents and recent completions/clip approve <id> - Approve a pending approval/clip budget <agent> - Check an agent's remaining budget/clip issues [project] - List open issues with optional project filter/clip agents - Show all agents with status indicators/clip help - Display all available commands/clip connect [company] - Link a Discord channel to a Paperclip company/clip connect-channel <project> - Map a Discord channel to a project for notification routing/clip digest <on|off|status> [mode] - Configure daily digest (daily/bidaily/tridaily)/clip commands import <json> - Import a workflow command from JSON/clip commands list - List registered workflow commands/clip commands run <name> [args] - Execute a workflow command/clip commands delete <name> - Delete a workflow command/acp spawn agent:<name> task:<description> - Start a new agent session in a Discord thread/acp status session:<id> - Check ACP session status/acp cancel session:<id> - Cancel a running ACP session/acp close session:<id> - Close a completed ACP session and archive the threaddiscord_signals tooltrigger-backfill actionescalate_to_human tool - agents can call directly with reason, confidence score, conversation history, and suggested replyescalation-resolved events; timed-out escalations emit escalation-timed-out eventsmaxAgentsPerThread, default 5)@agentname in a thread message routes to that specific agenthandoff_to_agent tool; requires human approval via Approve/Reject buttonsdiscuss_with_agent tool
mediaChannelIdsenableMediaPipeline: trueenableInbound config toggle (default: true)daily (once), bidaily (twice), tridaily (three times per day)/clip digest on <mode> or the digestMode config setting/clip commands runfetch_issue, invoke_agent, http_request, send_message, create_issue, wait_approval, set_state{{arg0}}, {{args}}, {{prev.result}}, {{step_id.result}}wait_approval steps suspend execution and show Approve/Reject buttons/clip commands import, list with /clip commands list!command style commands via the register_custom_command tool!commandname <args> in any monitored channellistCommands()enableCustomCommands: trueregister_watch toolcheck-watches job runs on a configurable interval (default 15 min) and scans recent messages (20 min window){{author}}, {{content}}, and {{channel}} interpolationenableProactiveSuggestions: truenpm install paperclip-plugin-discord
Or register with your Paperclip instance directly:
curl -X POST http://127.0.0.1:3100/api/plugins/install \
-H "Content-Type: application/json" \
-d '{"packageName":"paperclip-plugin-discord"}'
Note —
paperclipaimaster, post #5429 (2026-05-09): The new Secrets Manager ships with a temporary kill switch on plugin secret-ref UUIDs while a company-scopedplugin_configfollow-up lands. If you're running paperclipai master, plugin activation will fail withPlugin secret references are disabled until company-scoped plugin config lands, andPOST /api/plugins/:id/configreturns HTTP 422 for configs containing secret-ref UUIDs (e.g.discordBotTokenRef). This is intentional fail-closed mitigation (PAP-2394 — see the upstream plan doc). Until the follow-up lands, pin to the last paperclipai release before #5429. This callout will be removed once secret-ref resolution is restored.
Before filing a bug, confirm which Paperclip host this plugin is actually talking to. Run paperclipai plugin target (#8575) — it prints the resolved API URL plus the server's status, version, deploymentMode, and deploymentExposure before anything is installed. A server version older than this plugin expects is the most common cause of activation failures and secret-resolution errors (e.g. discordBotTokenRef not resolving) that look like plugin bugs but aren't. If the URL or version is wrong, point Paperclip at the right host — or update the server — before opening an issue.
Create a Discord application at https://discord.com/developers/applications
Add a bot to the application and copy the bot token
Enable the MESSAGE CONTENT privileged intent (for intelligence scanning)
Invite the bot to your server with applications.commands and bot scopes
In Paperclip, create a company secret holding your bot token, by either:
discord-bot-token) and the bot token as the value, then click Create / Seal. The secret is created at the company level (not bound to that agent — despite the agent-context UI) and the returned UUID can be used from any plugin in the company.POST /api/companies/{companyId}/secrets with body {"name": "discord-bot-token", "value": "<your-bot-token>", "provider": "local_encrypted"}. The response contains the secret's UUID.Copy the resulting secret UUID — you'll paste it into discordBotTokenRef in the next step.
Configure the plugin with the secret UUID in discordBotTokenRef, your guild ID, and channel ID
| Setting | Required | Description |
|---|---|---|
discordBotTokenRef | Yes | Secret reference to your Discord bot token |
defaultChannelId | Yes | Default channel for notifications |
defaultGuildId | No | Server ID (required for slash commands and intelligence) |
approvalsChannelId | No | Dedicated channel for approvals |
errorsChannelId | No | Dedicated channel for agent errors |
bdPipelineChannelId | No | Dedicated channel for agent run lifecycle |
escalationChannelId | No | Dedicated channel for agent escalations |
notifyOnIssueCreated | No | Post when issues are created (default: true) |
notifyOnIssueDone | No | Post when issues complete (default: true) |
notifyOnApprovalCreated | No | Post when approvals are needed (default: true) |
notifyOnAgentError | No | Post when agents error (default: true) |
enableEscalations | No | Enable escalation features (default: true) |
escalationTimeoutMinutes | No | Timeout before marking escalation timed out (default: 30, min: 5, max: 1440) |
enableIntelligence | No | Enable community signal scanning (default: false) |
intelligenceChannelIds | No | Channel IDs to scan for signals |
backfillDays | No | Days of history to scan on first install (default: 90, max: 365) |
intelligenceRetentionDays | No | Days to retain intelligence signals (default: 30, max: 365) |
maxAgentsPerThread | No | Max concurrent agents per Discord thread (default: 5, max: 10) |
enableCommands | No | Enable slash command handling (default: true) |
enableInbound | No | Enable reply routing to Paperclip (default: true) |
topicRouting | No | Route notifications by project-to-channel mappings (default: false) |
digestMode | No | Digest frequency: off, daily, bidaily, tridaily (default: off) |
dailyDigestTime | No | UTC time for daily digest, HH:MM (default: 09:00) |
bidailySecondTime | No | Second digest time for bidaily mode (default: 17:00) |
tridailyTimes | No | Comma-separated HH:MM times for tridaily (default: 07:00,13:00,19:00) |
enableMediaPipeline | No | Detect and process media attachments (default: false) |
mediaChannelIds | No | Channel IDs to monitor for media (empty = all) |
enableCustomCommands | No | Allow agents to register !commands (default: false) |
enableProactiveSuggestions | No | Allow agents to register watch conditions (default: false) |
proactiveScanIntervalMinutes | No | How often to check watches (default: 15, min: 5, max: 60) |
paperclipBaseUrl | No | Base URL for Paperclip API calls (default: http://localhost:3100) |
Voice is opt-in and adds no weight to a normal install. Its dependencies are declared as optional peer dependencies, so npm install paperclip-plugin-discord does not pull them in and nothing about the text path changes.
To enable it, install the voice dependencies alongside the plugin:
npm install @discordjs/voice prism-media opusscript@0.0.8 libsodium-wrappers ws
opusscriptis pinned to0.0.8on purpose:prism-media@1.3.5declares it aspeerOptional opusscript@^0.0.8, so installing a neweropusscriptfails withERESOLVE.
prism-media ships no Opus codec of its own — it needs one of opusscript, @discordjs/opus, node-opus or ffmpeg-static to decode incoming audio. opusscript is pure JavaScript and needs no native build, so it is the recommended default; @discordjs/opus is faster but compiles a native module. Without an engine the plugin refuses to start voice and logs the install command, rather than joining the channel and silently transcribing nothing.
Then set these environment variables on the plugin worker:
| Variable | Required | Description |
|---|---|---|
DISCORD_VOICE_GUILD_ID | Yes | Guild (server) ID containing the voice channel |
DISCORD_VOICE_CHANNEL_ID | Yes | Voice channel the bot joins on startup |
DISCORD_VOICE_WEBHOOK_URL | Yes | Webhook URL of the text channel transcripts are posted to |
DEEPGRAM_API_KEY | Yes | Deepgram API key for streaming speech-to-text |
DISCORD_VOICE_USERNAME | No | Webhook display name for transcripts (default: Voice) |
DISCORD_VOICE_DEFAULT_ISSUE_ID | No | Fallback Paperclip issue for spoken utterances, used when the transcript channel has no target of its own. |
If any of the four required variables is missing, voice initializes nothing and the plugin runs exactly as before. Voice startup and shutdown are both wrapped: a voice failure is logged, reported through plugin health, and never crashes the plugin or interrupts text routing.
A voice utterance is not a reply to anything, so it has no message to inherit a destination from. Two destinations are tried, in order:
DISCORD_VOICE_DEFAULT_ISSUE_ID, when the channel has no pointer yet.Either way the utterance is commented on the resolved issue under the company
that owns this install, through the same ingress and the same API call a typed
reply uses, authored discord:voice:<userId>.
With neither available — no notification has been posted in that channel and no default configured — voice runs display-only: it still transcribes and still posts to the text channel, but sends nothing to Paperclip, and plugin health says so. The same is true for an utterance whose destination Paperclip rejects (the issue was deleted, say): the transcript still shows, health reports it, and nothing else in the plugin is affected.
Known phase-1 property: the channel pointer is last-notification-wins. It
follows the most recent thing the plugin announced in that channel, not what the
room is actually discussing, so a notification arriving mid-conversation moves
where the next utterance lands. Set DISCORD_VOICE_DEFAULT_ISSUE_ID and use a
channel the plugin does not post into if you want a fixed destination. A
genuinely conversation-scoped target needs state voice does not have yet.
A pointer is only ever followed for the company that currently owns the install. Ownership can move between installs, and a pointer left behind by a previous owner is ignored rather than allowed to file a transcript under the wrong company. The same rule covers slow work: an utterance still being transcribed when ownership moves is dropped rather than filed under whoever owns the install by the time it finishes.
To find the pointer, the plugin has to know which channel the webhook posts into, which a webhook URL does not carry — so it asks Discord for the webhook object once. At most one such lookup is in flight at a time and every concurrent utterance shares its result; a resolved channel is cached, and a failed lookup is retried after a five-minute cooldown. While it is unresolved, utterances use the default issue.
Plugin health tracks two independent things about voice: whether it can connect, and whether what it hears can be routed. Neither is evidence about the other, so reconnecting does not clear a routing problem and saving the configuration does not either — only an utterance actually reaching Paperclip does.
The webhook transcript is never the transport. The inbound router refuses bot authors and refuses anything that is not a reply to a message this plugin posted, and voice does not ask it to make an exception: it calls the ingress directly. Nothing this plugin writes to Discord can be read back in as input.
Notes:
GUILD_VOICE_STATES intent to the gateway IDENTIFY. Intents are fixed when the socket identifies, so turning voice on or off reconnects the gateway.@discordjs/voice.allowed_mentions: { parse: [] }. Speech recognition will happily render "at everyone" as @everyone, so no transcript can ever ping anyone — the text is preserved verbatim, but Discord resolves no mentions in it.Not in this phase: text-to-speech output, per-agent voices, cost guards.
| Tool | Phase | Description |
|---|---|---|
escalate_to_human | 1 | Escalate a conversation to a human via Discord |
discord_signals | - | Query community intelligence signals |
handoff_to_agent | 2 | Hand off a thread to another agent (requires human approval) |
discuss_with_agent | 2 | Start a multi-turn agent-to-agent discussion |
register_custom_command | 4 | Register a !command for Discord users |
register_watch | 5 | Register a watch condition for proactive suggestions |
@MatB57 - Escalation channel concept, "Chat OS" vision for turning chat plugins into bidirectional agent command centers, and the HITL suggested-reply flow.
@leeknowsai - Worker bootstrap and packaging fix (#1), rich notification embeds, approval button UX, and per-type channel routing (#4). Most of the notification formatting and interactive approval flow is their work.
Notification event handler patterns adapted from PR #398 by @StartupBros.
Brings the Discord plugin to full parity with the Telegram plugin across 14 feature gaps.
New slash commands: /clip issues, /clip agents, /clip help, /clip connect, /clip connect-channel, /clip digest, /clip commands import/list/run/delete
Reply routing: Replying to bot notifications now routes messages back to Paperclip as issue comments or escalation responses. Controlled by the enableInbound toggle.
Daily digest: Configurable summary digests (daily/bidaily/tridaily) with tasks completed, active agents, and blocked issues. Configure via /clip digest on <mode> or the digestMode config.
Workflow engine: Define multi-step workflows with 7 step types (fetch_issue, invoke_agent, http_request, send_message, create_issue, wait_approval, set_state). Supports template interpolation and approval-gated execution.
Config toggles: enableCommands, enableInbound, topicRouting, digest scheduling options.
The discordBotTokenRef field now requires a Paperclip secret reference (a UUID), not the raw token value. If you previously entered a raw bot token in the field, follow these steps to migrate:
The plugin will fail to activate if a raw token (non-UUID) is entered in the field.
pnpm install
pnpm typecheck
pnpm test
pnpm build
323 tests covering formatters, commands, intelligence, session registry, media pipeline, custom commands, proactive suggestions, retry logic, workflow engine, and Telegram-parity features.
Issues and PRs welcome at github.com/mvanhorn/paperclip-plugin-discord.
Auto-publishes to npm on push to main via OIDC trusted publishing.
MIT
TypeScript
99.7%
Bidirectional Discord integration for Paperclip: notifications, slash commands, and community intelligence
TypeScript
50
183 commits
updated Sep 10, 2026
Bidirectional Discord integration for Paperclip. Push agent notifications to Discord, receive slash commands, approve requests with interactive buttons, gather community intelligence, run multi-agent sessions in threads, process media attachments, register custom commands, and deploy proactive agent suggestions.
Built on the Paperclip plugin SDK and the domain event bridge (PR #909).
Multiple Paperclip users asked for notifications on the same day the plugin system shipped (2026-03-14):
"is there a way to have codex/claude check paperclip to see when tasks are done without me prompting it?" - @Choose Liberty, Discord #dev
"basically to have it 'let me know when its done'" - @Choose Liberty, Discord #dev
"can claude code check paperclip to see when tasks are done" - @Nascozz, Discord #dev
@dotta (maintainer) responded: "we're also adding issue-changed hooks for plugins so when that lands someone could [make notifications]." @Ryze said "Really excited by the plugins. I had developed a custom plugin bridge that I will now deprecate and migrate over to the new supported plugin system."
This is that plugin.
discord:{username})approvalsChannelId - Dedicated channel for approval notificationserrorsChannelId - Dedicated channel for agent errorsbdPipelineChannelId - Dedicated channel for agent run lifecycleescalationChannelId - Dedicated channel for agent escalationsdefaultChannelId when per-type channels aren't configured/clip status - Show active agents and recent completions/clip approve <id> - Approve a pending approval/clip budget <agent> - Check an agent's remaining budget/clip issues [project] - List open issues with optional project filter/clip agents - Show all agents with status indicators/clip help - Display all available commands/clip connect [company] - Link a Discord channel to a Paperclip company/clip connect-channel <project> - Map a Discord channel to a project for notification routing/clip digest <on|off|status> [mode] - Configure daily digest (daily/bidaily/tridaily)/clip commands import <json> - Import a workflow command from JSON/clip commands list - List registered workflow commands/clip commands run <name> [args] - Execute a workflow command/clip commands delete <name> - Delete a workflow command/acp spawn agent:<name> task:<description> - Start a new agent session in a Discord thread/acp status session:<id> - Check ACP session status/acp cancel session:<id> - Cancel a running ACP session/acp close session:<id> - Close a completed ACP session and archive the threaddiscord_signals tooltrigger-backfill actionescalate_to_human tool - agents can call directly with reason, confidence score, conversation history, and suggested replyescalation-resolved events; timed-out escalations emit escalation-timed-out eventsmaxAgentsPerThread, default 5)@agentname in a thread message routes to that specific agenthandoff_to_agent tool; requires human approval via Approve/Reject buttonsdiscuss_with_agent tool
mediaChannelIdsenableMediaPipeline: trueenableInbound config toggle (default: true)daily (once), bidaily (twice), tridaily (three times per day)/clip digest on <mode> or the digestMode config setting/clip commands runfetch_issue, invoke_agent, http_request, send_message, create_issue, wait_approval, set_state{{arg0}}, {{args}}, {{prev.result}}, {{step_id.result}}wait_approval steps suspend execution and show Approve/Reject buttons/clip commands import, list with /clip commands list!command style commands via the register_custom_command tool!commandname <args> in any monitored channellistCommands()enableCustomCommands: trueregister_watch toolcheck-watches job runs on a configurable interval (default 15 min) and scans recent messages (20 min window){{author}}, {{content}}, and {{channel}} interpolationenableProactiveSuggestions: truenpm install paperclip-plugin-discord
Or register with your Paperclip instance directly:
curl -X POST http://127.0.0.1:3100/api/plugins/install \
-H "Content-Type: application/json" \
-d '{"packageName":"paperclip-plugin-discord"}'
Note —
paperclipaimaster, post #5429 (2026-05-09): The new Secrets Manager ships with a temporary kill switch on plugin secret-ref UUIDs while a company-scopedplugin_configfollow-up lands. If you're running paperclipai master, plugin activation will fail withPlugin secret references are disabled until company-scoped plugin config lands, andPOST /api/plugins/:id/configreturns HTTP 422 for configs containing secret-ref UUIDs (e.g.discordBotTokenRef). This is intentional fail-closed mitigation (PAP-2394 — see the upstream plan doc). Until the follow-up lands, pin to the last paperclipai release before #5429. This callout will be removed once secret-ref resolution is restored.
Before filing a bug, confirm which Paperclip host this plugin is actually talking to. Run paperclipai plugin target (#8575) — it prints the resolved API URL plus the server's status, version, deploymentMode, and deploymentExposure before anything is installed. A server version older than this plugin expects is the most common cause of activation failures and secret-resolution errors (e.g. discordBotTokenRef not resolving) that look like plugin bugs but aren't. If the URL or version is wrong, point Paperclip at the right host — or update the server — before opening an issue.
Create a Discord application at https://discord.com/developers/applications
Add a bot to the application and copy the bot token
Enable the MESSAGE CONTENT privileged intent (for intelligence scanning)
Invite the bot to your server with applications.commands and bot scopes
In Paperclip, create a company secret holding your bot token, by either:
discord-bot-token) and the bot token as the value, then click Create / Seal. The secret is created at the company level (not bound to that agent — despite the agent-context UI) and the returned UUID can be used from any plugin in the company.POST /api/companies/{companyId}/secrets with body {"name": "discord-bot-token", "value": "<your-bot-token>", "provider": "local_encrypted"}. The response contains the secret's UUID.Copy the resulting secret UUID — you'll paste it into discordBotTokenRef in the next step.
Configure the plugin with the secret UUID in discordBotTokenRef, your guild ID, and channel ID
| Setting | Required | Description |
|---|---|---|
discordBotTokenRef | Yes | Secret reference to your Discord bot token |
defaultChannelId | Yes | Default channel for notifications |
defaultGuildId | No | Server ID (required for slash commands and intelligence) |
approvalsChannelId | No | Dedicated channel for approvals |
errorsChannelId | No | Dedicated channel for agent errors |
bdPipelineChannelId | No | Dedicated channel for agent run lifecycle |
escalationChannelId | No | Dedicated channel for agent escalations |
notifyOnIssueCreated | No | Post when issues are created (default: true) |
notifyOnIssueDone | No | Post when issues complete (default: true) |
notifyOnApprovalCreated | No | Post when approvals are needed (default: true) |
notifyOnAgentError | No | Post when agents error (default: true) |
enableEscalations | No | Enable escalation features (default: true) |
escalationTimeoutMinutes | No | Timeout before marking escalation timed out (default: 30, min: 5, max: 1440) |
enableIntelligence | No | Enable community signal scanning (default: false) |
intelligenceChannelIds | No | Channel IDs to scan for signals |
backfillDays | No | Days of history to scan on first install (default: 90, max: 365) |
intelligenceRetentionDays | No | Days to retain intelligence signals (default: 30, max: 365) |
maxAgentsPerThread | No | Max concurrent agents per Discord thread (default: 5, max: 10) |
enableCommands | No | Enable slash command handling (default: true) |
enableInbound | No | Enable reply routing to Paperclip (default: true) |
topicRouting | No | Route notifications by project-to-channel mappings (default: false) |
digestMode | No | Digest frequency: off, daily, bidaily, tridaily (default: off) |
dailyDigestTime | No | UTC time for daily digest, HH:MM (default: 09:00) |
bidailySecondTime | No | Second digest time for bidaily mode (default: 17:00) |
tridailyTimes | No | Comma-separated HH:MM times for tridaily (default: 07:00,13:00,19:00) |
enableMediaPipeline | No | Detect and process media attachments (default: false) |
mediaChannelIds | No | Channel IDs to monitor for media (empty = all) |
enableCustomCommands | No | Allow agents to register !commands (default: false) |
enableProactiveSuggestions | No | Allow agents to register watch conditions (default: false) |
proactiveScanIntervalMinutes | No | How often to check watches (default: 15, min: 5, max: 60) |
paperclipBaseUrl | No | Base URL for Paperclip API calls (default: http://localhost:3100) |
Voice is opt-in and adds no weight to a normal install. Its dependencies are declared as optional peer dependencies, so npm install paperclip-plugin-discord does not pull them in and nothing about the text path changes.
To enable it, install the voice dependencies alongside the plugin:
npm install @discordjs/voice prism-media opusscript@0.0.8 libsodium-wrappers ws
opusscriptis pinned to0.0.8on purpose:prism-media@1.3.5declares it aspeerOptional opusscript@^0.0.8, so installing a neweropusscriptfails withERESOLVE.
prism-media ships no Opus codec of its own — it needs one of opusscript, @discordjs/opus, node-opus or ffmpeg-static to decode incoming audio. opusscript is pure JavaScript and needs no native build, so it is the recommended default; @discordjs/opus is faster but compiles a native module. Without an engine the plugin refuses to start voice and logs the install command, rather than joining the channel and silently transcribing nothing.
Then set these environment variables on the plugin worker:
| Variable | Required | Description |
|---|---|---|
DISCORD_VOICE_GUILD_ID | Yes | Guild (server) ID containing the voice channel |
DISCORD_VOICE_CHANNEL_ID | Yes | Voice channel the bot joins on startup |
DISCORD_VOICE_WEBHOOK_URL | Yes | Webhook URL of the text channel transcripts are posted to |
DEEPGRAM_API_KEY | Yes | Deepgram API key for streaming speech-to-text |
DISCORD_VOICE_USERNAME | No | Webhook display name for transcripts (default: Voice) |
DISCORD_VOICE_DEFAULT_ISSUE_ID | No | Fallback Paperclip issue for spoken utterances, used when the transcript channel has no target of its own. |
If any of the four required variables is missing, voice initializes nothing and the plugin runs exactly as before. Voice startup and shutdown are both wrapped: a voice failure is logged, reported through plugin health, and never crashes the plugin or interrupts text routing.
A voice utterance is not a reply to anything, so it has no message to inherit a destination from. Two destinations are tried, in order:
DISCORD_VOICE_DEFAULT_ISSUE_ID, when the channel has no pointer yet.Either way the utterance is commented on the resolved issue under the company
that owns this install, through the same ingress and the same API call a typed
reply uses, authored discord:voice:<userId>.
With neither available — no notification has been posted in that channel and no default configured — voice runs display-only: it still transcribes and still posts to the text channel, but sends nothing to Paperclip, and plugin health says so. The same is true for an utterance whose destination Paperclip rejects (the issue was deleted, say): the transcript still shows, health reports it, and nothing else in the plugin is affected.
Known phase-1 property: the channel pointer is last-notification-wins. It
follows the most recent thing the plugin announced in that channel, not what the
room is actually discussing, so a notification arriving mid-conversation moves
where the next utterance lands. Set DISCORD_VOICE_DEFAULT_ISSUE_ID and use a
channel the plugin does not post into if you want a fixed destination. A
genuinely conversation-scoped target needs state voice does not have yet.
A pointer is only ever followed for the company that currently owns the install. Ownership can move between installs, and a pointer left behind by a previous owner is ignored rather than allowed to file a transcript under the wrong company. The same rule covers slow work: an utterance still being transcribed when ownership moves is dropped rather than filed under whoever owns the install by the time it finishes.
To find the pointer, the plugin has to know which channel the webhook posts into, which a webhook URL does not carry — so it asks Discord for the webhook object once. At most one such lookup is in flight at a time and every concurrent utterance shares its result; a resolved channel is cached, and a failed lookup is retried after a five-minute cooldown. While it is unresolved, utterances use the default issue.
Plugin health tracks two independent things about voice: whether it can connect, and whether what it hears can be routed. Neither is evidence about the other, so reconnecting does not clear a routing problem and saving the configuration does not either — only an utterance actually reaching Paperclip does.
The webhook transcript is never the transport. The inbound router refuses bot authors and refuses anything that is not a reply to a message this plugin posted, and voice does not ask it to make an exception: it calls the ingress directly. Nothing this plugin writes to Discord can be read back in as input.
Notes:
GUILD_VOICE_STATES intent to the gateway IDENTIFY. Intents are fixed when the socket identifies, so turning voice on or off reconnects the gateway.@discordjs/voice.allowed_mentions: { parse: [] }. Speech recognition will happily render "at everyone" as @everyone, so no transcript can ever ping anyone — the text is preserved verbatim, but Discord resolves no mentions in it.Not in this phase: text-to-speech output, per-agent voices, cost guards.
| Tool | Phase | Description |
|---|---|---|
escalate_to_human | 1 | Escalate a conversation to a human via Discord |
discord_signals | - | Query community intelligence signals |
handoff_to_agent | 2 | Hand off a thread to another agent (requires human approval) |
discuss_with_agent | 2 | Start a multi-turn agent-to-agent discussion |
register_custom_command | 4 | Register a !command for Discord users |
register_watch | 5 | Register a watch condition for proactive suggestions |
@MatB57 - Escalation channel concept, "Chat OS" vision for turning chat plugins into bidirectional agent command centers, and the HITL suggested-reply flow.
@leeknowsai - Worker bootstrap and packaging fix (#1), rich notification embeds, approval button UX, and per-type channel routing (#4). Most of the notification formatting and interactive approval flow is their work.
Notification event handler patterns adapted from PR #398 by @StartupBros.
Brings the Discord plugin to full parity with the Telegram plugin across 14 feature gaps.
New slash commands: /clip issues, /clip agents, /clip help, /clip connect, /clip connect-channel, /clip digest, /clip commands import/list/run/delete
Reply routing: Replying to bot notifications now routes messages back to Paperclip as issue comments or escalation responses. Controlled by the enableInbound toggle.
Daily digest: Configurable summary digests (daily/bidaily/tridaily) with tasks completed, active agents, and blocked issues. Configure via /clip digest on <mode> or the digestMode config.
Workflow engine: Define multi-step workflows with 7 step types (fetch_issue, invoke_agent, http_request, send_message, create_issue, wait_approval, set_state). Supports template interpolation and approval-gated execution.
Config toggles: enableCommands, enableInbound, topicRouting, digest scheduling options.
The discordBotTokenRef field now requires a Paperclip secret reference (a UUID), not the raw token value. If you previously entered a raw bot token in the field, follow these steps to migrate:
The plugin will fail to activate if a raw token (non-UUID) is entered in the field.
pnpm install
pnpm typecheck
pnpm test
pnpm build
323 tests covering formatters, commands, intelligence, session registry, media pipeline, custom commands, proactive suggestions, retry logic, workflow engine, and Telegram-parity features.
Issues and PRs welcome at github.com/mvanhorn/paperclip-plugin-discord.
Auto-publishes to npm on push to main via OIDC trusted publishing.
MIT
TypeScript
99.7%