@nordbyte/nordrelay 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.env.example +80 -11
  2. package/README.md +154 -22
  3. package/dist/access-control.js +7 -1
  4. package/dist/activity-events.js +44 -0
  5. package/dist/audit-log.js +40 -2
  6. package/dist/bot-preferences.js +1 -0
  7. package/dist/bot-rendering.js +10 -7
  8. package/dist/bot.js +535 -11
  9. package/dist/channel-actions.js +7 -2
  10. package/dist/channel-adapter.js +40 -7
  11. package/dist/channel-command-catalog.js +88 -0
  12. package/dist/channel-command-service.js +369 -0
  13. package/dist/channel-mirror-registry.js +77 -0
  14. package/dist/channel-peer-prompt.js +95 -0
  15. package/dist/channel-runtime.js +12 -5
  16. package/dist/channel-turn-service.js +237 -0
  17. package/dist/codex-state.js +114 -78
  18. package/dist/config-metadata.js +93 -13
  19. package/dist/config.js +103 -8
  20. package/dist/context-key.js +87 -5
  21. package/dist/discord-artifacts.js +165 -0
  22. package/dist/discord-bot.js +2073 -0
  23. package/dist/discord-channel-runtime.js +133 -0
  24. package/dist/discord-command-surface.js +57 -0
  25. package/dist/discord-rate-limit.js +141 -0
  26. package/dist/index.js +36 -5
  27. package/dist/job-store.js +127 -0
  28. package/dist/metrics.js +87 -0
  29. package/dist/peer-auth.js +85 -0
  30. package/dist/peer-client.js +256 -0
  31. package/dist/peer-context.js +21 -0
  32. package/dist/peer-identity.js +127 -0
  33. package/dist/peer-runtime-service.js +636 -0
  34. package/dist/peer-server.js +220 -0
  35. package/dist/peer-store.js +294 -0
  36. package/dist/peer-types.js +52 -0
  37. package/dist/relay-external-activity-monitor.js +47 -6
  38. package/dist/relay-runtime-helpers.js +208 -0
  39. package/dist/relay-runtime.js +897 -394
  40. package/dist/remote-prompt.js +98 -0
  41. package/dist/runtime-cache.js +57 -0
  42. package/dist/session-locks.js +10 -7
  43. package/dist/support-bundle.js +1 -0
  44. package/dist/telegram-access-commands.js +15 -2
  45. package/dist/telegram-access-middleware.js +16 -3
  46. package/dist/telegram-agent-commands.js +25 -0
  47. package/dist/telegram-artifact-commands.js +46 -0
  48. package/dist/telegram-command-menu.js +3 -53
  49. package/dist/telegram-diagnostics-command.js +5 -50
  50. package/dist/telegram-general-commands.js +16 -6
  51. package/dist/telegram-operational-commands.js +14 -6
  52. package/dist/telegram-preference-commands.js +23 -127
  53. package/dist/telegram-queue-commands.js +74 -4
  54. package/dist/telegram-support-command.js +7 -0
  55. package/dist/telegram-update-commands.js +27 -0
  56. package/dist/user-management.js +208 -0
  57. package/dist/web-api-contract.js +17 -0
  58. package/dist/web-dashboard-access-routes.js +74 -1
  59. package/dist/web-dashboard-artifact-routes.js +3 -3
  60. package/dist/web-dashboard-assets.js +2 -0
  61. package/dist/web-dashboard-pages.js +109 -13
  62. package/dist/web-dashboard-peer-routes.js +204 -0
  63. package/dist/web-dashboard-runtime-routes.js +53 -8
  64. package/dist/web-dashboard-session-routes.js +27 -20
  65. package/dist/web-dashboard-ui.js +2 -0
  66. package/dist/web-dashboard.js +160 -6
  67. package/dist/web-state.js +33 -2
  68. package/dist/webui-assets/dashboard.css +75 -1
  69. package/dist/webui-assets/dashboard.js +779 -55
  70. package/package.json +5 -2
  71. package/plugins/nordrelay/scripts/nordrelay.mjs +578 -19
package/.env.example CHANGED
@@ -1,9 +1,11 @@
1
1
  # NordRelay runtime config example.
2
- # Access is managed with NordRelay users, groups, linked Telegram identities, and enabled Telegram group chats.
2
+ # Access is managed with NordRelay users, groups, linked chat identities, and enabled group/guild channels.
3
3
  # Create the first admin with `nordrelay init` or `nordrelay user create-admin`.
4
4
 
5
5
  # Telegram
6
- # Required Telegram bot and transport settings.
6
+ # Telegram bot and transport settings.
7
+ # Start the Telegram bot adapter.
8
+ TELEGRAM_ENABLED=true
7
9
  # BotFather token.
8
10
  TELEGRAM_BOT_TOKEN=123456789:replace-me
9
11
  # polling or webhook.
@@ -20,6 +22,40 @@ TELEGRAM_WEBHOOK_PATH=/telegram/webhook
20
22
  # Optional Telegram webhook secret token.
21
23
  TELEGRAM_WEBHOOK_SECRET=
22
24
 
25
+ # Discord
26
+ # Discord bot settings. Discord is opt-in and uses the same NordRelay users, groups, and permissions as Telegram.
27
+ # Start the Discord bot adapter.
28
+ DISCORD_ENABLED=false
29
+ # Discord bot token.
30
+ DISCORD_BOT_TOKEN=
31
+ # Discord application/client id used for slash command registration.
32
+ DISCORD_CLIENT_ID=
33
+ # Comma-separated guild ids for instant guild slash-command registration.
34
+ DISCORD_GUILD_IDS=
35
+ # Optional comma-separated guild allow-list.
36
+ DISCORD_ALLOWED_GUILD_IDS=
37
+ # Optional comma-separated channel allow-list before user/group checks.
38
+ DISCORD_ALLOWED_CHANNEL_IDS=
39
+ # Read regular Discord text messages as prompts. Requires enabling the privileged intent in Discord.
40
+ DISCORD_MESSAGE_CONTENT_ENABLED=true
41
+ # slash, message, or both.
42
+ # Options: slash, message, both
43
+ DISCORD_COMMAND_MODE=both
44
+ # Register Discord slash commands on startup when client id is configured.
45
+ DISCORD_AUTO_REGISTER_COMMANDS=true
46
+ # Optional Discord override for CLI mirror mode. Uses the NordRelay default when unset.
47
+ # Options: off, status, final, full
48
+ DISCORD_CLI_MIRROR_MODE=
49
+ # Optional Discord override for mirrored edit interval.
50
+ DISCORD_CLI_MIRROR_MIN_UPDATE_MS=
51
+ # Optional Discord override for completion notifications.
52
+ # Options: off, minimal, all
53
+ DISCORD_NOTIFY_MODE=
54
+ # Optional Discord quiet hours override. Use HH-HH, off, or leave blank for default.
55
+ DISCORD_QUIET_HOURS=
56
+ # Optional Discord override for automatic artifact summaries/uploads.
57
+ DISCORD_AUTO_SEND_ARTIFACTS=
58
+
23
59
  # Agents
