@nordbyte/nordrelay 0.4.1 → 0.5.1

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 (57) hide show
  1. package/.env.example +155 -64
  2. package/README.md +81 -65
  3. package/dist/access-control.js +126 -115
  4. package/dist/agent-updates.js +62 -9
  5. package/dist/bot-rendering.js +838 -0
  6. package/dist/bot-ui.js +1 -0
  7. package/dist/bot.js +342 -2498
  8. package/dist/channel-actions.js +8 -8
  9. package/dist/channel-runtime.js +89 -0
  10. package/dist/config-metadata.js +238 -0
  11. package/dist/config.js +0 -58
  12. package/dist/index.js +8 -0
  13. package/dist/operations.js +63 -9
  14. package/dist/relay-artifact-service.js +126 -0
  15. package/dist/relay-external-activity-monitor.js +216 -0
  16. package/dist/relay-queue-service.js +66 -0
  17. package/dist/relay-runtime-types.js +1 -0
  18. package/dist/relay-runtime.js +96 -354
  19. package/dist/settings-service.js +2 -117
  20. package/dist/support-bundle.js +205 -0
  21. package/dist/telegram-access-commands.js +123 -0
  22. package/dist/telegram-access-middleware.js +129 -0
  23. package/dist/telegram-agent-commands.js +212 -0
  24. package/dist/telegram-artifact-commands.js +139 -0
  25. package/dist/telegram-channel-runtime.js +132 -0
  26. package/dist/telegram-command-menu.js +55 -0
  27. package/dist/telegram-command-types.js +1 -0
  28. package/dist/telegram-diagnostics-command.js +102 -0
  29. package/dist/telegram-general-commands.js +52 -0
  30. package/dist/telegram-operational-commands.js +153 -0
  31. package/dist/telegram-output.js +216 -0
  32. package/dist/telegram-preference-commands.js +198 -0
  33. package/dist/telegram-queue-commands.js +278 -0
  34. package/dist/telegram-support-command.js +53 -0
  35. package/dist/telegram-update-commands.js +93 -0
  36. package/dist/user-management.js +708 -0
  37. package/dist/web-api-contract.js +104 -0
  38. package/dist/web-api-types.js +1 -0
  39. package/dist/web-dashboard-access-routes.js +163 -0
  40. package/dist/web-dashboard-artifact-routes.js +65 -0
  41. package/dist/web-dashboard-assets.js +35 -2
  42. package/dist/web-dashboard-http.js +143 -0
  43. package/dist/web-dashboard-pages.js +257 -0
  44. package/dist/web-dashboard-runtime-routes.js +92 -0
  45. package/dist/web-dashboard-session-routes.js +209 -0
  46. package/dist/web-dashboard-ui.js +14 -14
  47. package/dist/web-dashboard.js +330 -707
  48. package/dist/webui-assets/dashboard.css +989 -0
  49. package/dist/webui-assets/dashboard.js +1750 -0
  50. package/dist/zip-writer.js +83 -0
  51. package/package.json +13 -4
  52. package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
  53. package/plugins/nordrelay/commands/remote.md +1 -1
  54. package/plugins/nordrelay/scripts/nordrelay.mjs +227 -78
  55. package/plugins/nordrelay/skills/telegram-remote/SKILL.md +1 -1
  56. package/dist/web-dashboard-client.js +0 -275
  57. package/dist/web-dashboard-style.js +0 -9
package/README.md CHANGED
@@ -36,9 +36,10 @@ Session control:
36
36
  - `/status`, `/health`, and `/version` report connector runtime health from Telegram.
37
37
  - `/tasks` and `/progress` show the current turn status, queue length, active tool, elapsed time, and last error.
38
38
  - `/activity` shows a compact timeline of recent rollout events for the active thread, with filters and export.
39
- - `/diagnostics` reports redacted runtime, config, role, Telegram rate-limit, mirror, voice, session, queue, and progress details for admins.
39
+ - `/diagnostics` reports redacted runtime, config, user/group authorization, Telegram rate-limit, mirror, voice, session, queue, and progress details.
40
+ - `/support` exports a redacted diagnostics ZIP with config, health, versions, agent paths, recent logs, audit events, update jobs, state backend, and OS/Node/npm info.
40
41
  - `/lock`, `/unlock`, and `/locks` provide a team write-lock for shared sessions so one user can operate while others watch.
41
- - `/audit` shows recent prompt, queue, lock, and command audit events for admins.
42
+ - `/audit` shows recent prompt, queue, lock, command, authentication, permission-denied, user, group, Telegram-link, Telegram-chat, and web-session audit events for admins.
42
43
 
43
44
  Adapter architecture:
44
45
 
@@ -46,7 +47,7 @@ Adapter architecture:
46
47
  - `/channels` shows available and planned messaging adapters for Discord, WhatsApp, Slack, and Matrix.
47
48
  - Codex, Pi, Hermes, OpenClaw, and Claude Code are implemented as agent adapters.
48
49
  - `/agents` shows available/planned agent adapters and whether Codex, Pi, Hermes, OpenClaw, and Claude Code are enabled.
49
- - Shared command-action renderers keep channel-neutral responses for adapter lists, queues, artifacts, logs, and update jobs separate from Telegram-specific keyboards and delivery.
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.
50
51
 
51
52
  Codex runtime:
52
53
 
@@ -160,13 +161,17 @@ Telegram output:
160
161
 
161
162
  Authentication and safety:
162
163
 
