Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

9.9 KiB

@elizaos/plugin-discord-local

Local Discord desktop integration for Eliza agents via Discord RPC and macOS UI automation.

Purpose / role

Connects an Eliza agent to the Discord desktop app running on the same machine. It listens for incoming messages and notifications over the Discord local RPC socket, ingests them into the agent's memory, and sends replies by driving the Discord UI through AppleScript/osascript. It is opt-in: the plugin does nothing until DISCORD_LOCAL_CLIENT_ID and DISCORD_LOCAL_CLIENT_SECRET are present in the agent's settings.

macOS only. The send path calls osascript and /usr/bin/open; the plugin will refuse non-darwin platforms at runtime.

Plugin surface

All surface is declared in the discordLocalPlugin export at the bottom of src/index.ts.

Services

Name Class What it does
discord-local DiscordLocalService Manages the IPC socket connection to the local Discord app, handles OAuth, subscribes to RPC events (MESSAGE_CREATE, NOTIFICATION_CREATE), ingests messages into runtime memory, and drives osascript to send replies.

Routes (all rawPath: true)

Method Path Handler
GET /api/setup/discord/status handleDiscordStatus — returns connection/auth state
POST /api/setup/discord/start handleDiscordStart — triggers OAuth authorization flow
POST /api/setup/discord/cancel handleDiscordCancel — clears session and disconnects
GET /api/discord/guilds handleDiscordGuilds — lists guilds via RPC
GET /api/discord/channels handleDiscordChannels — lists channels for a guild (?guildId=)
POST /api/discord/subscriptions handleDiscordSubscriptions — updates MESSAGE_CREATE subscriptions

Actions / Providers / Evaluators

None. This plugin registers only a service and routes.

Send handler

DiscordLocalService.registerSendHandlers registers the source discord-local (and discord when no other discord handler is registered) on the runtime. This lets the orchestration layer route outbound messages to Discord without importing this package directly.

Layout

plugins/plugin-discord-local/
  src/
    index.ts          Everything — service, routes, plugin export, all helpers
  build.ts            Bun build script
  package.json
  tsconfig.json
  tsconfig.build.json
  biome.json

The entire plugin is a single file (src/index.ts). There are no sub-modules.

Key exports from src/index.ts:

  • defaultdiscordLocalPlugin (the Plugin object)
  • DiscordLocalService — the service class
  • DISCORD_LOCAL_PLUGIN_NAME"@elizaos/plugin-discord-local"
  • DISCORD_LOCAL_SERVICE_NAME"discord-local"

Commands

bun run --cwd plugins/plugin-discord-local build       # compile to dist/
bun run --cwd plugins/plugin-discord-local dev         # watch build (bun --hot)
bun run --cwd plugins/plugin-discord-local typecheck   # tsgo --noEmit
bun run --cwd plugins/plugin-discord-local lint        # biome check --write
bun run --cwd plugins/plugin-discord-local lint:check  # biome check (no write)
bun run --cwd plugins/plugin-discord-local format      # biome format --write
bun run --cwd plugins/plugin-discord-local format:check
bun run --cwd plugins/plugin-discord-local clean       # rm dist .turbo

No test script is defined in this package's package.json.

Config / env vars

All settings are read via runtime.getSetting(key).

Setting Required Default Description
DISCORD_LOCAL_CLIENT_ID Yes Discord application client ID (from Discord Developer Portal). Plugin is disabled if absent.
DISCORD_LOCAL_CLIENT_SECRET Yes Discord application client secret. Plugin is disabled if absent.
DISCORD_LOCAL_ENABLED No true Set to "false" to explicitly disable after credentials are set.
DISCORD_LOCAL_SCOPES No rpc,identify,rpc.notifications.read Comma-separated OAuth scopes. Must include rpc for IPC to work.
DISCORD_LOCAL_MESSAGE_CHANNEL_IDS No "" Comma-separated Discord channel IDs to subscribe to MESSAGE_CREATE.
DISCORD_LOCAL_SEND_DELAY_MS No 900 Milliseconds to wait after focusing Discord before typing. Minimum 100.

Session tokens are persisted to <stateDir>/discord-local/session.json. resolveStateDir() from @elizaos/core determines stateDir.

The IPC socket is located by scanning DISCORD_IPC_DIR, XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP, /tmp, /private/tmp, and ~/Library/Application Support/discord (recursive, macOS fallback).

How to extend