24
60
  # Agent access. Codex is enabled by default; Pi, Hermes, OpenClaw, and Claude Code are opt-in.
25
61
  # Allow Codex sessions.
@@ -161,15 +197,27 @@ ENABLE_TELEGRAM_REACTIONS=false
161
197
  TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS=80
162
198
  # Minimum edit interval.
163
199
  TELEGRAM_EDIT_MIN_INTERVAL_MS=1200
164
- # off, status, final, or full.
200
+ # Default mirror mode for chat adapters: off, status, final, or full.
165
201
  # Options: off, status, final, full
166
- TELEGRAM_CLI_MIRROR_MODE=status
167
- # Minimum mirrored edit interval.
168
- TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS=4000
169
- # off, minimal, or all.
202
+ NORDRELAY_CLI_MIRROR_MODE=status
203
+ # Default minimum mirrored edit interval.
204
+ NORDRELAY_CLI_MIRROR_MIN_UPDATE_MS=4000
205
+ # Default completion notifications: off, minimal, or all.
170
206
  # Options: off, minimal, all
171
- TELEGRAM_NOTIFY_MODE=minimal
172
- # HH-HH or blank.
207
+ NORDRELAY_NOTIFY_MODE=minimal
208
+ # Default quiet hours. Use HH-HH, off, or leave blank.
209
+ NORDRELAY_QUIET_HOURS=
210
+ # Default automatic artifact summaries/uploads for chat adapters.
211
+ NORDRELAY_AUTO_SEND_ARTIFACTS=false
212
+ # Optional Telegram override for CLI mirror mode. Uses the NordRelay default when unset.
213
+ # Options: off, status, final, full
214
+ TELEGRAM_CLI_MIRROR_MODE=
215
+ # Optional Telegram override for mirrored edit interval.
216
+ TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS=
217
+ # Optional Telegram override for completion notifications.
218
+ # Options: off, minimal, all
219
+ TELEGRAM_NOTIFY_MODE=
220
+ # Optional Telegram quiet hours override. Use HH-HH, off, or leave blank for default.
173
221
  TELEGRAM_QUIET_HOURS=
174
222
  # Additional comma-separated regex patterns.
175
223
  TELEGRAM_REDACT_PATTERNS=
@@ -191,8 +239,8 @@ ARTIFACT_MAX_INBOX_DIRS=30
191
239
  ARTIFACT_IGNORE_DIRS=
192
240
  # Extra ignored glob patterns.
193
241
  ARTIFACT_IGNORE_GLOBS=
194
- # Automatically send artifact files.
195
- TELEGRAM_AUTO_SEND_ARTIFACTS=false
242
+ # Optional Telegram override for automatic artifact summaries/uploads.
243
+ TELEGRAM_AUTO_SEND_ARTIFACTS=
196
244
 
197
245
  # Workspace
198
246
  # State and workspace guardrails.
@@ -207,11 +255,32 @@ NORDRELAY_STATE_BACKEND=json
207
255
  NORDRELAY_AUDIT_MAX_EVENTS=1000
208
256
  # Write-lock TTL.
209
257
  NORDRELAY_SESSION_LOCK_TTL_MS=1800000
258
+ # Stale-while-refresh TTL for expensive dashboard API snapshots.
259
+ NORDRELAY_DASHBOARD_CACHE_TTL_MS=10000
260
+ # Maximum persisted unified jobs retained for the WebUI jobs view.
261
+ NORDRELAY_UNIFIED_JOB_MAX_ITEMS=1000
210
262
  # NPM version cache TTL.
211
263
  NORDRELAY_VERSION_CACHE_TTL_MS=3600000
212
264
  # Installed agent CLI version cache TTL.
213
265
  NORDRELAY_CLI_VERSION_CACHE_TTL_MS=60000
214
266
 
267
+ # Peers
268
+ # Optional NordRelay-to-NordRelay federation. Pairing is explicit, authenticated, scoped, and TLS-protected.
269
+ # Expose the dedicated authenticated NordRelay peer API.
270
+ NORDRELAY_PEER_ENABLED=false
271
+ # Human-readable name shown to paired NordRelay instances.
272
+ NORDRELAY_PEER_NAME=
273
+ # Bind host for the peer API. Use 127.0.0.1 for local-only or a LAN/interface IP when explicitly exposing peers.
274
+ NORDRELAY_PEER_HOST=127.0.0.1
275
+ # Port for the peer API.
276
+ NORDRELAY_PEER_PORT=31979
277
+ # Optional public URL other instances should use for this node.
278
+ NORDRELAY_PEER_PUBLIC_URL=
279
+ # Serve the peer API over HTTPS with an automatically generated local certificate.
280
+ NORDRELAY_PEER_TLS_ENABLED=true
281
+ # Reject plaintext peer serving on non-loopback hosts.
282
+ NORDRELAY_PEER_REQUIRE_TLS=true
283
+
215
284
  # Voice
216
285
  # Optional voice transcription settings.
217
286
  # Whisper fallback API key.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # NordRelay
2
2
 
3
- NordRelay is a remote control plane for coding agents across messaging channels. The current implementation connects Codex, Pi, Hermes, OpenClaw, and Claude Code coding-agent sessions to Telegram, keeps independent sessions per chat or forum topic, streams replies and tool activity back to Telegram, supports files, photos, voice input, model controls, session browsing, retry/abort, and CLI handback.
3
+ NordRelay is a remote control plane for coding agents across messaging channels and paired NordRelay instances. The current implementation connects Codex, Pi, Hermes, OpenClaw, and Claude Code coding-agent sessions to Telegram, Discord, the WebUI, and trusted peer nodes, keeps independent sessions per chat, thread, forum topic, DM, or remote target, streams replies and tool activity back to the active channel, supports files, photos, voice input, model controls, session browsing, retry/abort, CLI handback, and scoped multi-host control.
4
4
 
5
5
  The repo is both a local Codex marketplace and a standalone Node app. The plugin lives in `plugins/nordrelay/`; the full bot runtime lives in `src/` and uses `@openai/codex-sdk` for Codex, Pi RPC mode for Pi, the Hermes API Server for Hermes, the OpenClaw Gateway WebSocket RPC surface for OpenClaw, and the Claude Agent SDK for Claude Code.
6
6
 
@@ -43,11 +43,23 @@ Session control:
43
43
 
44
44
  Adapter architecture:
45
45
 