163
- - Telegram access requires `TELEGRAM_ADMIN_USER_IDS` by default; a fresh install only accepts those admin user ids.
164
- - `TELEGRAM_ALLOWED_USER_IDS` can add non-admin operators.
165
- - `TELEGRAM_ALLOWED_CHAT_IDS` is supported for private chats, groups, and backward compatibility.
166
- - `TELEGRAM_ALLOW_ANY_CHAT=true` is an explicit unsafe override for temporary local setup only.
167
- - `TELEGRAM_ADMIN_USER_IDS` restricts admin-only commands such as `/logs`, `/restart`, and `/update`.
168
- - `TELEGRAM_READONLY_USER_IDS` can inspect status and sessions but cannot send prompts or run mutating commands by default.
169
- - `TELEGRAM_ROLE_POLICIES_JSON` can override role permissions for `admin`, `operator`, and `readonly`.
164
+ - 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.
167
+ - The last active admin cannot be disabled or demoted, and web sessions are revoked when passwords or group memberships change.
168
+ - Admins can review and revoke active WebUI sessions from the Users page.
169
+ - Telegram private chats require a linked active NordRelay user.
170
+ - Telegram group and forum chats must be registered before use; admins can run `/register_chat` in the chat or enable chats in the WebUI.
171
+ - `/whoami` shows the linked NordRelay account and groups.
172
+ - `/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.
170
175
  - `/auth` reports Codex authentication, Pi provider environment health, Hermes API Server reachability, OpenClaw Gateway reachability, or Claude Code CLI auth for the selected agent.
171
176
  - `/login` starts Telegram-managed CLI auth for Codex, Hermes, or Claude Code when enabled.
172
177
  - `/logout` signs out of CLI auth for Codex, Hermes, or Claude Code; Codex logout is disabled while `CODEX_API_KEY` is in use.
@@ -179,9 +184,9 @@ Operations:
179
184
 
180
185
  - Plugin command/skill starts, stops, restarts, and inspects the connector process.
181
186
  - Manual process commands support `start`, `stop`, `restart`, `status`, and `foreground`.
182
- - Telegram admin commands support `/logs`, `/diagnostics`, `/restart`, and `/update` for NordRelay and agent CLIs.
187
+ - Telegram admin commands support `/logs`, `/diagnostics`, `/support`, `/restart`, and `/update` for NordRelay and agent CLIs.
183
188
  - `/update` detects 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.
184
- - `/update agents`, `/update <agent>`, `/update jobs`, `/update log <id>`, `/update cancel <id>`, and `/update input <id> <text>` manage Codex, Pi, Hermes, OpenClaw, and Claude Code updater jobs from Telegram.
189
+ - `/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.
185
190
  - `/logs` renders redacted connector, NordRelay update, and agent update logs with local-time timestamps, levels, file path, last-modified time, and highlighted warnings/errors.
186
191
  - Logs can be emitted as timestamped plain text or JSON records with `CONNECTOR_LOG_FORMAT`.
187
192
  - Telegram sends/edits/documents are routed through a rate-limit queue that honors Telegram retry-after responses.
@@ -191,12 +196,12 @@ Operations:
191
196
  - `nordrelay init` creates a private runtime config, `nordrelay doctor` validates host prerequisites, and `nordrelay web` starts the connector plus a full local WebUI dashboard.
192
197
  - The WebUI has responsive header/sidebar/footer navigation, live chat streaming, session controls, queue/artifact/log/diagnostic views, and settings management.
193
198
  - The WebUI supports light and dark themes, tabbed settings groups, paginated session browsing, and chat uploads for images, documents, and audio transcription.
194
- - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, and diagnostics.
195
- - Binding the dashboard to `0.0.0.0` is refused unless `NORDRELAY_DASHBOARD_TOKEN` or `NORDRELAY_DASHBOARD_USER` plus `NORDRELAY_DASHBOARD_PASSWORD` is configured.
199
+ - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, diagnostics, and redacted diagnostics bundle export.
200
+ - The dashboard can bind to `127.0.0.1` or `0.0.0.0`; user login and session cookies are mandatory in both modes.
196
201
  - Telegram can run with long polling or an HTTP webhook via `TELEGRAM_TRANSPORT=webhook`.
197
202
  - Version freshness checks are cached with `NORDRELAY_VERSION_CACHE_TTL_MS` to keep `/version` responsive.
198
- - CI includes typecheck, tests, package dry run, npm audit, and a separate secret-scan workflow.
199
- - `npm run dev`, `npm run build`, `npm run check`, `npm test`, `npm start`, `npm stop`, and `npm run status` are available.
203
+ - CI runs on Ubuntu, Windows, and macOS with typecheck, Vitest, Playwright WebUI browser tests, package dry run, npm audit, and a separate secret-scan workflow.
204
+ - `npm run dev`, `npm run build`, `npm run check`, `npm test`, `npm run test:e2e`, `npm start`, `npm stop`, and `npm run status` are available.
200
205
  - Dockerfile and `docker-compose.yml` are included for containerized operation.
201
206
  - A `launchd/start.sh` helper is included for host-managed startup.
202
207
 
@@ -207,6 +212,7 @@ Recommended npm setup:
207
212
  ```bash
208
213
  npm install -g @nordbyte/nordrelay
209
214
  nordrelay init
215
+ nordrelay user list
210
216
  nordrelay doctor
211
217
  nordrelay start
212
218
  ```
@@ -216,9 +222,16 @@ npm is the fastest install path and is the recommended default for normal use. `
216
222
  Non-interactive setup is also supported:
217
223
 
218
224
  ```bash
219
- nordrelay init --token 123456789:replace-me --admin-id 123456789
225
+ nordrelay init \
226
+ --token 123456789:replace-me \
227
+ --admin-email you@example.com \
228
+ --admin-name "Your Name" \
229
+ --admin-password "replace-with-a-long-password" \
230
+ --telegram-user-id 123456789
220
231
  ```
221
232
 
233
+ `--telegram-user-id` is optional, but linking the first admin during setup is the fastest way to use Telegram immediately.
234
+
222
235
  Source checkout setup:
223
236
 
224
237
  Install dependencies and build the runtime:
@@ -237,14 +250,13 @@ Create the Telegram bot:
237
250
  2. Run `/newbot`.
238
251
  3. Choose a display name and bot username.
239
252
  4. Copy the bot token into `TELEGRAM_BOT_TOKEN` in `~/.nordrelay/nordrelay.env`.
240
- 5. Find your Telegram user id with a trusted id helper bot, for example `@userinfobot`, or from Telegram API tooling.
241
- 6. Put your user id into `TELEGRAM_ADMIN_USER_IDS`.
253
+ 5. Create the first admin user with `nordrelay init` or `nordrelay user create-admin`.
254
+ 6. Link Telegram from the WebUI, with `nordrelay user link-telegram`, or by creating a link code and sending `/link <code>` to the bot.
242
255
 
243
256
  Minimal private-bot `~/.nordrelay/nordrelay.env`:
244
257
 
245
258
  ```dotenv
246
259
  TELEGRAM_BOT_TOKEN=123456789:replace-me
247
- TELEGRAM_ADMIN_USER_IDS=123456789
248
260
  NORDRELAY_CODEX_ENABLED=true
249
261
  NORDRELAY_PI_ENABLED=false
250
262
  NORDRELAY_HERMES_ENABLED=false
@@ -255,13 +267,14 @@ CODEX_SANDBOX_MODE=workspace-write
255
267
  CODEX_APPROVAL_POLICY=never
256
268
  ```
257
269
 
258
- Optional non-admin operators can be added with:
259
-
260
- ```dotenv
261
- TELEGRAM_ALLOWED_USER_IDS=234567890,345678901
262
- ```
270
+ User and Telegram access management:
263
271
 
264
- Do not use `TELEGRAM_ALLOW_ANY_CHAT=true` for normal coding sessions. It makes the bot reachable from any Telegram chat that can message it.
272
+ - `nordrelay init` creates the first admin user and writes `~/.nordrelay/users.json`.
273
+ - `nordrelay user create-admin --email you@example.com --name "Your Name"` creates another admin.
274
+ - `nordrelay user create --email dev@example.com --name "Dev" --group user` creates a normal user.
275
+ - `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.
265
278
 
266
279
  Codex authentication:
267
280
 
@@ -334,7 +347,7 @@ Where Codex exposes namespaced plugin commands, this also works:
334
347
  /nordrelay:remote
335
348
  ```
336
349
 
337
- The old unnamespaced `/remote` command is no longer required and current Codex TUI builds do not register it as a top-level plugin slash command. The command is only a process-manager shortcut; Telegram contains the actual controls.
350
+ The command is only a process-manager shortcut; Telegram contains the actual controls.
338
351
 
339
352
  Manual process commands:
340
353
 
@@ -357,6 +370,7 @@ node plugins/nordrelay/scripts/nordrelay.mjs status
357
370
  node plugins/nordrelay/scripts/nordrelay.mjs restart
358
371
  node plugins/nordrelay/scripts/nordrelay.mjs stop
359
372
  node plugins/nordrelay/scripts/nordrelay.mjs foreground
373
+ node plugins/nordrelay/scripts/nordrelay.mjs user list
360
374
  node plugins/nordrelay/scripts/nordrelay.mjs doctor
361
375
  node plugins/nordrelay/scripts/nordrelay.mjs web
362
376
  ```
@@ -411,8 +425,8 @@ The dashboard is a second NordRelay client next to Telegram. It can:
411
425
  - Edit all supported runtime settings from tabbed Settings groups with option selects, validation feedback, and restart actions.
412
426
  - View filtered connector/update/agent-update logs, structured diagnostics, enabled channels, and agent adapters.
413
427
  - Inspect a per-agent capability matrix showing model, reasoning, launch, fast mode, attachments, activity, usage, auth, login/logout, and handback support.
414
- - Check NordRelay and agent CLI versions, then start Codex, Pi, Hermes, OpenClaw, or Claude Code updates from outdated version rows with live output, cancel, full-log, and stdin response controls for interactive updaters.
415
- - Load dashboard CSS and client JavaScript as authenticated static assets instead of inline HTML, keeping the server shell, style, and browser client modules separate.
428
+ - 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.
429
+ - Build dashboard CSS and client JavaScript from modular source assets through esbuild, then serve them as authenticated static assets instead of inline HTML.
416
430
 
417
431
  Dashboard API endpoints are served under `/api/*`. Streaming uses `GET /api/events`.
418
432
 
@@ -421,13 +435,9 @@ Dashboard auth:
421
435
  ```dotenv
422
436
  NORDRELAY_DASHBOARD_HOST=127.0.0.1
423
437
  NORDRELAY_DASHBOARD_PORT=31878
424
- NORDRELAY_DASHBOARD_TOKEN=replace-with-random-token
425
- # or:
426
- NORDRELAY_DASHBOARD_USER=admin
427
- NORDRELAY_DASHBOARD_PASSWORD=replace-with-random-password
428
438
  ```
