feat(discord): post a finished turn as the CLI's whole last message, stale thread cleanup, types moved to src/types

This commit is contained in:
Devvyn committed 2026-10-30 18:26:35 +08:00
1 parent d6c7d30bd4
commit 093f242662
18 files changed
+779 -97

No files matched your search

+1 -1
View File
@@ -1493,7 +1493,7 @@ One thread per tab in a private Discord channel, alerts out and replies typed ba
| Method | Path | Body | Notes |
| ------ | ------ | ------ | ------ |
| `GET` | `/api/discord` | none | Config without the token, plus `hasToken`, `tokenMasked`, `gateway` (`off` \| `connecting` \| `connected` \| `error`), `gatewayError` and `lastResult`. |
| `PUT` | `/api/discord` | `{ enabled?, token?, guildId?, parentChannelId?, allowedUserIds?, threadCreation?, onSessionDelete?, pingMode?, scope?, startupHint?, typedHint? }` (strict) | `threadCreation`: `lazy` \| `onSessionStart`. `onSessionDelete`: `keep` \| `archive` \| `delete`. `pingMode`: `off` \| `critical` \| `all`. `scope`: `attention` \| `all`. An absent `token` keeps the saved one; `""` clears it. `400` for a non-numeric id or enabling without a token, guild, channel and at least one allowed user. |
| `PUT` | `/api/discord` | `{ enabled?, token?, guildId?, parentChannelId?, allowedUserIds?, threadCreation?, onSessionDelete?, staleThreads?, pingMode?, scope?, startupHint?, typedHint? }` (strict) | `threadCreation`: `lazy` \| `onSessionStart`. `onSessionDelete`: `keep` \| `archive` \| `delete`. `pingMode`: `off` \| `critical` \| `all`. `scope`: `attention` \| `all`. An absent `token` keeps the saved one; `""` clears it. `400` for a non-numeric id or enabling without a token, guild, channel and at least one allowed user. |
| `POST` | `/api/discord/test` | none | Posts one message to the parent channel with the saved config, even while disabled. `200` with `data.ok`; `400` if no token or channel is saved. |
| `POST` | `/api/sessions/:id/discord-notify` | `{ message, urgent?, file? }` (strict, message 1 to 1500 characters) | A CLI asks for the user: posts into that tab's thread, pinging when `urgent`. `file` is an absolute path inside the tab's working folder (real path, regular file, at most 8 MB, local sessions only) uploaded with the post; `400` otherwise. A `200` carries the delivery result: `data.ok` is `false` (with `data.error`) when Discord refused or failed the post. `409` while the bridge is disabled, `429` past 6 posts a minute per tab. The `codeman notify "message" [--urgent] [--file path]` command wraps this route. |
+11 -9
View File
@@ -7,7 +7,8 @@ Codeman can post to a private Discord server when a session needs you, give ever
## What it does
- Posts when a session asks for permission, asks a question, goes idle, stops or errors.
- Posts when a session asks for permission, asks a question, goes idle or errors. A finished turn ("stops") is an `info` event, so it posts only when **Which events** is set to Everything.
- Posts a finished turn as the CLI's whole last message, with its bold, lists and numbered questions kept, split over several posts if it is long. This reads the CLI's transcript where it keeps one (Claude Code, Codex, DeepSeek Harness, OMP) and otherwise splits the screen into turns (OpenCode, Gemini, Antigravity, Pi, Grok, Copilot), which can start mid-message when a long answer has scrolled off. A shell, and a permission or question dialog, show the last lines of the screen instead, without the prompt row and status lines under them.
- Creates one thread per tab in a single parent channel, so a reply always reaches exactly one session.
- Types your reply into that session as a single line followed by Enter. A single digit sent while a permission or question is waiting goes as the key alone, without Enter, because the dialog acts on the keypress.
- Lets a CLI post a deliberate message (for example "press the reset button on the case") with the `codeman notify` command, which calls `POST /api/sessions/:id/discord-notify`. Each tab may post 6 a minute; past that the command says "too many messages", so a looping agent cannot ping your phone without end.
@@ -88,14 +89,15 @@ In the Discord mobile app set the server to **All Messages**. Discord's own push
## Options
| Option | Values | Default | Effect |
| ----------------- | --------------------------- | ----------- | --------------------------------------------------------------------------------------- |
| `threadCreation` | `lazy`, `onSessionStart` | `lazy` | `lazy` makes a thread on the first notification. `onSessionStart` makes one per new tab |
| `onSessionDelete` | `keep`, `archive`, `delete` | `archive` | What happens to the thread when its tab is deleted. `delete` needs Manage Threads |
| `pingMode` | `off`, `critical`, `all` | `critical` | `critical` pings you for permission prompts, questions, errors and urgent posts |
| `scope` | `attention`, `all` | `attention` | `attention` skips the quiet "response complete" event. `all` sends it too |
| `startupHint` | on, off | on | Tell Claude about `codeman notify` in its system prompt at launch |
| `typedHint` | on, off | off | Also send the note once, as a first message, to brand-new non-Claude CLI tabs |
| Option | Values | Default | Effect |
| ----------------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `threadCreation` | `lazy`, `onSessionStart` | `lazy` | `lazy` makes a thread on the first notification. `onSessionStart` makes one per new tab |
| `onSessionDelete` | `keep`, `archive`, `delete` | `archive` | What happens to the thread when its tab is deleted. `delete` needs Manage Threads |
| `staleThreads` | `keep`, `archive`, `delete` | `keep` | What happens to threads this bot made that no tab uses any more, checked after startup and then daily. Skips threads under an hour old, other people's threads and other channels, does nothing while the thread map is empty, and closes at most 25 per run. `delete` needs Manage Threads. Use one channel per Codeman |
| `pingMode` | `off`, `critical`, `all` | `critical` | `critical` pings you for permission prompts, questions, errors and urgent posts |
| `scope` | `attention`, `all` | `attention` | `attention` skips the quiet "response complete" event. `all` sends it too |
| `startupHint` | on, off | on | Tell Claude about `codeman notify` in its system prompt at launch |
| `typedHint` | on, off | off | Also send the note once, as a first message, to brand-new non-Claude CLI tabs |
## Telling the CLIs
+13 -9
View File
@@ -38,14 +38,18 @@ only), never in the shared settings, and never shown again once saved.
## Options
| Option | Choices | Default |
| ---------------------------------- | ---------------------------------------- | --------------- |
| Create threads | On the first alert, or for every new tab | First alert |
| When a tab is deleted | Keep, archive or delete the thread | Archive |
| @ ping | Never, critical events only, every post | Critical only |
| Which events | Needs attention, or everything | Needs attention |
| Tell Claude how to reach you | On or off | On |
| Also tell other CLIs, as a message | On or off | Off |
| Option | Choices | Default |
| ---------------------------------------- | ------------------------------------------- | --------------- |
| Create threads | On the first alert, or for every new tab | First alert |
| When a tab is deleted | Keep, archive or delete the thread | Archive |
| Stale threads | Keep, archive or delete threads no tab uses | Keep |
| @ ping | Never, critical events only, every post | Critical only |
| Which events | Needs attention, or everything | Needs attention |
| Tell Claude how to reach you | On or off | On |
| Also tell other CLIs, as a first message | On or off | Off |
A finished turn is posted as the CLI's whole last message, with its formatting and questions kept, split over
several posts if it is long. A shell, and permission or question dialogs, show the last lines of the screen instead.
Deleting a thread needs the **Manage Threads** permission on the channel. "Critical" means a
permission prompt, a question, an error, or a post an agent marked urgent.
@@ -55,7 +59,7 @@ permission prompt, a question, an error, or a post an agent marked urgent.
An agent only posts deliberately if it knows it can:
- **Claude** is launched with the instruction as part of its system prompt (**Tell Claude how to reach you**, on by default), so nothing shows in the chat. This reaches local tabs only (remote and Docker tabs have no `codeman` command).
- **Every other CLI** is told only if you turn on **Also tell other CLIs, as a message** (off by default: it costs one model turn per tab, and for a spawned worker or a scheduled run it arrives with the first real prompt). Then the same text is sent once, as a message, the first time a brand-new tab goes idle.
- **Every other CLI** is told only if you turn on **Also tell other CLIs, as a first message** (off by default: it costs one model turn per tab, and for a spawned worker or a scheduled run it arrives with the first real prompt). Then the same text is sent once, as a message, the first time a brand-new tab goes idle.
Restored tabs, shell tabs, and remote (ssh) and Docker tabs (which have no `codeman` command) are never sent it.
Tabs that were already open when you turned the bridge on are not told. The alerts still reach