46
- - Telegram is implemented as the first channel adapter with text, typing, streaming edits, inline buttons, files, photos, voice, topics, and webhook capability metadata.
47
- - `/channels` shows available and planned messaging adapters for Discord, WhatsApp, Slack, and Matrix.
46
+ - Telegram supports text, typing, streaming edits, inline buttons, files, photos, voice, forum topics, and polling/webhook transport.
47
+ - Discord supports text, typing, streaming edits, buttons, files, photos, voice/audio transcription, guild channels, threads, DMs, message commands, and slash commands.
48
+ - `/channels` shows available and planned messaging adapters for Telegram, Discord, WhatsApp, Slack, and Matrix.
48
49
  - Codex, Pi, Hermes, OpenClaw, and Claude Code are implemented as agent adapters.
49
50
  - `/agents` shows available/planned agent adapters and whether Codex, Pi, Hermes, OpenClaw, and Claude Code are enabled.
50
- - Shared command-action renderers and a channel runtime contract keep inbound commands, outbound messages, typing, files, inline actions, and streaming-ready delivery separate from Telegram-specific API calls.
51
+ - Shared command-action renderers and a channel runtime contract keep inbound commands, outbound messages, typing, files, inline actions, and streaming-ready delivery separate from channel-specific API calls.
52
+
53
+ Peer federation:
54
+
55
+ - Optional NordRelay-to-NordRelay pairing lets one instance operate agents on trusted Ubuntu, macOS, Windows, LAN, or remote hosts.
56
+ - Peer serving is disabled by default and uses a dedicated API port separate from the dashboard.
57
+ - Pairing requires an explicit one-time invitation code, Ed25519 node identity verification, a per-peer shared secret, request HMAC signatures, timestamp/nonce replay protection, and TLS fingerprint pinning.
58
+ - Peer scopes restrict which remote WebUI/API actions are allowed, including read, prompt, queue, file, diagnostic, log, and session permissions.
59
+ - Peer records can also restrict allowed agent ids, allowed workspace roots, and per-peer workspace aliases such as `app=/srv/app`.
60
+ - The WebUI has a Peers page plus a local/remote target selector; dashboard pages, SSE streaming, queue actions, artifact downloads, health checks, and the global session browser proxy through the selected peer when allowed.
61
+ - Telegram and Discord expose `/peers` and `/target` so a linked user can choose whether prompts run locally or on a paired NordRelay instance.
62
+ - Remote prompts stream text, tool status, turn completion, and errors back to the originating Telegram or Discord context.
51
63
 
52
64
  Codex runtime:
53
65
 
@@ -159,19 +171,33 @@ Telegram output:
159
171
  - Old artifact and inbox turn directories are pruned automatically with configurable retention.
160
172
  - Optional Telegram message reactions can acknowledge work start and completion with `ENABLE_TELEGRAM_REACTIONS=true`.
161
173
 
174
+ Discord input and output:
175
+
176
+ - Enable Discord with `DISCORD_ENABLED=true` and `DISCORD_BOT_TOKEN`. If a requested chat adapter is missing its token, NordRelay disables that adapter and keeps running as long as another chat adapter is usable.
177
+ - Set `DISCORD_CLIENT_ID` to let NordRelay register slash commands automatically.
178
+ - `DISCORD_COMMAND_MODE=both` supports slash commands and `/command` text messages. Set it to `slash` if the bot should not read message commands.
179
+ - `DISCORD_MESSAGE_CONTENT_ENABLED=true` lets regular Discord messages become prompts. The matching privileged intent must also be enabled in the Discord Developer Portal.
180
+ - Discord DMs, guild channels, and threads get independent NordRelay contexts.
181
+ - Discord attachments are staged like Telegram uploads; images are passed as image inputs and audio files are transcribed before prompting.
182
+ - Discord buttons cover session picks, model/reasoning/launch picks, queue actions, artifact actions, update jobs, and abort where Discord component limits allow.
183
+ - Discord slash commands mirror the Telegram command surface where Discord supports it: `/agent`, `/auth`, `/login`, `/logout`, `/session`, `/sessions`, `/new`, `/switch`, `/attach`, `/handback`, `/workspaces`, `/pin`, `/unpin`, `/pinned`, `/model`, `/reasoning`, `/fast`, `/launch`, `/launch_profiles`, `/queue`, `/stop`, `/retry`, `/sync`, `/progress`, `/activity`, `/audit`, `/artifacts`, `/logs`, `/version`, `/diagnostics`, `/restart`, `/update`, `/lock`, `/unlock`, `/mirror`, `/notify`, `/voice`, `/link`, `/whoami`, and `/register_channel`.
184
+
162
185
  Authentication and safety:
163
186
 
164
187
  - WebUI login is required for every dashboard page, API route, SSE stream, artifact download, and health endpoint.
165
- - Access is managed through NordRelay users, groups, permissions, web sessions, and linked Telegram identities.
166
- - Built-in groups are `Admin`, `User`, and `Read Only`; custom groups can be created in the WebUI and can restrict allowed agents, workspace roots, and Telegram chats.
188
+ - Access is managed through NordRelay users, groups, permissions, web sessions, linked Telegram identities, and linked Discord identities.
189
+ - Built-in groups are `Admin`, `User`, and `Read Only`; custom groups can be created in the WebUI and can restrict allowed agents, workspace roots, Telegram chats, and Discord channels.
167
190
  - The last active admin cannot be disabled or demoted, and web sessions are revoked when passwords or group memberships change.
168
191
  - Admins can review and revoke active WebUI sessions from the Users page.
169
192
  - Telegram private chats require a linked active NordRelay user.
170
193
  - Telegram group and forum chats must be registered before use; admins can run `/register_chat` in the chat or enable chats in the WebUI.
194
+ - Discord DMs require a linked active NordRelay user.
195
+ - Discord guild channels and threads must be registered before use; admins can run `/register_channel` in the channel or enable channels in the WebUI.
171
196
  - `/whoami` shows the linked NordRelay account and groups.
172
197
  - `/link <code>` links a Telegram account to a NordRelay user after a link code is created in the WebUI or with `nordrelay user link-code`.
173
- - WebUI login and Telegram link attempts are rate-limited to reduce brute-force risk.
174
- - User, group, Telegram-link, Telegram-chat, web-session, login, and permission-denied events are written to the audit log.
198
+ - `/link <code>` also links a Discord account when the code was created as a Discord link code.
199
+ - WebUI login and chat-account link attempts are rate-limited to reduce brute-force risk.
200
+ - User, group, Telegram-link, Telegram-chat, Discord-link, Discord-channel, web-session, login, and permission-denied events are written to the audit log.
175
201
  - `/auth` reports Codex authentication, Pi provider environment health, Hermes API Server reachability, OpenClaw Gateway reachability, or Claude Code CLI auth for the selected agent.
176
202
  - `/login` starts Telegram-managed CLI auth for Codex, Hermes, or Claude Code when enabled.
177
203
  - `/logout` signs out of CLI auth for Codex, Hermes, or Claude Code; Codex logout is disabled while `CODEX_API_KEY` is in use.
@@ -185,18 +211,25 @@ Operations:
185
211
  - Plugin command/skill starts, stops, restarts, and inspects the connector process.
186
212
  - Manual process commands support `start`, `stop`, `restart`, `status`, `update`, and `foreground`.
187
213
  - Telegram admin commands support `/logs`, `/diagnostics`, `/support`, `/restart`, and `/update` for NordRelay and agent CLIs.
214
+ - `nordrelay peer identity`, `list`, `invite`, `add`, `test`, and `revoke` manage secure peer federation from the CLI.
188
215
  - `nordrelay update`, `/update`, and the WebUI update button detect the install type: npm installs update with `npm install -g @nordbyte/nordrelay@latest`; source checkouts pull `origin/main`, install dependencies, run check, tests, and build, then restart if the connector is running.
189
216
  - `/update agents`, `/update <agent>`, `/update install <agent>`, `/update jobs`, `/update log <id>`, `/update cancel <id>`, and `/update input <id> <text>` manage Codex, Pi, Hermes, OpenClaw, and Claude Code updater or installer jobs from Telegram.
190
217
  - `/logs` renders redacted connector, NordRelay update, and agent update logs with local-time timestamps, levels, file path, last-modified time, and highlighted warnings/errors.
191
218
  - Logs can be emitted as timestamped plain text or JSON records with `CONNECTOR_LOG_FORMAT`.
192
219
  - Telegram sends/edits/documents are routed through a rate-limit queue that honors Telegram retry-after responses.
220
+ - Mirror, notification, quiet-hour, and automatic artifact-delivery defaults are configured through channel-neutral `NORDRELAY_*` settings, with Telegram and Discord override keys when a channel should differ.
221
+ - The WebUI Tasks page includes a unified Jobs view for active WebUI turns, external CLI turns, queued prompts, agent update/install jobs, self-updates, and diagnostics bundle exports, with log, cancel, and retry actions where supported.
222
+ - Unified Jobs are persisted across restarts and retain recent prompt, queue, update, connector-update, and support-bundle history for WebUI inspection.
223
+ - The WebUI Metrics page reports queue state, active/completed/failed turns, job counts, average prompt duration, and Telegram/Discord rate-limit counters.
224
+ - Expensive dashboard views such as version checks, adapter health, and diagnostics use a short stale-while-refresh server cache so the UI can render recent data while fresh checks run in the background.
193
225
  - Context metadata, queues, and preferences are written atomically with backup recovery.
194
226
  - Context metadata, queues, preferences, audit events, and locks can use JSON files or the optional SQLite state backend with `NORDRELAY_STATE_BACKEND=sqlite`.
195
227
  - Runtime config, state, and logs are written under `~/.nordrelay/`.
196
228
  - `nordrelay init` creates a private runtime config, `nordrelay doctor` validates host prerequisites, and `nordrelay web` starts the connector plus a full local WebUI dashboard.
229
+ - On first WebUI startup without an admin account, NordRelay shows a setup wizard for creating the first admin; remote setup requires the one-time token printed in the server console.
197
230
  - The WebUI has responsive header/sidebar/footer navigation, live chat streaming, session controls, queue/artifact/log/diagnostic views, and settings management.
198
231
  - The WebUI supports light and dark themes, tabbed settings groups, paginated session browsing, and chat uploads for images, documents, and audio transcription.
199
- - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, diagnostics, and redacted diagnostics bundle export.
232
+ - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, diagnostics, peers, and redacted diagnostics bundle export.
200
233
  - The dashboard can bind to `127.0.0.1` or `0.0.0.0`; user login and session cookies are mandatory in both modes.
201
234
  - Telegram can run with long polling or an HTTP webhook via `TELEGRAM_TRANSPORT=webhook`.
202
235
  - Version freshness checks are cached with `NORDRELAY_VERSION_CACHE_TTL_MS`, and installed agent CLI version checks are cached with `NORDRELAY_CLI_VERSION_CACHE_TTL_MS`, to keep `/version` and adapter health responsive.
@@ -218,12 +251,16 @@ nordrelay start
218
251
  ```
219
252
 
220
253
  npm is the fastest install path and is the recommended default for normal use. `nordrelay init` writes the private runtime config to `~/.nordrelay/nordrelay.env`.
254
+ If you start `nordrelay web` before creating an admin, the dashboard opens a first-run setup wizard. Remote setup requires the one-time setup token printed in the NordRelay console.
221
255
 
222
256
  Non-interactive setup is also supported:
223
257
 
224
258
  ```bash
225
259
  nordrelay init \
226
260
  --token 123456789:replace-me \
261
+ --enable-discord \
262
+ --discord-token "discord-bot-token" \
263
+ --discord-client-id "discord-client-id" \
227
264
  --admin-email you@example.com \
228
265
  --admin-name "Your Name" \
229
266
  --admin-password "replace-with-a-long-password" \
@@ -231,6 +268,7 @@ nordrelay init \
231
268
  ```
232
269
 
233
270
  `--telegram-user-id` is optional, but linking the first admin during setup is the fastest way to use Telegram immediately.
271
+ Use `--discord-user-id <id>` with `nordrelay user create-admin` or `nordrelay user link-discord` to link Discord directly.
234
272
 
235
273
  Source checkout setup:
236
274
 
@@ -253,10 +291,23 @@ Create the Telegram bot:
253
291
  5. Create the first admin user with `nordrelay init` or `nordrelay user create-admin`.
254
292
  6. Link Telegram from the WebUI, with `nordrelay user link-telegram`, or by creating a link code and sending `/link <code>` to the bot.
255
293
 
294
+ Create the Discord bot:
295
+
296
+ 1. Open the Discord Developer Portal and create an application.
297
+ 2. Add a bot user and copy the bot token into `DISCORD_BOT_TOKEN`.
298
+ 3. Copy the application client ID into `DISCORD_CLIENT_ID`.
299
+ 4. Enable the Message Content intent if you want regular Discord messages to become prompts.
300
+ 5. Invite the bot with application-command, message-send, message-read, attachment, and thread permissions.
301
+ 6. Link Discord from the WebUI, with `nordrelay user link-discord`, or by creating a Discord link code and sending `/link <code>` to the bot.
302
+ 7. In guild channels, run `/register_channel` once from an admin-linked Discord account.
303
+
256
304
  Minimal private-bot `~/.nordrelay/nordrelay.env`:
257
305
 
258
306
  ```dotenv