429
439
 
430
- When `NORDRELAY_DASHBOARD_HOST=0.0.0.0`, NordRelay refuses to start the dashboard unless token or basic auth is configured. Login cookies use `SameSite=Strict`, and every dashboard route, API endpoint, SSE stream, artifact download, and health endpoint requires the same auth.
440
+ The dashboard always requires NordRelay email/password login. Login cookies use `SameSite=Strict`, and every dashboard route, API endpoint, SSE stream, artifact download, and health endpoint requires an authenticated active user with the matching permission.
431
441
 
432
442
  Webhook mode:
433
443
 
@@ -440,7 +450,7 @@ TELEGRAM_WEBHOOK_PATH=/telegram/webhook
440
450
  TELEGRAM_WEBHOOK_SECRET=replace-with-random-secret
441
451
  ```
442
452
 
443
- Run NordRelay behind your reverse proxy so the public URL forwards to `http://127.0.0.1:8080/telegram/webhook`. `GET /healthz` returns a simple health check.
453
+ Run NordRelay behind your reverse proxy so the public URL forwards to `http://127.0.0.1:8080/telegram/webhook`. Dashboard health checks are available to authenticated WebUI sessions through `/healthz` and `/api/health`.
444
454
 
445
455
  ## Telegram Commands
446
456
 
@@ -449,6 +459,9 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
449
459
  - `/channels` shows available and planned messaging adapters.
450
460
  - `/agents` shows available and planned coding-agent adapters.
451
461
  - `/agent` selects the active agent for this Telegram context.
462
+ - `/link <code>` links the Telegram account to a NordRelay user.
463
+ - `/whoami` shows the linked NordRelay user, groups, and permissions.
464
+ - `/register_chat` enables the current Telegram group or forum chat for NordRelay when the linked user has user-management permission.
452
465
  - `/new` starts a new thread. If the selected agent knows multiple workspaces, Telegram shows a workspace picker.
453
466
  - `/session` shows current thread details.
454
467
  - `/sessions` opens a paginated recent-session picker.
@@ -472,7 +485,7 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
472
485
  - `/cancel <queue-id>` removes one queued prompt; the queue id is the short code shown in messages such as `Queued prompt 332kmt`.
473
486
  - `/clearqueue` clears queued prompts for this Telegram context.
474
487
  - `/activity [all|tools|errors|user|agent|tasks] [limit] [since 1h] [export]` shows or exports rollout activity for the active thread.
475
- - `/audit [limit]` shows recent audit events. Admin only.
488
+ - `/audit [limit]` shows recent audit events. Requires `audit.read`.
476
489
  - `/lock` locks writes for this Telegram session to the current user.
477
490
  - `/unlock` releases the current session write lock.
478
491
  - `/locks` lists active write locks.
@@ -499,14 +512,15 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
499
512
  - `/status` reports connector runtime status.
500
513
  - `/health` reports runtime health, auth, PIDs, Codex CLI, Pi CLI, Hermes CLI, OpenClaw CLI, Claude Code CLI, and state DB.
501
514
  - `/version` reports connector, Codex CLI, Pi CLI, Hermes CLI, OpenClaw CLI, and Claude Code CLI paths plus installed/latest NordRelay, Codex, Pi, Hermes, OpenClaw, and Claude Code versions with status icons.
502
- - `/logs [lines]` shows a redacted, timestamped connector log tail. Admin only.
503
- - `/logs update [lines]` shows the self-update log. Admin only.
504
- - `/logs agent [lines]` shows the aggregate agent updater log. Admin only.
505
- - `/logs all [lines]` shows connector, self-update, and agent update logs together. Admin only.
506
- - `/diagnostics` shows redacted connector diagnostics. Admin only.
507
- - `/restart` restarts the connector process. Admin only.
508
- - `/update` updates through npm or git depending on the detected install type, then restarts only on success. Admin only.
509
- - `/update agents`, `/update <agent>`, `/update jobs`, `/update log <id>`, `/update cancel <id>`, and `/update input <id> <text>` manage agent CLI update jobs. Admin only.
515
+ - `/logs [lines]` shows a redacted, timestamped connector log tail. Requires `logs.read`.
516
+ - `/logs update [lines]` shows the self-update log. Requires `logs.read`.
517
+ - `/logs agent [lines]` shows the aggregate agent updater log. Requires `logs.read`.
518
+ - `/logs all [lines]` shows connector, self-update, and agent update logs together. Requires `logs.read`.
519
+ - `/diagnostics` shows redacted connector diagnostics. Requires `diagnostics.read`.
520
+ - `/support` exports a redacted diagnostics ZIP. Requires `diagnostics.read`.
521
+ - `/restart` restarts the connector process. Requires `system.restart`.
522
+ - `/update` updates through npm or git depending on the detected install type, then restarts only on success. Requires `updates.run`.
523
+ - `/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`.
510
524
 
511
525
  ## Command Examples
512
526
 
@@ -689,12 +703,6 @@ Voice transcription uses `OPENAI_API_KEY`, not `CODEX_API_KEY`.
689
703
  Telegram:
690
704
 
691
705
  - `TELEGRAM_BOT_TOKEN`: required BotFather token.
692
- - `TELEGRAM_ADMIN_USER_IDS`: required comma-separated Telegram user ids allowed to use admin commands. Admin ids are automatically allowed to use the bot.
693
- - `TELEGRAM_ALLOWED_USER_IDS`: optional comma-separated non-admin Telegram user ids allowed to use the bot.
694
- - `TELEGRAM_READONLY_USER_IDS`: comma-separated Telegram user ids that can inspect status and sessions but cannot run prompts or mutating commands.
695
- - `TELEGRAM_ALLOWED_CHAT_IDS`: comma-separated chat ids allowed to use the bot. Group ids may be negative.
696
- - `TELEGRAM_ALLOW_ANY_CHAT`: allows all chats when `true`. Keep `false` unless you intentionally want an open bot.
697
- - `TELEGRAM_ROLE_POLICIES_JSON`: optional JSON object mapping roles to permissions. Permissions are `inspect`, `sessions`, `prompt`, `files`, `settings`, `auth`, and `admin`.
698
706
  - `TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS`: minimum interval for normal Telegram API sends. Defaults to `80`.
699
707
  - `TELEGRAM_EDIT_MIN_INTERVAL_MS`: minimum interval for Telegram message edits. Defaults to `1200`.
700
708
  - `TELEGRAM_TRANSPORT`: `polling` or `webhook`. Defaults to `polling`.
@@ -709,11 +717,13 @@ Telegram:
709
717
  - `TELEGRAM_QUIET_HOURS`: optional quiet-hour range in `HH-HH` format, for example `22-7`.
710
718
  - `TELEGRAM_REDACT_PATTERNS`: comma-separated regular expressions for additional Telegram/log redaction.
711
719
 
712
- Role policy example:
720
+ User management:
713
721
 
