discord

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 13 Imported by: 0

README

herrscher-discord-gateway

The Discord channel edge. Adapts the dctl Discord REST client to the Herrscher gateway ports and self-registers from init(), so a host enables Discord with a blank import and a rebuild. It is a pure plugin — no main, no composition root — and it is a smart gateway: it implements EventSink and renders the live turn stream itself, so the core never learns anything Discord-specific.

Role · Category · Ports · Config · Status · Repo

Aspect Value
Role Receives Discord mentions and slash interactions, posts replies, and renders turn progress in-channel
Category Gateway (inbound edge)
Ports implemented Gateway, EventSink, RoutedEventSink, SessionControlReceiver, ChannelReader, MenuRouter, ChannelAdmin, Prober
Config & env token / DISCORD_BOT_TOKEN (required), owner / DISCORD_USER_ID (required, the user the bot obeys), verbosity / DISCORD_VERBOSITY (quiet default / actions / full — see below), context_messages / DISCORD_CONTEXT_MESSAGES (default 30), playbook / DISCORD_PLAYBOOK (default pr-job), DCTL_STATE_DIR (default: ~/.config/dctl)
Status live
Repo herrscher-discord-gateway

Install

herrscher plugin add github.com/Herrscherd/herrscher-discord-gateway

Rendering happens here, not in the core

The Gateway receives the raw turn-event stream and draws Discord itself: one live-updating progress message per turn (capped at 15 lines, one edit per 1.5 s), a ⏳ ACK reaction on the triggering message, and a final reply chunked at Discord's 2000-rune limit and collapsed to a ✅ summary. A mid-turn backend reset discards the partial render and keeps going; an abandoned turn clears the ACK silently.

DISCORD_VERBOSITY sets how much of that reaches the channel. quiet (default) posts tool names only, so no absolute path, shell command or search pattern from your machine is ever shown — the level to leave alone when the bot is pinged in a channel other people read. actions adds each tool's detail; full also adds the assistant's thinking. Repeated lines collapse to ×N, and the ✅ summary (tool names, count, duration, cost) is the same at every level.

Owner-bound, not channel-bound

The gateway identifies with GUILD_MESSAGES and DIRECT_MESSAGES — both non-privileged — and acts on a message only when the configured owner @mentions the bot or replies to it. Everyone else's messages are never triggers, but the last context_messages messages of the channel are read over REST and carried into the turn, so the agent sees the whole conversation. The first ping in an unknown channel is acked with ⏳ and asks which repo to work on with a select menu — the ack lands right away, since that ping is answered by a question rather than by a turn; the answer creates a session that adopts that channel and is remembered in discord-router.json (mode 0600, under DCTL_STATE_DIR). Rendering is per conversation: each channel gets its own progress message, ⏳ ack and reply.

The slash surface

/set, /session, /service and /allow are registered globally — on the application, not on a server — so the bot carries its commands into every server it is invited to and no server has to be named in config. Discord takes up to an hour to propagate a change to a global command. Commands the core owns become neutral argv through SessionControl.Dispatch; /allow and /session allow mutate a plugin-local store the core never sees (discord-allow.json, mode 0600, under DCTL_STATE_DIR). Two gates stack: Discord's default_member_permissions = Manage Server, plus that store — empty means everyone who can see the command (so the first operator can bootstrap), populated means listed users only. Treat the store as the real policy; the Discord default is overridable by a guild admin.

Reconnection

The gateway websocket reconnects with exponential backoff until the daemon context is cancelled. A non-recoverable close is treated as fatal: 4004 (bad token) and 4010–4014 (bad shard / API version / intents) stop the loop and log once rather than respamming a doomed IDENTIFY. Fix the token or the intents, then restart.

Further reading

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindCustomID added in v0.3.0

func BindCustomID(channel string) string

BindCustomID builds the custom_id carried by the menu that asks which repo a channel should work on.

func ChoiceCustomID

func ChoiceCustomID(session string) string

ChoiceCustomID builds the custom_id carried by a session's choice select menu.

func NewGatewaySet

func NewGatewaySet(ctx context.Context, cfg contracts.PluginConfig) (contracts.GatewaySet, error)

NewGatewaySet builds the Discord channel from config: it wires the outbound gateway, the read/status reader, the channel admin and the reachability prober.

func ParseBindCustomID added in v0.3.0

func ParseBindCustomID(id string) (string, bool)

ParseBindCustomID extracts the channel id from a repo-binding custom_id and reports whether the id is a binding menu at all.

func ParseChoiceCustomID

func ParseChoiceCustomID(id string) (string, bool)

ParseChoiceCustomID extracts the session name from a choice-menu custom_id and reports whether the id is a choice menu at all.

Types

type ChannelAdmin

type ChannelAdmin struct {
	// contains filtered or unexported fields
}