259
307
  TELEGRAM_BOT_TOKEN=123456789:replace-me
308
+ DISCORD_ENABLED=false
309
+ DISCORD_BOT_TOKEN=
310
+ DISCORD_CLIENT_ID=
260
311
  NORDRELAY_CODEX_ENABLED=true
261
312
  NORDRELAY_PI_ENABLED=false
262
313
  NORDRELAY_HERMES_ENABLED=false
@@ -267,14 +318,44 @@ CODEX_SANDBOX_MODE=workspace-write
267
318
  CODEX_APPROVAL_POLICY=never
268
319
  ```
269
320
 
270
- User and Telegram access management:
321
+ User and chat access management:
271
322
 
272
323
  - `nordrelay init` creates the first admin user and writes `~/.nordrelay/users.json`.
273
324
  - `nordrelay user create-admin --email you@example.com --name "Your Name"` creates another admin.
274
325
  - `nordrelay user create --email dev@example.com --name "Dev" --group user` creates a normal user.
275
326
  - `nordrelay user link-telegram --email you@example.com --telegram-user-id 123456789` links a Telegram account directly.
276
- - `nordrelay user link-code --email you@example.com` creates a short-lived code that the user sends as `/link <code>` to the bot.
277
- - Group chats are disabled until an admin enables them from the WebUI or runs `/register_chat` inside the group.
327
+ - `nordrelay user link-discord --email you@example.com --discord-user-id 123456789012345678` links a Discord account directly.
328
+ - `nordrelay user link-code --email you@example.com` creates a short-lived Telegram code that the user sends as `/link <code>` to the Telegram bot.
329
+ - `nordrelay user discord-link-code --email you@example.com` creates a short-lived Discord code that the user sends as `/link <code>` to the Discord bot.
330
+ - Telegram group chats are disabled until an admin enables them from the WebUI or runs `/register_chat` inside the group.
331
+ - Discord guild channels are disabled until an admin enables them from the WebUI or runs `/register_channel` inside the channel.
332
+
333
+ Peer setup:
334
+
335
+ 1. On each host that should accept peer connections, set `NORDRELAY_PEER_ENABLED=true` in `~/.nordrelay/nordrelay.env`.
336
+ 2. Keep `NORDRELAY_PEER_TLS_ENABLED=true` and `NORDRELAY_PEER_REQUIRE_TLS=true` for LAN or internet use.
337
+ 3. Use `NORDRELAY_PEER_HOST=127.0.0.1` for local-only testing, a LAN/interface IP for trusted local networks, or keep the peer API behind a TLS reverse proxy/VPN for internet access.
338
+ 4. Set `NORDRELAY_PEER_PUBLIC_URL=https://host.example:31979` when other hosts cannot reach the bind address directly.
339
+ 5. Restart NordRelay on the accepting host and create an invitation:
340
+
341
+ ```bash
342
+ nordrelay peer invite --name workstation --scopes inspect,sessions.read,sessions.write,prompt.send,prompt.abort,queue.read,queue.write,files.read,files.write,diagnostics.read,logs.read
343
+ ```
344
+
345
+ 6. On the controlling host, run the printed command:
346
+
347
+ ```bash
348
+ nordrelay peer add https://workstation.example:31979 --code one-time-code
349
+ ```
350
+
351
+ 7. Confirm the connection:
352
+
353
+ ```bash
354
+ nordrelay peer list
355
+ nordrelay peer test <peer-id>
356
+ ```
357
+
358
+ Use `--workspace-aliases app=/srv/app,demo=/home/me/demo` on invites when a controller should be able to start remote sessions with short workspace names. Use the WebUI Peers page for the same invite, pair, enable/disable, test, alias, global-session, and revoke workflow. Use `/peers` from Telegram or Discord to inspect paired nodes and `/target <peer-id>` or `/target local` to choose where subsequent prompts run.
278
359
 
279
360
  Codex authentication:
280
361
 
@@ -361,6 +442,9 @@ nordrelay restart
361
442
  nordrelay stop
362
443
  nordrelay foreground
363
444
  nordrelay web
445
+ nordrelay peer list
446
+ nordrelay peer invite
447
+ nordrelay peer add https://peer.example:31979 --code one-time-code
364
448
  ```
365
449
 
366
450
  Source checkout process commands:
@@ -375,6 +459,7 @@ node plugins/nordrelay/scripts/nordrelay.mjs foreground
375
459
  node plugins/nordrelay/scripts/nordrelay.mjs user list
376
460
  node plugins/nordrelay/scripts/nordrelay.mjs doctor
377
461
  node plugins/nordrelay/scripts/nordrelay.mjs web
462
+ node plugins/nordrelay/scripts/nordrelay.mjs peer list
378
463
  ```
379
464
 
380
465
  NPM shortcuts:
@@ -422,6 +507,7 @@ The dashboard is a second NordRelay client next to Telegram. It can:
422
507
  - Control the active session model, reasoning/thinking, fast mode, and launch profile directly from the chat view.
423
508
  - Abort turns, hand sessions back to the native CLI, and inspect the active session.
424
509
  - Manage queued prompts with pause/resume, run, cancel, reorder buttons, and drag-and-drop prioritization.
510
+ - Inspect unified jobs across queued prompts, active turns, mirrored CLI work, agent updates, self-updates, and support-bundle exports.
425
511
  - Browse, preview, download, ZIP, and delete artifacts.
426
512
  - Inspect the activity timeline for WebUI and mirrored CLI turns.
427
513
  - Edit all supported runtime settings from tabbed Settings groups with option selects, validation feedback, and restart actions.
@@ -429,6 +515,7 @@ The dashboard is a second NordRelay client next to Telegram. It can:
429
515
  - Inspect a per-agent capability matrix showing model, reasoning, launch, fast mode, attachments, activity, usage, auth, login/logout, and handback support.
430
516
  - Check NordRelay and agent CLI versions, then start Codex, Pi, Hermes, OpenClaw, or Claude Code updates from outdated rows or installs from not-installed rows with live output, cancel, delete-log, and stdin response controls.
431
517
  - Build dashboard CSS and client JavaScript from modular source assets through esbuild, then serve them as authenticated static assets instead of inline HTML.
518
+ - Pair, test, enable/disable, and revoke NordRelay peers, then switch the dashboard target between the local instance and paired remote instances.
432
519
 
433
520
  Dashboard API endpoints are served under `/api/*`. Streaming uses `GET /api/events`.
434
521
 
@@ -461,6 +548,8 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
461
548
  - `/channels` shows available and planned messaging adapters.
462
549
  - `/agents` shows available and planned coding-agent adapters.
463
550
  - `/agent` selects the active agent for this Telegram context.
551
+ - `/peers` shows configured NordRelay peer instances.
552
+ - `/target local|<peer-id>` selects whether prompts for this chat run locally or on a paired peer.
464
553
  - `/link <code>` links the Telegram account to a NordRelay user.
465
554
  - `/whoami` shows the linked NordRelay user, groups, and permissions.
466
555
  - `/register_chat` enables the current Telegram group or forum chat for NordRelay when the linked user has user-management permission.
@@ -524,6 +613,19 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
524
613
  - `/update` updates through npm or git depending on the detected install type, then restarts only on success. Requires `updates.run`.
525
614
  - `/update agents`, `/update <agent>`, `/update install <agent>`, `/update jobs`, `/update log <id>`, `/update cancel <id>`, and `/update input <id> <text>` manage agent CLI update and install jobs. Requires `updates.run`.
526
615
 
616
+ ## Discord Commands
617
+
618
+ Discord supports slash commands and `/command` text messages for the shared command set. The primary differences from Telegram are:
619
+
620
+ - `/register_channel` replaces `/register_chat` for guild channels and threads.
621
+ - `/prompt <text>` is available for slash-command-only deployments where regular message content is disabled.
622
+ - `/link <code>` consumes Discord link codes created in the WebUI or with `nordrelay user discord-link-code`.
623
+ - `/queue`, `/sessions`, `/agent`, `/model`, `/reasoning`, `/launch`, `/artifacts`, `/update`, and `/stop` use Discord buttons where component limits allow.
624
+ - `/peers` and `/target local|<peer-id>` use the same paired-instance target selection as Telegram.
625
+ - `/artifacts latest`, `/artifacts zip latest`, `/artifacts images`, `/artifacts docs`, `/artifacts search <text>`, and `/artifacts delete <turn-id>` are available in Discord.
626
+ - Unsafe launch profiles require explicit confirmation with `/launch <profile-id> confirm`.
627
+ - Discord does not support Telegram reactions or Telegram webhook transport; typing, message edits, attachments, files, DMs, guild channels, and threads are supported.
628
+
527
629
  ## Command Examples
528
630
 
529
631
  Switching to an existing thread:
@@ -704,7 +806,8 @@ Voice transcription uses `OPENAI_API_KEY`, not `CODEX_API_KEY`.
704
806
 
705
807
  Telegram:
706
808
 
707
- - `TELEGRAM_BOT_TOKEN`: required BotFather token.
809
+ - `TELEGRAM_ENABLED`: starts the Telegram adapter. Defaults to `true`.
810
+ - `TELEGRAM_BOT_TOKEN`: BotFather token. Required for the Telegram adapter to start.
708
811
  - `TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS`: minimum interval for normal Telegram API sends. Defaults to `80`.
709
812
  - `TELEGRAM_EDIT_MIN_INTERVAL_MS`: minimum interval for Telegram message edits. Defaults to `1200`.
710
813
  - `TELEGRAM_TRANSPORT`: `polling` or `webhook`. Defaults to `polling`.
@@ -713,19 +816,46 @@ Telegram:
713
816
  - `TELEGRAM_WEBHOOK_PORT`: local bind port for webhook mode. Defaults to `8080`.
714
817
  - `TELEGRAM_WEBHOOK_PATH`: webhook request path. Defaults to `/telegram/webhook`.
715
818
  - `TELEGRAM_WEBHOOK_SECRET`: optional Telegram webhook secret token.
716
- - `TELEGRAM_CLI_MIRROR_MODE`: default CLI mirror mode: `off`, `status`, `final`, or `full`. Defaults to `status`.
717
- - `TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS`: minimum interval for mirrored CLI status edits. Defaults to `4000`.
718
- - `TELEGRAM_NOTIFY_MODE`: default notification mode: `off`, `minimal`, or `all`. Defaults to `minimal`.
719
- - `TELEGRAM_QUIET_HOURS`: optional quiet-hour range in `HH-HH` format, for example `22-7`.
819
+ - `NORDRELAY_CLI_MIRROR_MODE`: default CLI mirror mode for chat adapters: `off`, `status`, `final`, or `full`. Defaults to `status`.
820
+ - `NORDRELAY_CLI_MIRROR_MIN_UPDATE_MS`: default minimum interval for mirrored CLI status edits. Defaults to `4000`.
821
+ - `NORDRELAY_NOTIFY_MODE`: default notification mode for chat adapters: `off`, `minimal`, or `all`. Defaults to `minimal`.
822
+ - `NORDRELAY_QUIET_HOURS`: optional default quiet-hour range in `HH-HH` format, for example `22-7`; use `off` in a channel override to disable inherited quiet hours.
823
+ - `NORDRELAY_AUTO_SEND_ARTIFACTS`: default automatic artifact summaries/uploads for chat adapters. Defaults to `false`.
824
+ - `TELEGRAM_CLI_MIRROR_MODE`, `TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS`, `TELEGRAM_NOTIFY_MODE`, `TELEGRAM_QUIET_HOURS`, and `TELEGRAM_AUTO_SEND_ARTIFACTS`: optional Telegram-specific overrides.
720
825
  - `TELEGRAM_REDACT_PATTERNS`: comma-separated regular expressions for additional Telegram/log redaction.
721
826
 
827
+ Discord:
828
+
829
+ - `DISCORD_ENABLED`: starts the Discord adapter. Defaults to `false`.
830
+ - `DISCORD_BOT_TOKEN`: Discord bot token. Required for the Discord adapter to start.
831
+ - `DISCORD_CLIENT_ID`: Discord application/client id used for slash-command registration.
832
+ - `DISCORD_GUILD_IDS`: optional comma-separated guild ids for instant guild slash-command registration.
833
+ - `DISCORD_ALLOWED_GUILD_IDS`: optional guild allow-list before user/group permissions are checked.
834
+ - `DISCORD_ALLOWED_CHANNEL_IDS`: optional channel allow-list before user/group permissions are checked.
835
+ - `DISCORD_MESSAGE_CONTENT_ENABLED`: reads regular Discord text messages as prompts. Defaults to `true`.
836
+ - `DISCORD_COMMAND_MODE`: `slash`, `message`, or `both`. Defaults to `both`.
837
+ - `DISCORD_AUTO_REGISTER_COMMANDS`: registers slash commands on startup when `DISCORD_CLIENT_ID` is set. Defaults to `true`.
838
+ - `DISCORD_CLI_MIRROR_MODE`, `DISCORD_CLI_MIRROR_MIN_UPDATE_MS`, `DISCORD_NOTIFY_MODE`, `DISCORD_QUIET_HOURS`, and `DISCORD_AUTO_SEND_ARTIFACTS`: optional Discord-specific overrides for the channel-neutral defaults.
839
+
722
840
  User management:
723
841
 
724
- - Users, groups, Telegram identities, Telegram group-chat access, and web sessions are stored in `~/.nordrelay/users.json`.
725
- - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, and `link-code`.
842
+ - Users, groups, Telegram identities, Telegram group-chat access, Discord identities, Discord channel access, and web sessions are stored in `~/.nordrelay/users.json`.
843
+ - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, `link-discord`, `link-code`, and `discord-link-code`.
726
844
  - Built-in groups are `admin`, `user`, and `readonly`.
727
- - Group permissions include `inspect`, `sessions.read`, `sessions.write`, `prompt.send`, `prompt.abort`, `files.read`, `files.write`, `settings.read`, `settings.write`, `auth.manage`, `diagnostics.read`, `logs.read`, `logs.clear`, `queue.read`, `queue.write`, `updates.run`, `system.restart`, `users.read`, `users.write`, and `audit.read`.
728
- - Custom groups can also restrict access to specific agent ids, workspace roots, and Telegram chat ids.
845
+ - Group permissions include `inspect`, `sessions.read`, `sessions.write`, `prompt.send`, `prompt.abort`, `files.read`, `files.write`, `settings.read`, `settings.write`, `auth.manage`, `diagnostics.read`, `logs.read`, `logs.clear`, `queue.read`, `queue.write`, `updates.run`, `system.restart`, `users.read`, `users.write`, `audit.read`, `peers.read`, `peers.write`, and `peers.connect`.
846
+ - Custom groups can also restrict access to specific agent ids, workspace roots, Telegram chat ids, and Discord channel ids.
847
+
848
+ Peers:
849
+
850
+ - `NORDRELAY_PEER_ENABLED`: starts the dedicated peer API. Defaults to `false`.
851
+ - `NORDRELAY_PEER_NAME`: optional human-readable node name shown to paired instances.
852
+ - `NORDRELAY_PEER_HOST`: peer API bind host. Defaults to `127.0.0.1`.
853
+ - `NORDRELAY_PEER_PORT`: peer API port. Defaults to `31979`.
854
+ - `NORDRELAY_PEER_PUBLIC_URL`: optional URL other instances should use to reach this node.
855
+ - `NORDRELAY_PEER_TLS_ENABLED`: serves the peer API over HTTPS with an automatically generated local certificate. Defaults to `true`.
856
+ - `NORDRELAY_PEER_REQUIRE_TLS`: refuses plaintext peer serving on non-loopback hosts. Defaults to `true`.
857
+ - Peer identity, TLS certificate, peers, and invitations are stored under `~/.nordrelay/identity.json`, `~/.nordrelay/tls/`, and `~/.nordrelay/peers.json`.
858
+ - Peer invitations expire after at most 24 hours even if a longer lifetime is requested.
729
859
 
730
860
  Agent selection:
731
861
 
@@ -734,7 +864,7 @@ Agent selection:
734
864
  - `NORDRELAY_HERMES_ENABLED`: enables Hermes contexts through the Hermes API Server. Defaults to `false`.
735
865
  - `NORDRELAY_OPENCLAW_ENABLED`: enables OpenClaw contexts through the OpenClaw Gateway. Defaults to `false`.
736
866
  - `NORDRELAY_CLAUDE_CODE_ENABLED`: enables Claude Code contexts through the Claude Agent SDK. Defaults to `false`.
737
- - `NORDRELAY_DEFAULT_AGENT`: `codex`, `pi`, `hermes`, `openclaw`, or `claude-code`, used for new Telegram contexts. Defaults to the first enabled agent.
867
+ - `NORDRELAY_DEFAULT_AGENT`: `codex`, `pi`, `hermes`, `openclaw`, or `claude-code`, used for new chat contexts. Defaults to the first enabled agent.
738
868
  - `NORDRELAY_STATE_BACKEND`: `json` or `sqlite`. JSON is the default; SQLite requires `better-sqlite3`.
739
869
  - `NORDRELAY_AUDIT_MAX_EVENTS`: maximum audit events retained. Defaults to `1000`.
740
870
  - `NORDRELAY_SESSION_LOCK_TTL_MS`: session write-lock TTL. Defaults to `1800000`.
@@ -885,12 +1015,14 @@ Unsafe profiles are intentionally gated. Telegram asks for confirmation before a
885
1015
  - Link Telegram accounts only to active NordRelay users that should control agents remotely.
886
1016
  - Enable Telegram group/forum chats only when the whole chat context is trusted for the permissions granted to linked users.
887
1017
  - Review group permissions before granting `prompt.send`, `prompt.abort`, `files.write`, `settings.write`, `updates.run`, `system.restart`, or `users.write`.
1018
+ - Review peer scopes before granting `peers.write`, `peers.connect`, broad `prompt.send`, or unrestricted workspace roots to a paired instance.
888
1019
  - Treat `danger-full-access` as equivalent to shell access on the host.
889
1020
  - Treat uploaded files as untrusted input. They are staged inside the active workspace so the selected sandbox policy still matters.
890
1021
  - Keep `CODEX_API_KEY`, `HERMES_API_KEY`, `OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_GATEWAY_PASSWORD`, and `OPENAI_API_KEY` in `~/.nordrelay/nordrelay.env` or host secret management.
891
1022
  - In group chats, remember that any linked user with prompt permissions can prompt the selected agent in that chat context.
892
1023
  - Use `TOOL_VERBOSITY=summary` or `errors-only` when command output may include sensitive data.
893
1024
  - Review and unsafe launch profiles add a Telegram approve/deny gate before each turn starts.
1025
+ - Keep the peer API disabled unless needed. For internet use, expose it only through a firewall, VPN, or hardened reverse proxy; keep TLS enabled and revoke unused peers with `nordrelay peer revoke <peer-id>`.
894
1026
 
895
1027
  ## Troubleshooting
896
1028
 
@@ -20,6 +20,9 @@ export const ALL_PERMISSIONS = [
20
20
  "users.read",
21
21
  "users.write",
22
22
  "audit.read",
23
+ "peers.read",
24
+ "peers.write",
25
+ "peers.connect",
23
26
  ];
24
27
  export const ADMIN_GROUP_ID = "admin";
25
28
  export const USER_GROUP_ID = "user";
