Repository navigation
Discord status: show what the agent is doing on the Discord profile (#278) - #279
Merged
Merged
Conversation
…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>
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>
tpm28
approved these changes
Nov 9, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #278
Summary
discordPresence,discordDetails), and they're protected, so the model's settings tool can't turn them on.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:
1506443909406920948) as the defaultclientId. 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.assets.large_imagetakes 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
The Discord images (frames of
think,bashandedit; each is a 1.2 s loop):Checked on the real Discord app (Linux) with 1loop's ID:
SET_ACTIVITYis 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_ACTIVITYwith a nonce, answered; PING → PONG).Presence.Setstores 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.gotriesdiscord-ipc-0…9in$XDG_RUNTIME_DIR,$TMPDIR,/run/user/<uid>and/tmp, plus the Discord and Vesktop Flatpak and Snap folders.dial_windows.goopens\\.\pipe\discord-ipc-Nfor overlapped I/O, because on a synchronous handle the background reader would block every write.dial_js.gois a stub.internal/ui/discord_presence.go:syncPresence/presenceActivitybuild the activity from the running chats'chatRun(start time) andturn(mark→ generic text and GIF;labelonly when shared). It runs fromrunStarted,runEndedandapplyActivity, the same places that feed the phone's status.discordGIFdraws 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 withUPDATE_DISCORD_ART=1 go test ./internal/ui -run TestDiscordArt, and the test fails if one is missing. Idle uses the existingassets/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 atmainshowed Discord's placeholder until a release.general.discord_presenceandgeneral.discord_detailsare ininternal/settings/settings.go(desktop, protected). The phone copy was regenerated (UPDATE_SETTINGS=1 go test ./internal/settings), and two search cases were added.--filesystem=xdg-run/discord-ipc-0, plus the Discord and Vesktop Flatpak runtime folders, since--filesystem=hostdoes not include/run/user.internal/discordruns against a fake Discord overnet.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/discordpasses.Assisted-by: Claude Opus 5.5