ChannelAdmin adapts the dctl client to contracts.ChannelAdmin: session channel creation/archival and posting.

func NewChannelAdmin

func NewChannelAdmin(c *dctl.Client) *ChannelAdmin

func (*ChannelAdmin) Archive

func (a *ChannelAdmin) Archive(ctx context.Context, id string) error

func (*ChannelAdmin) ChannelRef

func (a *ChannelAdmin) ChannelRef(id string) string

ChannelRef renders a Discord channel id as channel-mention markup so operator output links to the channel.

func (*ChannelAdmin) CreateUnder

func (a *ChannelAdmin) CreateUnder(ctx context.Context, parentID, name string) (string, error)

func (*ChannelAdmin) ForumPost

func (a *ChannelAdmin) ForumPost(ctx context.Context, forumID, name, content string) (string, error)

func (*ChannelAdmin) Kind

func (a *ChannelAdmin) Kind(ctx context.Context, id string) (string, error)

func (*ChannelAdmin) Send

func (a *ChannelAdmin) Send(ctx context.Context, channelID, content string) error

type Gateway

type Gateway struct {
	// contains filtered or unexported fields
}

Gateway adapts the Discord REST client to contracts.Gateway. When built from real config it also carries a slash runtime and a rendering sink, so it drives the slash surface (SessionControlReceiver) and renders the live turn stream (EventSink).

func NewGateway

func NewGateway(c client) *Gateway

func (*Gateway) BindSessionControl

func (g *Gateway) BindSessionControl(ctrl contracts.SessionControl)

BindSessionControl receives the daemon's runtime session controller and starts the slash command surface (sync + websocket loop). It satisfies contracts.SessionControlReceiver. Gateways built without a slash runtime (e.g. in tests) ignore the binding.

func (*Gateway) Emit

func (g *Gateway) Emit(contracts.Event)

Emit is the unrouted fallback for a host that does not route events. Without a conversation there is nothing to render into, so it drops rather than guessing a channel. It satisfies contracts.EventSink.

func (*Gateway) EmitTo added in v0.3.0

func (g *Gateway) EmitTo(conv contracts.Conversation, e contracts.Event)

EmitTo renders one live turn event into the conversation the host routed it to. It satisfies contracts.RoutedEventSink, which the host prefers over the flat EventSink — so each session renders into its own channel instead of one global default.

func (*Gateway) Manifest

func (g *Gateway) Manifest() contracts.Manifest

func (*Gateway) Menu

func (g *Gateway) Menu(ctx context.Context, conv contracts.Conversation, replyTo contracts.MessageID, prompt string, opts []contracts.Choice) error

func (*Gateway) Post

func (g *Gateway) Post(ctx context.Context, conv contracts.Conversation, text string) (contracts.MessageID, error)

func (*Gateway) React

func (g *Gateway) React(ctx context.Context, conv contracts.Conversation, msg contracts.MessageID, emoji string) error

func (*Gateway) Reply

func (g *Gateway) Reply(ctx context.Context, conv contracts.Conversation, replyTo contracts.MessageID, text string) (contracts.MessageID, error)

type Platform

type Platform struct {
	// contains filtered or unexported fields
}

Platform adapts the dctl client to the neutral channel ports contracts.ChannelReader and contracts.MenuRouter (the consumer's read/ channel-bootstrap/reaction/status/routed-menu surface).

func NewPlatform

func NewPlatform(c *dctl.Client) *Platform

func (*Platform) DefaultChannel

func (p *Platform) DefaultChannel() string

func (*Platform) Enabled

func (p *Platform) Enabled() bool

func (*Platform) EnsureChannel

func (p *Platform) EnsureChannel(ctx context.Context, parentID, name string) (contracts.Channel, error)

func (*Platform) Read

func (p *Platform) Read(ctx context.Context, channelID string, limit int, after string) ([]contracts.Message, error)

Read returns recent channel messages and records the id of the last non-bot message so the next turn's ACK reaction lands on it. A channel the router drives by push returns nothing: that channel already has an inbound path, and two would deliver every message twice.

func (*Platform) RouteMenu

func (p *Platform) RouteMenu(ctx context.Context, channelID, replyTo, prompt, route string, opts []contracts.Choice) (contracts.MessageID, error)

func (*Platform) Unreact

func (p *Platform) Unreact(ctx context.Context, channelID, messageID, emoji string) error

func (*Platform) UpsertStatusMessage

func (p *Platform) UpsertStatusMessage(ctx context.Context, channelID, messageID, content string) (string, error)

type Prober

type Prober struct {
	// contains filtered or unexported fields
}

Prober adapts a cheap REST round-trip (/users/@me) to contracts.Prober.

func NewProber

func NewProber(c *dctl.Client) *Prober

func (*Prober) Probe

func (p *Prober) Probe(ctx context.Context) (int64, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL