packages/discord-presence-core/README.md
@clankie/discord-presence-core
Transport-neutral Discord participation. Everything here is blind to whether Clankie is wearing the official bot or the personal-lab user session, which is what lets both bodies be one character (ADR 0024, ADR 0048).
| Module | Responsibility |
|---|---|
presence-session | Gateway/voice phase lifecycle, typed phase events, act-tool revoke fence |
presence-action-advertiser | Retains the live catalogue and forwards phase as an execution fence |
discord-rest | Shared bounded REST writes for both Discord transports |
captain-action-control | Authenticated local control requests from captain tools |
text-ingress | Normalises gateway messages into bounded, allowlist-gated captain turns, images included (ADR 0081) |
room-text | Shared admission for typed room experience across text, voice, and game threads (ADR 0124) |
voice-address | Phonetic name-mention: opens a session; the offered turn decides whether to speak (ADR 0119) |
voice-floor | Dormant ↔ engaged floor: wake, offer (silence-ok), listen, decay, volition (ADR 0119) |
realtime-session | Injectable OpenAI/xAI realtime boundaries: transcription, conversation, and ask_clankie round trips |
elevenlabs-tts | Injectable ElevenLabs multi-context streaming-TTS boundary (ADR 0070) |
external-voice | Pairs a text-modality realtime session with a TTS mouth behind the one conversation port (ADR 0070) |
voice-session | Vox-backed attributed speech/text input, shared group floor, deliberate barge-in and playback |
voice-composition | Shared voice dependency assembly for bot and user-session bodies |
voice-control | Local join/leave control request handling |
voice-music | Shared bounded queue and transport controls |
voice-ingress | Routes one ask_clankie handoff to the continuing discord_voice captain lane |
voice-consent | Ephemeral consent under explicit or owner-selected presence policy; opt-out always wins |
voice-audio | Shared provider/local-voice PCM helpers and content-free RMS measurement |
receipt-store | Append-only, content-free receipts for both planes |
Voice receipts use the discord.voice.* vocabulary — joined, consent,
utterance, text_input, floor, response, volition, overlap, interrupted,
failed, left — and every field is a content-free scalar: ids, counts,
durations, and typed outcomes, never transcript, prompt, audio, or PCM.
Rules
- Never import
discord.js. A bot-shaped client is a transport detail and belongs in the app that owns that transport. The bot bridge usesdiscord.js; the user-session bridge uses a bounded raw gateway plusfetch. - Derive the lane address from
discordPresenceLaneAddress. It is keyed by where the conversation happens (discord:<guildId|dm>:<channelId>), never by which transport observed it. A transport-local identifier would fork one conversation into two captain lanes and split the character in half. transportKindis configuration, not inference. Both ingress paths take it from their host process; neither guesses.- Visuals are selected here, never in a bridge. Both transports map
their raw attachment and
gifvembed shapes and callselectInboundImageAttachments, so one rule decides what he can be shown. A policy that admitted an image on one body and not the other would be two characters, not one (ADR 0081). - This package never fetches attachment bytes. It carries references; the clankie service resolves them at the last hop before the model.
- The active voice room owns its attached text chat. Text-only messages in
that exact guild/channel enter
VoiceFloor, where the realtime room persona speaks, hands off, or stays silent. The bridge does not also launch a text captain turn (ADR 0124). - Voice identity stays attached to a gateway stream. Speakers use separate transcription inputs. Only attributed JSON transcript items converge into the shared engaged conversation; overlapping raw audio is never interleaved and guessed after the fact. An open session hears consented speech; the floor decides who gets a spoken turn (ADR 0119).
- Speaker listeners are bounded. An inactive per-speaker transcription session closes after two minutes and reopens on demand. At 25 retained listeners, the least recently active idle listener is evicted before another opens; active captures and pending transcript correlation are never evicted.
- Vox is the sole voice media owner. Both media-enabled Discord bodies use
native Vox capture, TTS playback, DAVE readiness, and audible music; a
text-only bot does not spawn it. This package owns policy and correlation
only; ordinary leave clears Vox's primary role without closing the process or
touching its stream-watch/publish roles. TTS is audible only after Vox reports
started;bufferedmeans queued and does not occupy the floor, whiledrainedmeans finished PCM and trailing frames crossed the sender. Fresh app-level readiness evidence recordsmediaOwner: vox; the joined receipt separately proves positive role-scoped DAVE. A leave qualifies only after the account gateway confirms detachment. See ADR 0128.
Consumers
apps/discord-bridge— official bot: slash commands, voice, and the activity plane.apps/discord-user-session— personal-lab user session, gated by ADR 0048.