@@ -71,6 +74,8 @@ const COMMAND_PERMISSIONS = new Map([
71
74
  ["health", "inspect"],
72
75
  ["version", "inspect"],
73
76
  ["channels", "inspect"],
77
+ ["peers", "peers.read"],
78
+ ["target", "peers.connect"],
74
79
  ["agents", "inspect"],
75
80
  ["tasks", "inspect"],
76
81
  ["progress", "inspect"],
@@ -119,6 +124,7 @@ const COMMAND_PERMISSIONS = new Map([
119
124
  ["abort", "prompt.abort"],
120
125
  ["stop", "prompt.abort"],
121
126
  ["register_chat", "users.write"],
127
+ ["register_channel", "users.write"],
122
128
  ["chat_access", "users.write"],
123
129
  ["link", "inspect"],
124
130
  ]);
@@ -147,7 +153,7 @@ export function permissionForCallbackData(callbackData) {
147
153
  if (callbackData.startsWith("approval_") || callbackData.startsWith("codex_abort:") || callbackData.startsWith("agent_abort:")) {
148
154
  return "prompt.abort";
149
155
  }
150
- if (callbackData.startsWith("queue_")) {
156
+ if (callbackData.startsWith("queue_") || callbackData.startsWith("peer_queue_")) {
151
157
  return "queue.write";
152
158
  }
153
159
  if (callbackData.startsWith("artifact_delete")) {
@@ -0,0 +1,44 @@
1
+ export function activityCategoryForType(type) {
2
+ if (/^(prompt|cli_turn|voice|upload|attachment)/.test(type))
3
+ return "prompt";
4
+ if (/^(session|agent_switch|handback|model_|reasoning_|fast_|launch_)/.test(type))
5
+ return "session";
6
+ if (/^queue_/.test(type))
7
+ return "queue";
8
+ if (/^agent_(install|update)/.test(type))
9
+ return "agent-update";
10
+ if (/^(artifact|artifacts)/.test(type))
11
+ return "artifact";
12
+ if (/^(auth|login|logout)/.test(type))
13
+ return "auth";
14
+ if (/^(user_|group_|telegram_chat_|telegram_link|discord_channel_|discord_link|peer_|permission_|access_|lock_)/.test(type))
15
+ return "security";
16
+ if (/^(tool_|cli_tool)/.test(type))
17
+ return "tool";
18
+ return "system";
19
+ }
20
+ export function activityActorLabel(actor) {
21
+ if (!actor) {
22
+ return "system";
23
+ }
24
+ return actor.label || actor.username || actor.id || actor.channelUserId || actor.channel;
25
+ }
26
+ export function auditCategoryForAction(action) {
27
+ if (/^prompt_/.test(action))
28
+ return "prompt";
29
+ if (/^queue_/.test(action))
30
+ return "queue";
31
+ if (/^lock_/.test(action))
32
+ return "security";
33
+ if (/^auth_/.test(action))
34
+ return "auth";
35
+ if (/^(permission_|user_|group_|telegram_|discord_|peer_)/.test(action))
36
+ return "security";
37
+ if (/^(artifact|file)/.test(action))
38
+ return "artifact";
39
+ if (/^(model_|reasoning_|fast_|launch_|session_|handback)/.test(action))
40
+ return "session";
41
+ if (/^(tool_)/.test(action))
42
+ return "tool";
43
+ return "system";
44
+ }
package/dist/audit-log.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { auditCategoryForAction, } from "./activity-events.js";
2
3
  import { createDocumentStore } from "./state-backend.js";
3
4
  export class AuditLogStore {
4
5
  store;
@@ -18,6 +19,7 @@ export class AuditLogStore {
18
19
  id: randomUUID().replace(/-/g, "").slice(0, 12),
19
20
  timestamp: new Date().toISOString(),
20
21
  ...event,
22
+ category: event.category ?? auditCategoryForAction(event.action),
21
23
  channelId: event.channelId ?? "telegram",
22
24
  };
23
25
  payload.events.push(next);
@@ -27,8 +29,22 @@ export class AuditLogStore {
27
29
  this.store.write(payload);
28
30
  return next;
29
31
  }
30
- list(limit = 20) {
31
- return this.readPayload().events.slice(-Math.max(1, Math.min(200, limit))).reverse();
32
+ list(options = 20) {
33
+ const resolved = typeof options === "number" ? { limit: options } : options;
34
+ const limit = Math.max(1, Math.min(500, resolved.limit ?? 20));
35
+ const since = normalizeSince(resolved.since);
36
+ return this.readPayload().events
37
+ .filter((event) => !resolved.channelId || resolved.channelId === "all" || event.channelId === resolved.channelId)
38
+ .filter((event) => !resolved.status || resolved.status === "all" || event.status === resolved.status)
39
+ .filter((event) => !resolved.action || resolved.action === "all" || event.action === resolved.action)
40
+ .filter((event) => !resolved.category || resolved.category === "all" || (event.category ?? auditCategoryForAction(event.action)) === resolved.category)
41
+ .filter((event) => !resolved.agentId || resolved.agentId === "all" || event.agentId === resolved.agentId)
42
+ .filter((event) => !resolved.threadId || event.threadId === resolved.threadId)
43
+ .filter((event) => !resolved.workspace || event.workspace === resolved.workspace)
44
+ .filter((event) => !resolved.actor || auditActorMatches(event, resolved.actor))
45
+ .filter((event) => !since || Date.parse(event.timestamp) >= since)
46
+ .slice(-limit)
47
+ .reverse();
32
48
  }
33
49
  readPayload() {
34
50
  const payload = this.store.read();
@@ -41,6 +57,28 @@ export class AuditLogStore {
41
57
  };
42
58
  }
43
59
  }
60
+ function normalizeSince(value) {
61
+ if (value === undefined || value === null || value === "") {
62
+ return null;
63
+ }
64
+ const time = typeof value === "number" ? value : Date.parse(value);
65
+ return Number.isFinite(time) ? time : null;
66
+ }
67
+ function auditActorMatches(event, query) {
68
+ const needle = query.trim().toLowerCase();
69
+ if (!needle) {
70
+ return true;
71
+ }
72
+ return [
73
+ event.actor?.id,
74
+ event.actor?.label,
75
+ event.actor?.username,
76
+ event.actor?.channelUserId,
77
+ event.actor?.channel,
78
+ event.actorId,
79
+ event.actorRole,
80
+ ].some((value) => String(value ?? "").toLowerCase().includes(needle));
81
+ }
44
82
  function isAuditEvent(value) {
45
83
  if (!value || typeof value !== "object" || Array.isArray(value)) {
46
84
  return false;
@@ -117,6 +117,7 @@ function normalizePreferences(value) {
117
117
  voiceBackend: isVoiceBackendPreference(candidate.voiceBackend) ? candidate.voiceBackend : undefined,
118
118
  voiceLanguage: typeof candidate.voiceLanguage === "string" ? candidate.voiceLanguage : candidate.voiceLanguage === null ? null : undefined,
119
119
  voiceTranscribeOnly: typeof candidate.voiceTranscribeOnly === "boolean" ? candidate.voiceTranscribeOnly : undefined,
120
+ targetPeerId: typeof candidate.targetPeerId === "string" ? candidate.targetPeerId : candidate.targetPeerId === null ? null : undefined,
120
121
  });
121
122
  }
122
123
  function pruneEmptyPreferences(preferences) {