Add a route

  1. Write a handler function matching (req: RouteRequest, res: RouteResponse, runtime: IAgentRuntime) => Promise<void>.
  2. Append a Route object to discordLocalSetupRoutes at the bottom of src/index.ts.
  3. Access the service inside the handler with resolveDiscordLocalService(runtime).

Add an action

  1. Define the action object (Action from @elizaos/core) anywhere in src/index.ts or a new file under src/.
  2. Add it to discordLocalPlugin.actions = [...] before the export.
  3. In the action handler, retrieve the service via runtime.getService(DISCORD_LOCAL_SERVICE_NAME).

Add a provider

Same pattern: define a Provider, add to discordLocalPlugin.providers = [...].

Conventions / gotchas

  • macOS only. requireConfig() throws on non-darwin platforms. Do not attempt to run the send path on Linux/Windows.
  • Authorization is interactive. The OAuth flow (handleDiscordStartservice.authorize()) pops a Discord permission dialog in the desktop app. It cannot be automated headlessly.
  • Session persistence. OAuth tokens survive restarts via <stateDir>/discord-local/session.json. Token refresh is automatic when refreshToken and expiresAt are present.
  • Reconnect. On socket close, scheduleReconnect retries after 3 seconds if a session exists.
  • IPC timeout. Each RPC command times out after 20 seconds.
  • No test suite. There are no unit or integration tests in this package.
  • Single-file. All logic lives in src/index.ts. Keep additions in the same file unless they grow large enough to justify splitting; if you split, update the exports list above.
  • connector-setup integration. When the runtime exposes a connector-setup service, handleDiscordSubscriptions writes the chosen channel IDs back into the connector config and registers Discord as the escalation channel. This is an optional runtime capability; the plugin works without it.
  • For global architecture rules, naming, and logger conventions see the repo root AGENTS.md.

NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests

The binding, repo-wide standard is AGENTS.md. Read it. Nothing in this package is done until it is proven done — a reviewer must confirm it works without reading the code, from the artifacts you attach. This applies to every feature, fix, refactor, and chore here. "Tests pass" is not proof; "CI is green" is not proof.

  • Record AND read model trajectories. Capture the actual inputs and outputs of the model from a live LLM — not the deterministic proxy, not a mock: the prompt, the providers/context, the raw model output, every tool/action call, and the result. Then open the trajectory and review it by hand. A captured-but-unread trajectory is not evidence (packages/scenario-runner/bin/eliza-scenarios run <scenario> --report <out>).
  • Real, full-featured E2E — no larp. Every feature ships detailed end-to-end tests that drive the real path end to end. Not the happy "front door" only: cover error paths, edge/empty/invalid input, concurrency, roles/permissions, and adversarial input. A test that asserts against a mock/stub/fixture standing in for the thing under test does not count. If the real model/device/chain/connector/account is hard to reach, make it reachable — that is the work, not an excuse to mock. If the existing tests here are shallow or mocked, fixing them is part of your change.
  • Screenshots + logs at every phase, plus a complete walkthrough video/run-through of the entire feature or view, start to finish (bun run test:e2e:record).
  • Manually review every artifact the change touches — never just the green check: client logs (console + network), server logs ([ClassName] …), the model trajectories in and out, before/after full-page screenshots, and the domain artifacts listed below for this package.
  • No residuals. No shortcuts. The goal is not "done" — it is everything done. Clear every blocker by the hard path: build the real architecture, stand up the real model/device/service, actually test it. Never leave a TODO, a stub, a stepping-stone, or a "follow-up." When unsure, research thoroughly, weigh the options, and ship the best, highest-effort, production-ready version. Keep going until every possibility is exhausted.

Artifacts → attached inline in the PR (MP4 video, JPG screenshots, logs in <details>); attach each evidence type or explicitly mark it N/A with a reason — never leave it blank. If develop moved and changed behavior, re-capture evidence; stale proof is worse than none.

Capture & manually review for this package — platform connector:

  • A real (or sandbox-account) round-trip on the platform: inbound message → agent → outbound reply, captured as logs and a screenshot/recording of the actual conversation.
  • The raw inbound event/webhook payload and the outbound API request/response, with IDs mapped correctly (stringToUuid / createUniqueUuid).
  • Attachments, threads/replies, edits, multi-account, and rate-limit/error paths — not just a single text ping.
  • The agent trajectory for the turn the connector drove.