714
- ```dotenv
715
- TELEGRAM_ROLE_POLICIES_JSON={"readonly":["inspect","sessions"],"operator":["inspect","sessions","prompt","files"],"admin":"*"}
716
- ```
722
+ - Users, groups, Telegram identities, Telegram group-chat access, and web sessions are stored in `~/.nordrelay/users.json`.
723
+ - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, and `link-code`.
724
+ - Built-in groups are `admin`, `user`, and `readonly`.
725
+ - 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`.
726
+ - Custom groups can also restrict access to specific agent ids, workspace roots, and Telegram chat ids.
717
727
 
718
728
  Agent selection:
719
729
 
@@ -732,9 +742,6 @@ Dashboard:
732
742
 
733
743
  - `NORDRELAY_DASHBOARD_HOST`: dashboard bind host. Defaults to `127.0.0.1`.
734
744
  - `NORDRELAY_DASHBOARD_PORT`: dashboard bind port. Defaults to `31878`.
735
- - `NORDRELAY_DASHBOARD_TOKEN`: optional dashboard bearer/login token. Required when binding to `0.0.0.0` unless basic auth is configured.
736
- - `NORDRELAY_DASHBOARD_USER`: optional dashboard basic-auth user.
737
- - `NORDRELAY_DASHBOARD_PASSWORD`: optional dashboard basic-auth password. Required with `NORDRELAY_DASHBOARD_USER`.
738
745
  - `NORDRELAY_ENV_FILE`: optional explicit env-file path used by the wrapper and edited by the dashboard settings page. Defaults to `~/.nordrelay/nordrelay.env`.
739
746
 
740
747
  Codex:
@@ -831,7 +838,7 @@ NordRelay wrapper:
831
838
  - `NORDRELAY_HOME`: config/state/log directory override. Defaults to `~/.nordrelay`.
832
839
  - `NORDRELAY_SOURCE_ROOT`: runtime source root override. Useful when the plugin is launched from Codex cache.
833
840
  - `NORDRELAY_UPDATE_METHOD`: optional `auto`, `npm`, or `git` self-update method override. Auto uses git when the runtime root has a `.git` directory and npm otherwise.
834
- - Agent updates from the dashboard and Telegram use each agent's native updater where possible: `codex update`, `pi update pi`, `hermes update --yes`, `openclaw update --yes`, and `claude update`.
841
+ - Agent updates from the dashboard and Telegram use each agent's native updater where possible: `codex update`, `pi update pi`, `hermes update --yes`, `openclaw update --yes`, and `claude update`. Not-installed agents can be installed from the dashboard or with `/update install <agent>` using npm global installs.
835
842
  - `NORDRELAY_KEEP_PENDING_UPDATES`: set true to avoid dropping pending Telegram updates on start.
836
843
  - `NORDRELAY_FORWARD_TOOL_OUTPUT`: backward-compatible alias that sets `TOOL_VERBOSITY=all` when `TOOL_VERBOSITY` is unset.
837
844
  - `NORDRELAY_STATE_FILE`: internal state-file path passed by the wrapper.
@@ -871,14 +878,14 @@ Unsafe profiles are intentionally gated. Telegram asks for confirmation before a
871
878
 
872
879
  ## Security Notes
873
880
 
874
- - Always set `TELEGRAM_ADMIN_USER_IDS`; a fresh install refuses to start without at least one admin user id.
875
- - Prefer `TELEGRAM_ADMIN_USER_IDS` and `TELEGRAM_ALLOWED_USER_IDS` over `TELEGRAM_ALLOWED_CHAT_IDS` for private bots.
876
- - Use `TELEGRAM_ALLOWED_CHAT_IDS` for groups or forum topics only when you trust the entire chat.
877
- - Do not leave `TELEGRAM_ALLOW_ANY_CHAT=true` enabled after setup.
881
+ - Create the first admin user during setup and keep that account protected with a strong password.
882
+ - Link Telegram accounts only to active NordRelay users that should control agents remotely.
883
+ - Enable Telegram group/forum chats only when the whole chat context is trusted for the permissions granted to linked users.
884
+ - Review group permissions before granting `prompt.send`, `prompt.abort`, `files.write`, `settings.write`, `updates.run`, `system.restart`, or `users.write`.
878
885
  - Treat `danger-full-access` as equivalent to shell access on the host.
879
886
  - Treat uploaded files as untrusted input. They are staged inside the active workspace so the selected sandbox policy still matters.
880
887
  - 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.
881
- - In group chats, remember that any allowed user can prompt the selected agent in that chat context.
888
+ - In group chats, remember that any linked user with prompt permissions can prompt the selected agent in that chat context.
882
889
  - Use `TOOL_VERBOSITY=summary` or `errors-only` when command output may include sensitive data.
883
890
  - Review and unsafe launch profiles add a Telegram approve/deny gate before each turn starts.
884
891
 
@@ -1035,19 +1042,28 @@ npm run build
1035
1042
  - `plugins/nordrelay/`: Codex plugin bundle with manifest, skill, command, icon, and process wrapper.
1036
1043
  - `plugins/nordrelay/scripts/nordrelay.mjs`: process manager for `start`, `stop`, `restart`, `status`, and `foreground`.
1037
1044
  - `src/index.ts`: runtime entrypoint, config load, auth check, state-file writes, polling lifecycle, shutdown.
1038
- - `src/bot.ts`: Telegram command handlers, callbacks, message streaming, file/photo/voice handling, artifacts, and error handling.
1045
+ - `src/bot.ts`: Telegram prompt/session runtime, streaming, file/photo/voice handling, artifacts, and error handling.
1046
+ - `src/telegram-general-commands.ts`, `src/telegram-agent-commands.ts`, `src/telegram-preference-commands.ts`, `src/telegram-access-commands.ts`, `src/telegram-diagnostics-command.ts`, `src/telegram-update-commands.ts`, `src/telegram-support-command.ts`, and `src/telegram-command-menu.ts`: focused Telegram command groups for start/help/adapters, agent/auth controls, per-chat preferences, access linking, diagnostics/log/version commands, update jobs, diagnostics bundle export, and command menu registration.
1047
+ - `src/channel-adapter.ts`, `src/channel-runtime.ts`, and `src/channel-actions.ts`: channel descriptors, generic command routing, outbound delivery contracts, and channel-neutral command responses.
1048
+ - `src/config-metadata.ts`: shared setting metadata used by the WebUI settings page and generated `.env.example`.
1049
+ - `src/support-bundle.ts` and `src/zip-writer.ts`: redacted diagnostics bundle creation with a dependency-free ZIP writer.
1050
+ - `src/relay-queue-service.ts`, `src/relay-artifact-service.ts`, and `src/relay-external-activity-monitor.ts`: Web runtime queue operations, artifact preview/export/persistence, and external CLI activity mirroring.
1051
+ - `src/relay-runtime-types.ts`: shared Runtime/WebUI DTO types used by runtime, API contracts, and dashboard code.
1052
+ - `src/web-dashboard-http.ts`, `src/web-dashboard-pages.ts`, and `src/web-dashboard-runtime-routes.ts`: dashboard HTTP helpers, HTML shell rendering, and operational runtime API routes.
1053
+ - `src/webui/`: focused WebUI source assets for core runtime state/API helpers, overview rendering, live events, chat/session workflows, admin pages, and CSS sections.
1039
1054
  - `src/bot-preferences.ts`: per-context mirror, notification, quiet-hour, and voice preference persistence.
1040
1055
  - `src/telegram-rate-limit.ts`: centralized Telegram API send/edit/document rate limiting and retry-after tracking.
1041
1056
  - `src/persistence.ts`: atomic JSON/text writes with backup recovery.
1042
1057
  - `src/redaction.ts`: common secret redaction and custom redaction pattern support.
1043
1058
  - `src/workspace-policy.ts`: workspace allow/warn root evaluation.
1044
- - `src/access-control.ts`: Telegram role permissions and command/callback permission mapping.
1059
+ - `src/access-control.ts`: user/group permission definitions and command/callback/WebUI permission mapping.
1045
1060
  - `src/codex-session.ts`: Codex SDK service for new/resumed threads, streaming events, abort, model, reasoning, launch profiles, and handback.
1046
1061
  - `src/pi-session.ts`: Pi RPC service for JSONL RPC sessions, streaming events, abort, model, thinking, launch profiles, and handback.
1047
1062
  - `src/hermes-session.ts`: Hermes API Server service for streamed runs, stop, model, reasoning, launch profiles, attachments, and handback.
1048
1063
  - `src/openclaw-session.ts`: OpenClaw Gateway service for streamed runs, cancel, model, thinking, launch profiles, attachments, and handback.
1049
1064
  - `src/claude-code-session.ts`: Claude Agent SDK service for streamed runs, abort, model, effort, launch profiles, attachments, and handback.
1050
1065
  - `src/session-registry.ts`: per-chat/topic session registry and persisted context metadata.
1066
+ - `test/agent-adapter-contract.test.ts`: shared adapter contract coverage for descriptors, capability flags, reasoning options, launch profiles, and `AgentSessionService` method parity.
1051
1067
  - `src/session-format.ts`: compact Telegram rendering for session details, token usage, and limits.
1052
1068
  - `src/codex-state.ts`: reader for Codex `~/.codex/state_*.sqlite` thread, workspace, model, reasoning, sandbox, and approval metadata.
1053
1069
  - `src/pi-state.ts`: reader for Pi session JSONL files, activity timelines, diagnostics, and external busy detection.
@@ -1,11 +1,68 @@
1
- const ALL_PERMISSIONS = [
1
+ import { permissionForWebRequestFromContract } from "./web-api-contract.js";
2
+ export const ALL_PERMISSIONS = [
2
3
  "inspect",
3
- "sessions",
4
- "prompt",
5
- "files",
6
- "settings",
7
- "auth",
8
- "admin",
4
+ "sessions.read",
5
+ "sessions.write",
6
+ "prompt.send",
7
+ "prompt.abort",
8
+ "files.read",
9
+ "files.write",
10
+ "settings.read",
11
+ "settings.write",
12
+ "auth.manage",
13
+ "diagnostics.read",
14
+ "logs.read",
15
+ "logs.clear",
16
+ "queue.read",
17
+ "queue.write",
18
+ "updates.run",
19
+ "system.restart",
20
+ "users.read",
21
+ "users.write",
22
+ "audit.read",
23
+ ];
24
+ export const ADMIN_GROUP_ID = "admin";
25
+ export const USER_GROUP_ID = "user";
26
+ export const READONLY_GROUP_ID = "readonly";
27
+ export const BUILTIN_GROUPS = [
28
+ {
29
+ id: ADMIN_GROUP_ID,
30
+ name: "Admin",
31
+ description: "Full access to every NordRelay feature, user management, updates, and system controls.",
32
+ permissions: ALL_PERMISSIONS,
33
+ system: true,
34
+ },
35
+ {
36
+ id: USER_GROUP_ID,
37
+ name: "User",
38
+ description: "Normal read/write use of agents, sessions, prompts, files, and personal agent auth.",
39
+ permissions: [
40
+ "inspect",
41
+ "sessions.read",
42
+ "sessions.write",
43
+ "prompt.send",
44
+ "prompt.abort",
45
+ "files.read",
46
+ "files.write",
47
+ "settings.read",
48
+ "auth.manage",
49
+ "queue.read",
50
+ "queue.write",
51
+ ],
52
+ system: true,
53
+ },
54
+ {
55
+ id: READONLY_GROUP_ID,
56
+ name: "Read Only",
57
+ description: "Read-only access to status, sessions, activity, and artifacts.",
58
+ permissions: [
59
+ "inspect",
60
+ "sessions.read",
61
+ "files.read",
62
+ "settings.read",
63
+ ],
64
+ system: true,
65
+ },
9
66
  ];
10
67
  const COMMAND_PERMISSIONS = new Map([
11
68
  ["start", "inspect"],
@@ -15,141 +72,95 @@ const COMMAND_PERMISSIONS = new Map([
15
72
  ["version", "inspect"],
16
73
  ["channels", "inspect"],
17
74
  ["agents", "inspect"],
18
- ["diagnostics", "admin"],
19
75
  ["tasks", "inspect"],
20
76
  ["progress", "inspect"],
21
- ["activity", "inspect"],
22
- ["audit", "admin"],
23
- ["mirror", "settings"],
24
- ["notify", "settings"],
25
- ["workspaces", "sessions"],
26
- ["voice", "inspect"],
27
- ["agent", "settings"],
28
- ["session", "sessions"],
29
- ["sessions", "sessions"],
30
- ["switch", "sessions"],
31
- ["pinned", "sessions"],
32
- ["pin", "sessions"],
33
- ["unpin", "sessions"],
34
- ["attach", "sessions"],
35
- ["handback", "sessions"],
36
- ["new", "sessions"],
37
- ["sync", "sessions"],
38
- ["lock", "sessions"],
39
- ["unlock", "sessions"],
40
- ["locks", "sessions"],
41
- ["queue", "inspect"],
42
- ["cancel", "prompt"],
43
- ["clearqueue", "prompt"],
44
- ["retry", "prompt"],
45
- ["abort", "prompt"],
46
- ["stop", "prompt"],
47
- ["artifacts", "files"],
48
- ["launch", "settings"],
49
- ["launch_profiles", "settings"],
50
- ["launch-profiles", "settings"],
51
- ["fast", "settings"],
52
- ["model", "settings"],
53
- ["reasoning", "settings"],
54
- ["effort", "settings"],
77
+ ["activity", "sessions.read"],
55
78
  ["auth", "inspect"],
56
- ["login", "auth"],
57
- ["logout", "auth"],
58
- ["logs", "admin"],
59
- ["restart", "admin"],
60
- ["update", "admin"],
79
+ ["voice", "inspect"],
80
+ ["whoami", "inspect"],
81
+ ["diagnostics", "diagnostics.read"],
82
+ ["support", "diagnostics.read"],
83
+ ["diagnostics_bundle", "diagnostics.read"],
84
+ ["logs", "logs.read"],
85
+ ["audit", "audit.read"],
86
+ ["restart", "system.restart"],
87
+ ["update", "updates.run"],
88
+ ["workspaces", "sessions.read"],
89
+ ["session", "sessions.read"],
90
+ ["sessions", "sessions.read"],
91
+ ["pinned", "sessions.read"],
92
+ ["locks", "sessions.read"],
93
+ ["queue", "queue.read"],
94
+ ["artifacts", "files.read"],
95
+ ["agent", "settings.write"],
96
+ ["mirror", "settings.write"],
97
+ ["notify", "settings.write"],
98
+ ["launch", "settings.write"],
99
+ ["launch_profiles", "settings.write"],
100
+ ["launch-profiles", "settings.write"],
101
+ ["fast", "settings.write"],
102
+ ["model", "settings.write"],
103
+ ["reasoning", "settings.write"],
104
+ ["effort", "settings.write"],
105
+ ["login", "auth.manage"],
106
+ ["logout", "auth.manage"],
107
+ ["new", "sessions.write"],
108
+ ["switch", "sessions.write"],
109
+ ["attach", "sessions.write"],
110
+ ["handback", "sessions.write"],
111
+ ["sync", "sessions.write"],
112
+ ["pin", "sessions.write"],
113
+ ["unpin", "sessions.write"],
114
+ ["lock", "sessions.write"],
115
+ ["unlock", "sessions.write"],
116
+ ["retry", "prompt.send"],
117
+ ["clearqueue", "queue.write"],
118
+ ["cancel", "queue.write"],
119
+ ["abort", "prompt.abort"],
120
+ ["stop", "prompt.abort"],
121
+ ["register_chat", "users.write"],
122
+ ["chat_access", "users.write"],
123
+ ["link", "inspect"],
61
124
  ]);
62
- export function createDefaultRolePolicies() {
63
- return {
64
- admin: new Set(ALL_PERMISSIONS),
65
- operator: new Set(["inspect", "sessions", "prompt", "files", "settings", "auth"]),
66
- readonly: new Set(["inspect", "sessions"]),
67
- };
68
- }
69
- export function parseRolePoliciesJson(raw) {
70
- const policies = createDefaultRolePolicies();
71
- if (!raw) {
72
- return policies;
73
- }
74
- let parsed;
75
- try {
76
- parsed = JSON.parse(raw);
77
- }
78
- catch (error) {
79
- throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON: ${error instanceof Error ? error.message : String(error)}`);
80
- }
81
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
82
- throw new Error("TELEGRAM_ROLE_POLICIES_JSON must be an object keyed by role");
83
- }
84
- for (const [role, rawPermissions] of Object.entries(parsed)) {
85
- if (!isTelegramRole(role)) {
86
- throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON role: ${role}`);
87
- }
88
- policies[role] = parsePermissionList(rawPermissions, role);
89
- }
90
- if (!policies.admin.has("admin")) {
91
- policies.admin.add("admin");
92
- }
93
- return policies;
94
- }
95
- export function hasTelegramPermission(policies, role, permission) {
96
- return policies[role].has(permission) || policies[role].has("admin");
97
- }
98
125
  export function permissionForCommand(command) {
99
126
  if (!command) {
100
- return "inspect";
127
+ return null;
101
128
  }
102
- return COMMAND_PERMISSIONS.get(command.toLowerCase()) ?? "inspect";
129
+ return COMMAND_PERMISSIONS.get(command.toLowerCase()) ?? null;
103
130
  }
104
131
  export function permissionForCallbackData(callbackData) {
105
132
  if (!callbackData) {
106
- return "inspect";
133
+ return null;
107
134
  }
108
135
  if (callbackData === "noop_page") {
109
136
  return "inspect";
110
137
  }
111
138
  if (/^(sess_|ws_)/.test(callbackData)) {
112
- return "sessions";
139
+ return "sessions.write";
113
140
  }
114
141
  if (/^(launch_|launchconfirm_|model_|effort_|agent_)/.test(callbackData)) {
115
- return "settings";
142
+ return "settings.write";
116
143
  }
117
144
  if (callbackData.startsWith("upd_")) {
118
- return "admin";
145
+ return "updates.run";
119
146
  }
120
147
  if (callbackData.startsWith("approval_") || callbackData.startsWith("codex_abort:") || callbackData.startsWith("agent_abort:")) {
121
- return "prompt";
148
+ return "prompt.abort";
122
149
  }
123
150
  if (callbackData.startsWith("queue_")) {
124
- return "prompt";
151
+ return "queue.write";
152
+ }
153
+ if (callbackData.startsWith("artifact_delete")) {
154
+ return "files.write";
125
155
  }
126
156
  if (callbackData.startsWith("artifact_")) {
127
- return "files";
157
+ return "files.read";
128
158
  }
129
- return "inspect";
159
+ return null;
130
160
  }
131
- export function isTelegramRole(value) {
132
- return value === "admin" || value === "operator" || value === "readonly";
133
- }
134
- function parsePermissionList(rawPermissions, role) {
135
- if (rawPermissions === "*") {
136
- return new Set(ALL_PERMISSIONS);
137
- }
138
- if (!Array.isArray(rawPermissions)) {
139
- throw new Error(`TELEGRAM_ROLE_POLICIES_JSON.${role} must be an array or "*"`);
140
- }
141
- const permissions = new Set();
142
- for (const rawPermission of rawPermissions) {
143
- if (rawPermission === "*") {
144
- return new Set(ALL_PERMISSIONS);
145
- }
146
- if (typeof rawPermission !== "string" || !isTelegramPermission(rawPermission)) {
147
- throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON permission for ${role}: ${String(rawPermission)}`);
148
- }
149
- permissions.add(rawPermission);
150
- }
151
- return permissions;
161
+ export function permissionForWebRequest(method, pathname) {
162
+ return permissionForWebRequestFromContract(method, pathname);
152
163
  }
153
- function isTelegramPermission(value) {
164
+ export function isPermission(value) {
154
165
  return ALL_PERMISSIONS.includes(value);
155
166
  }