Skip to content

Discord status: show what the agent is doing on the Discord profile (#278) - #279

Merged
MopigamesYT merged 5 commits into
devfrom
feat/discord-presence
Nov 9, 2026
Merged

MopigamesYT merged 5 commits into
devfrom
feat/discord-presence

Conversation

@MopigamesYT

@MopigamesYT MopigamesYT commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Closes #278

1loop's Discord application ID (1569084103125508196, registered by @MopigamesYT) is set in discordAppID; ONELOOP_DISCORD_APP_ID=<id> still overrides it. Stacked on #259 (settings registry): its commit shows here until it merges.

Summary

  • Settings → General has a new Discord section with two toggles, both off: Discord status and Share the chat. Searching "discord" or "rich presence" finds them. They are saved in 1loop's state file (discordPresence, discordDetails), and they're protected, so the model's settings tool can't turn them on.
  • With Discord status on and the Discord app open on the same computer, the profile shows Playing 1loop while a chat works. It includes "Working with ", what the agent is doing in general terms (Thinking, Running a command, Editing files, Waiting for the user…), the time since the run started, and an animated GIF of 1loop's work ring in that state. Hovering the image shows the activity and model, and others see a Get 1loop button. When nothing runs, it says Idle with the ring artwork. With several chats working, it follows the one that started last and adds "(+N)".
  • With Share the chat also on, the first line becomes " · " and the second line shows the agent's own activity line (e.g. "Running go test ./…", "Reading internal/ui/x.go"). Off, none of that leaves the computer.
  • When Discord isn't running, 1loop retries quietly every 15 s. When Discord restarts, the status comes back. Turning the setting off or quitting clears it immediately.
  • Desktop only: there's nothing on the phone, the web client builds a stub, and chats running on a remote 1loop server are not shown.

How claude-rpc does it (research)

claude-rpc (MIT) is a Node daemon fed by Claude Code hooks. Two of its tricks matter here, and no code or assets were copied:

  • No per-user Discord app: it ships its own application ID (1506443909406920948) as the default clientId. Rich Presence over local IPC needs only an application ID, with no secret or token, and the application's name is what "Playing …" shows. So 1loop needs its own application, registered once.
  • Animated images: assets.large_image takes any https URL. Discord fetches it through its media proxy and plays GIFs. claude-rpc points it at GIFs on its CDN (cdn.qualit.ly/clawd-*.gif). Here the GIFs are drawn from 1loop's own work ring and served from this repo through raw.githubusercontent.com.

Screenshots

Before After
Before After

The Discord images (frames of think, bash and edit; each is a 1.2 s loop):

Presence GIF frames

Checked on the real Discord app (Linux) with 1loop's ID: SET_ACTIVITY is accepted and the profile shows "Playing 1loop". No screenshot of the profile card: driving the screen on a multi-monitor desktop was unreliable.

Technical details

  • internal/discord (new, no dependency): the IPC protocol (frames of op + length + JSON; handshake → READY → SET_ACTIVITY with a nonce, answered; PING → PONG). Presence.Set stores the wanted activity, and one goroutine connects lazily, sends only changes, keeps at most one update per 4 s (Discord drops more than 5 per 20 s), and notices when Discord goes away. Texts are cut to 128 characters, and a 1-character text is dropped, since Discord refuses the whole activity for it. Sockets: dial_unix.go tries discord-ipc-0…9 in $XDG_RUNTIME_DIR, $TMPDIR, /run/user/<uid> and /tmp, plus the Discord and Vesktop Flatpak and Snap folders. dial_windows.go opens \\.\pipe\discord-ipc-N for overlapped I/O, because on a synchronous handle the background reader would block every write. dial_js.go is a stub.
  • internal/ui/discord_presence.go: syncPresence / presenceActivity build the activity from the running chats' chatRun (start time) and turn (mark → generic text and GIF; label only when shared). It runs from runStarted, runEnded and applyActivity, the same places that feed the phone's status. discordGIF draws the GIFs with the work ring's own layers (ringArt, thinkOpacity) on a dark square with a 256-colour amber/white palette, because GIF transparency is 1-bit.
  • assets/discord/*.gif: 11 states at 256 px, ~120 KB each. They're regenerated with UPDATE_DISCORD_ART=1 go test ./internal/ui -run TestDiscordArt, and the test fails if one is missing. Idle uses the existing assets/1loop-ring-on-black.png. The URLs name the commit that added them (raw.githubusercontent.com/Feur-Inc/1loop/6cb9b3c…/assets/), so they work from any branch or build and never change; pointing at main showed Discord's placeholder until a release.
  • Settings: general.discord_presence and general.discord_details are in internal/settings/settings.go (desktop, protected). The phone copy was regenerated (UPDATE_SETTINGS=1 go test ./internal/settings), and two search cases were added.
  • Flatpak: --filesystem=xdg-run/discord-ipc-0, plus the Discord and Vesktop Flatpak runtime folders, since --filesystem=host does not include /run/user.
  • Tests: internal/discord runs against a fake Discord over net.Pipe (handshake, ping, set, clear on close, text limits). A UI test checks that nothing of the chat leaks unless it's shared. GOOS=windows go vet ./internal/discord passes.

Assisted-by: Claude Opus 5.5

…278)

Off by default, in General → Discord. While a chat runs, the profile shows
"Playing 1loop" with the model, a generic activity (thinking, running a
command, editing files…), the elapsed time and the work ring's animation
for that state as a GIF; "Idle" otherwise. Sharing the chat's title,
project and current command or file is a second, separate toggle.

internal/discord speaks Discord's local IPC (unix socket, Flatpak and Snap
folders, overlapped named pipe on Windows) with no new dependency, waits
for Discord when it is not running and keeps to its rate limit. Like
claude-rpc, it uses a shared application ID and https image URLs, so users
make nothing on the developer portal; 1loop's own ID is still to be set
(discordAppID, or ONELOOP_DISCORD_APP_ID meanwhile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MopigamesYT MopigamesYT added enhancement New feature or request desktop Desktop app (Linux, Windows) labels Nov 8, 2026
@MopigamesYT MopigamesYT self-assigned this Nov 8, 2026
@MopigamesYT
MopigamesYT requested a review from tpm28 November 8, 2026 20:18
MopigamesYT and others added 3 commits November 8, 2026 21:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d is "Waiting for a command" (#278)

The URLs named main, where the GIFs are not yet: Discord showed its
placeholder for every working state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…278)

Sharing a folder there makes the sandbox fail to start (bwrap: Can't make
symlink at /run/user/<uid>/.flatpak), so the app would not launch. Vesktop
users reach it through the usual discord-ipc-0 link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Discord settings join #259's General page after the tray's Window
section; the saved state keeps the tray's, folded projects' and Discord's
fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MopigamesYT
MopigamesYT merged commit 3656e43 into dev Nov 9, 2026
4 of 7 checks passed
@MopigamesYT
MopigamesYT deleted the feat/discord-presence branch November 9, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Desktop app (Linux, Windows) enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants