discord-fleet
Fleet-customized fork of anthropics/claude-plugins-official's external_plugins/discord (forked from version 0.0.4, Apache-2.0).
This is a Claude Code plugin: a Discord MCP bridge with built-in access control. Forked for the Fleet of agents — Vox, Kit, Iris, Scout, Oracle, Pearl — to add lane-emoji conventions and a telemetry-relay design path.
Upstream's README is preserved as UPSTREAM_README.md. For the original setup walkthrough, access-control reference, or tool documentation, read that file. This README only documents what this fork changes.
What this fork adds
1. Lane-emoji react conventions (stub, default-off)
The fleet's overnight build pattern uses leading emojis to disambiguate parallel lanes in a single Discord channel:
| Emoji | Lane |
|---|
| 🦊 | Kit (sibling agent) |
| 🪞 | Iris (sibling agent, the Auditor) |
| 👥 | Team build / orchestration |
| 🎲 | Oracle (prediction-market agent) |
| 🛒 | Scout (paper-trading agent) |
| 🧩 | Plugin / infrastructure work |
| 📒 | Linear / ticket work |
| 🚢 | Push / ship / deploy |
| 📜 | Honcho / memory ingestion |
Configure lane-emojis in ~/.claude/channels/discord/access.json:
{
// ...existing fields...
"ackReaction": "🦊",
"laneEmojis": {
"🦊": "kit",
"🪞": "iris",
"👥": "team",
"🧩": "plugin-fork",
"📒": "linear",
"🚢": "push"
}
}
Behavior:
- When a configured lane-emoji is the first character of an inbound message, the bot reacts with that emoji instead of the global
ackReaction. This gives a glanceable view of which lane each message belongs to in chat history.
- When
laneEmojis is absent or empty, behavior is identical to upstream — the global ackReaction (e.g. 🦊) fires on every message. No code path changes for non-fork users.
- The longest matching prefix wins, so
🦊 and a hypothetical 🦊🦊🦊 can coexist without ambiguity.
Implementation: ~25 LOC patch in server.ts, gated on laneEmojis being non-empty so users who never opt in pay zero runtime cost.
2. Telemetry relay (design spec only — see TELEMETRY.md)
Claude Code's --output-format json-stream emits per-tool-call telemetry: duration_ms, tool name, status. The lane-progress checklist message (the 🦊 Running: block) could become a live timing tape if the plugin consumed that stream and edit_message'd the lane post.
Status: design only. See TELEMETRY.md for the proposed protocol, integration point, and rate-limit considerations. Not implemented in 0.1.0.
3. Inbound context enrichment (additive, mostly default-on)
The upstream listener hands Claude Code only what arrives in a single messageCreate event. The orchestrator then re-derives wall-clock, recent history, and reply-threading every turn. This fork has the listener do that work once per inbound, structurally. Design doc: learnings/proposals/2026-05-14-discord-fleet-listener-enrichment.md in the vox-agent repo.
New <channel> tag attributes (all additive — no existing attribute renamed or removed; a consumer that ignores unknown attributes is unaffected):
| Attribute | Always emitted? | Purpose |
|---|
now | yes | Server wall-clock (ISO 8601). Obey wall-clock-honesty without a date shellout. |
channel_name | yes | Channel name (or DM). Grounds the orchestrator on where — upstream only sent an opaque ID. |
is_dm | yes | "true"/"false". |
delta_since_last_inbound_ms | only if a prior inbound exists this channel | Ms since the previous inbound here. Large gap = cold revival, small = active-thread continuation — no orchestrator math. |
is_reply_to | only if the message is a reply | The message_id it threads under. |
is_reply_to_bot | only if the message is a reply | "true" when the user is replying to our prior message vs a new topic. |
recent_history | only if inboundHistoryLimit > 0 | Pre-fetched transcript (same format fetch_messages produces). Satisfies fetch-before-post without an extra round-trip. |
recent_history is the one config-gated field. Default is off (inboundHistoryLimit absent ⇒ 0) for exact upstream parity — zero extra Discord API calls, zero payload growth for non-fleet users. To enable for a fleet channel, add one key to ~/.claude/channels/discord/access.json:
{
// ...existing fields...
"inboundHistoryLimit": 10
}
Re-read on every inbound (no restart needed). Discord caps the fetch at 100. A history-fetch failure is non-fatal — the inbound notification is still delivered without recent_history.
The everything-else fields (now, channel_name, is_dm, deltas, reply-threading) are trivially derived from data the listener already has and ship on by default — they cost ~5 LOC and a Map lookup, no extra API calls.