@nordbyte/nordrelay 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -36,9 +36,9 @@ 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
40
  - `/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.
41
+ - `/audit` shows recent prompt, queue, lock, command, authentication, permission-denied, user, group, Telegram-link, Telegram-chat, and web-session audit events for admins.
42
42
 
43
43
  Adapter architecture:
44
44
 
@@ -46,6 +46,7 @@ Adapter architecture:
46
46
  - `/channels` shows available and planned messaging adapters for Discord, WhatsApp, Slack, and Matrix.
47
47
  - Codex, Pi, Hermes, OpenClaw, and Claude Code are implemented as agent adapters.
48
48
  - `/agents` shows available/planned agent adapters and whether Codex, Pi, Hermes, OpenClaw, and Claude Code are enabled.
49
+ - 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.
49
50
 
50
51
  Codex runtime:
51
52
 
@@ -159,13 +160,17 @@ Telegram output:
159
160
 
160
161
  Authentication and safety:
161
162
 
162
- - Telegram access requires `TELEGRAM_ADMIN_USER_IDS` by default; a fresh install only accepts those admin user ids.
163
- - `TELEGRAM_ALLOWED_USER_IDS` can add non-admin operators.
164
- - `TELEGRAM_ALLOWED_CHAT_IDS` is supported for private chats, groups, and backward compatibility.
165
- - `TELEGRAM_ALLOW_ANY_CHAT=true` is an explicit unsafe override for temporary local setup only.
166
- - `TELEGRAM_ADMIN_USER_IDS` restricts admin-only commands such as `/logs`, `/restart`, and `/update`.
167
- - `TELEGRAM_READONLY_USER_IDS` can inspect status and sessions but cannot send prompts or run mutating commands by default.
168
- - `TELEGRAM_ROLE_POLICIES_JSON` can override role permissions for `admin`, `operator`, and `readonly`.
163
+ - WebUI login is required for every dashboard page, API route, SSE stream, artifact download, and health endpoint.
164
+ - Access is managed through NordRelay users, groups, permissions, web sessions, and linked Telegram identities.
165
+ - 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.
166
+ - The last active admin cannot be disabled or demoted, and web sessions are revoked when passwords or group memberships change.
167
+ - Admins can review and revoke active WebUI sessions from the Users page.
168
+ - Telegram private chats require a linked active NordRelay user.
169
+ - Telegram group and forum chats must be registered before use; admins can run `/register_chat` in the chat or enable chats in the WebUI.
170
+ - `/whoami` shows the linked NordRelay account and groups.
171
+ - `/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`.
172
+ - WebUI login and Telegram link attempts are rate-limited to reduce brute-force risk.
173
+ - User, group, Telegram-link, Telegram-chat, web-session, login, and permission-denied events are written to the audit log.
169
174
  - `/auth` reports Codex authentication, Pi provider environment health, Hermes API Server reachability, OpenClaw Gateway reachability, or Claude Code CLI auth for the selected agent.
170
175
  - `/login` starts Telegram-managed CLI auth for Codex, Hermes, or Claude Code when enabled.
171
176
  - `/logout` signs out of CLI auth for Codex, Hermes, or Claude Code; Codex logout is disabled while `CODEX_API_KEY` is in use.
@@ -178,19 +183,20 @@ Operations:
178
183
 
179
184
  - Plugin command/skill starts, stops, restarts, and inspects the connector process.
180
185
  - Manual process commands support `start`, `stop`, `restart`, `status`, and `foreground`.
181
- - Telegram admin commands support `/logs`, `/diagnostics`, `/restart`, and `/update`.
186
+ - Telegram admin commands support `/logs`, `/diagnostics`, `/restart`, and `/update` for NordRelay and agent CLIs.
182
187
  - `/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.
183
- - `/logs` renders redacted connector and update logs with local-time timestamps, levels, file path, last-modified time, and highlighted warnings/errors.
188
+ - `/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
+ - `/logs` renders redacted connector, NordRelay update, and agent update logs with local-time timestamps, levels, file path, last-modified time, and highlighted warnings/errors.
184
190
  - Logs can be emitted as timestamped plain text or JSON records with `CONNECTOR_LOG_FORMAT`.
185
191
  - Telegram sends/edits/documents are routed through a rate-limit queue that honors Telegram retry-after responses.
186
192
  - Context metadata, queues, and preferences are written atomically with backup recovery.
187
193
  - Context metadata, queues, preferences, audit events, and locks can use JSON files or the optional SQLite state backend with `NORDRELAY_STATE_BACKEND=sqlite`.
188
194
  - Runtime config, state, and logs are written under `~/.nordrelay/`.
189
- - `nordrelay init` creates a private runtime config, `nordrelay doctor` validates host prerequisites, and `nordrelay web` starts a full local WebUI dashboard.
195
+ - `nordrelay init` creates a private runtime config, `nordrelay doctor` validates host prerequisites, and `nordrelay web` starts the connector plus a full local WebUI dashboard.
190
196
  - The WebUI has responsive header/sidebar/footer navigation, live chat streaming, session controls, queue/artifact/log/diagnostic views, and settings management.
191
197
  - The WebUI supports light and dark themes, tabbed settings groups, paginated session browsing, and chat uploads for images, documents, and audio transcription.
192
198
  - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, and diagnostics.
193
- - 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 dashboard can bind to `127.0.0.1` or `0.0.0.0`; user login and session cookies are mandatory in both modes.
194
200
  - Telegram can run with long polling or an HTTP webhook via `TELEGRAM_TRANSPORT=webhook`.
195
201
  - Version freshness checks are cached with `NORDRELAY_VERSION_CACHE_TTL_MS` to keep `/version` responsive.
196
202
  - CI includes typecheck, tests, package dry run, npm audit, and a separate secret-scan workflow.
@@ -205,6 +211,7 @@ Recommended npm setup:
205
211
  ```bash
206
212
  npm install -g @nordbyte/nordrelay
207
213
  nordrelay init
214
+ nordrelay user list
208
215
  nordrelay doctor
209
216
  nordrelay start
210
217
  ```
@@ -214,9 +221,16 @@ npm is the fastest install path and is the recommended default for normal use. `
214
221
  Non-interactive setup is also supported:
215
222
 
216
223
  ```bash
217
- nordrelay init --token 123456789:replace-me --admin-id 123456789
224
+ nordrelay init \
225
+ --token 123456789:replace-me \
226
+ --admin-email you@example.com \
227
+ --admin-name "Your Name" \
228
+ --admin-password "replace-with-a-long-password" \
229
+ --telegram-user-id 123456789
218
230
  ```
219
231
 
232
+ `--telegram-user-id` is optional, but linking the first admin during setup is the fastest way to use Telegram immediately.
233
+
220
234
  Source checkout setup:
221
235
 
222
236
  Install dependencies and build the runtime:
@@ -235,14 +249,13 @@ Create the Telegram bot:
235
249
  2. Run `/newbot`.
236
250
  3. Choose a display name and bot username.
237
251
  4. Copy the bot token into `TELEGRAM_BOT_TOKEN` in `~/.nordrelay/nordrelay.env`.
238
- 5. Find your Telegram user id with a trusted id helper bot, for example `@userinfobot`, or from Telegram API tooling.
239
- 6. Put your user id into `TELEGRAM_ADMIN_USER_IDS`.
252
+ 5. Create the first admin user with `nordrelay init` or `nordrelay user create-admin`.
253
+ 6. Link Telegram from the WebUI, with `nordrelay user link-telegram`, or by creating a link code and sending `/link <code>` to the bot.
240
254
 
241
255
  Minimal private-bot `~/.nordrelay/nordrelay.env`:
242
256
 
243
257
  ```dotenv
244
258
  TELEGRAM_BOT_TOKEN=123456789:replace-me
245
- TELEGRAM_ADMIN_USER_IDS=123456789
246
259
  NORDRELAY_CODEX_ENABLED=true
247
260
  NORDRELAY_PI_ENABLED=false
248
261
  NORDRELAY_HERMES_ENABLED=false
@@ -253,13 +266,14 @@ CODEX_SANDBOX_MODE=workspace-write
253
266
  CODEX_APPROVAL_POLICY=never
254
267
  ```
255
268
 
256
- Optional non-admin operators can be added with:
257
-
258
- ```dotenv
259
- TELEGRAM_ALLOWED_USER_IDS=234567890,345678901
260
- ```
269
+ User and Telegram access management:
261
270
 
262
- 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.
271
+ - `nordrelay init` creates the first admin user and writes `~/.nordrelay/users.json`.
272
+ - `nordrelay user create-admin --email you@example.com --name "Your Name"` creates another admin.
273
+ - `nordrelay user create --email dev@example.com --name "Dev" --group user` creates a normal user.
274
+ - `nordrelay user link-telegram --email you@example.com --telegram-user-id 123456789` links a Telegram account directly.
275
+ - `nordrelay user link-code --email you@example.com` creates a short-lived code that the user sends as `/link <code>` to the bot.
276
+ - Group chats are disabled until an admin enables them from the WebUI or runs `/register_chat` inside the group.
263
277
 
264
278
  Codex authentication:
265
279
 
@@ -270,6 +284,7 @@ Codex authentication:
270
284
  Pi setup:
271
285
 
272
286
  - Install Pi from https://pi.dev/ and confirm `pi --help` works on the host.
287
+ - npm installs should use the current package name: `npm install -g @earendil-works/pi-coding-agent`.
273
288
  - Set `NORDRELAY_PI_ENABLED=true` in `~/.nordrelay/nordrelay.env`.
274
289
  - Keep `NORDRELAY_DEFAULT_AGENT=codex` to start chats in Codex, or set `NORDRELAY_DEFAULT_AGENT=pi` to start chats in Pi.
275
290
  - Optional: set `PI_SESSION_DIR` if your Pi sessions are not stored in `~/.pi/agent/sessions/`.
@@ -331,7 +346,7 @@ Where Codex exposes namespaced plugin commands, this also works:
331
346
  /nordrelay:remote
332
347
  ```
333
348
 
334
- 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.
349
+ The command is only a process-manager shortcut; Telegram contains the actual controls.
335
350
 
336
351
  Manual process commands:
337
352
 
@@ -354,6 +369,7 @@ node plugins/nordrelay/scripts/nordrelay.mjs status
354
369
  node plugins/nordrelay/scripts/nordrelay.mjs restart
355
370
  node plugins/nordrelay/scripts/nordrelay.mjs stop
356
371
  node plugins/nordrelay/scripts/nordrelay.mjs foreground
372
+ node plugins/nordrelay/scripts/nordrelay.mjs user list
357
373
  node plugins/nordrelay/scripts/nordrelay.mjs doctor
358
374
  node plugins/nordrelay/scripts/nordrelay.mjs web
359
375
  ```
@@ -374,6 +390,7 @@ Runtime files:
374
390
  - Log file: `~/.nordrelay/nordrelay.log`
375
391
  - Home override: `NORDRELAY_HOME=/custom/path`
376
392
  - Local dashboard: `nordrelay web --host 127.0.0.1 --port 31878`
393
+ - `nordrelay start` and `nordrelay status` print the configured WebUI URL.
377
394
 
378
395
  ## WebUI Dashboard
379
396
 
@@ -383,6 +400,8 @@ Start the local WebUI:
383
400
  nordrelay web
384
401
  ```
385
402
 
403
+ If the connector is not already running, `nordrelay web` starts it automatically before binding the dashboard.
404
+
386
405
  Open:
387
406
 
388
407
  ```text
@@ -403,7 +422,10 @@ The dashboard is a second NordRelay client next to Telegram. It can:
403
422
  - Browse, preview, download, ZIP, and delete artifacts.
404
423
  - Inspect the activity timeline for WebUI and mirrored CLI turns.
405
424
  - Edit all supported runtime settings from tabbed Settings groups with option selects, validation feedback, and restart actions.
406
- - View filtered logs, structured diagnostics, enabled channels, and agent adapters.
425
+ - View filtered connector/update/agent-update logs, structured diagnostics, enabled channels, and agent adapters.
426
+ - Inspect a per-agent capability matrix showing model, reasoning, launch, fast mode, attachments, activity, usage, auth, login/logout, and handback support.
427
+ - Check NordRelay and agent CLI versions, then start Codex, Pi, Hermes, OpenClaw, or Claude Code updates from outdated version rows with live output, cancel, delete-log, and stdin response controls for interactive updaters.
428
+ - Build dashboard CSS and client JavaScript from modular source assets through esbuild, then serve them as authenticated static assets instead of inline HTML.
407
429
 
408
430
  Dashboard API endpoints are served under `/api/*`. Streaming uses `GET /api/events`.
409
431
 
@@ -412,13 +434,9 @@ Dashboard auth:
412
434
  ```dotenv
413
435
  NORDRELAY_DASHBOARD_HOST=127.0.0.1
414
436
  NORDRELAY_DASHBOARD_PORT=31878
415
- NORDRELAY_DASHBOARD_TOKEN=replace-with-random-token
416
- # or:
417
- NORDRELAY_DASHBOARD_USER=admin
418
- NORDRELAY_DASHBOARD_PASSWORD=replace-with-random-password
419
437
  ```
420
438
 
421
- 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.
439
+ 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.
422
440
 
423
441
  Webhook mode:
424
442
 
@@ -431,7 +449,7 @@ TELEGRAM_WEBHOOK_PATH=/telegram/webhook
431
449
  TELEGRAM_WEBHOOK_SECRET=replace-with-random-secret
432
450
  ```
433
451
 
434
- 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.
452
+ 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`.
435
453
 
436
454
  ## Telegram Commands
437
455
 
@@ -440,6 +458,9 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
440
458
  - `/channels` shows available and planned messaging adapters.
441
459
  - `/agents` shows available and planned coding-agent adapters.
442
460
  - `/agent` selects the active agent for this Telegram context.
461
+ - `/link <code>` links the Telegram account to a NordRelay user.
462
+ - `/whoami` shows the linked NordRelay user, groups, and permissions.
463
+ - `/register_chat` enables the current Telegram group or forum chat for NordRelay when the linked user has user-management permission.
443
464
  - `/new` starts a new thread. If the selected agent knows multiple workspaces, Telegram shows a workspace picker.
444
465
  - `/session` shows current thread details.
445
466
  - `/sessions` opens a paginated recent-session picker.
@@ -463,7 +484,7 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
463
484
  - `/cancel <queue-id>` removes one queued prompt; the queue id is the short code shown in messages such as `Queued prompt 332kmt`.
464
485
  - `/clearqueue` clears queued prompts for this Telegram context.
465
486
  - `/activity [all|tools|errors|user|agent|tasks] [limit] [since 1h] [export]` shows or exports rollout activity for the active thread.
466
- - `/audit [limit]` shows recent audit events. Admin only.
487
+ - `/audit [limit]` shows recent audit events. Requires `audit.read`.
467
488
  - `/lock` locks writes for this Telegram session to the current user.
468
489
  - `/unlock` releases the current session write lock.
469
490
  - `/locks` lists active write locks.
@@ -490,12 +511,14 @@ Run NordRelay behind your reverse proxy so the public URL forwards to `http://12
490
511
  - `/status` reports connector runtime status.
491
512
  - `/health` reports runtime health, auth, PIDs, Codex CLI, Pi CLI, Hermes CLI, OpenClaw CLI, Claude Code CLI, and state DB.
492
513
  - `/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.
493
- - `/logs [lines]` shows a redacted, timestamped connector log tail. Admin only.
494
- - `/logs update [lines]` shows the self-update log. Admin only.
495
- - `/logs all [lines]` shows connector and self-update logs together. Admin only.
496
- - `/diagnostics` shows redacted connector diagnostics. Admin only.
497
- - `/restart` restarts the connector process. Admin only.
498
- - `/update` updates through npm or git depending on the detected install type, then restarts only on success. Admin only.
514
+ - `/logs [lines]` shows a redacted, timestamped connector log tail. Requires `logs.read`.
515
+ - `/logs update [lines]` shows the self-update log. Requires `logs.read`.
516
+ - `/logs agent [lines]` shows the aggregate agent updater log. Requires `logs.read`.
517
+ - `/logs all [lines]` shows connector, self-update, and agent update logs together. Requires `logs.read`.
518
+ - `/diagnostics` shows redacted connector diagnostics. Requires `logs.read`.
519
+ - `/restart` restarts the connector process. Requires `system.restart`.
520
+ - `/update` updates through npm or git depending on the detected install type, then restarts only on success. Requires `updates.run`.
521
+ - `/update agents`, `/update <agent>`, `/update jobs`, `/update log <id>`, `/update cancel <id>`, and `/update input <id> <text>` manage agent CLI update jobs. Requires `updates.run`.
499
522
 
500
523
  ## Command Examples
501
524
 
@@ -678,12 +701,6 @@ Voice transcription uses `OPENAI_API_KEY`, not `CODEX_API_KEY`.
678
701
  Telegram:
679
702
 
680
703
  - `TELEGRAM_BOT_TOKEN`: required BotFather token.
681
- - `TELEGRAM_ADMIN_USER_IDS`: required comma-separated Telegram user ids allowed to use admin commands. Admin ids are automatically allowed to use the bot.
682
- - `TELEGRAM_ALLOWED_USER_IDS`: optional comma-separated non-admin Telegram user ids allowed to use the bot.
683
- - `TELEGRAM_READONLY_USER_IDS`: comma-separated Telegram user ids that can inspect status and sessions but cannot run prompts or mutating commands.
684
- - `TELEGRAM_ALLOWED_CHAT_IDS`: comma-separated chat ids allowed to use the bot. Group ids may be negative.
685
- - `TELEGRAM_ALLOW_ANY_CHAT`: allows all chats when `true`. Keep `false` unless you intentionally want an open bot.
686
- - `TELEGRAM_ROLE_POLICIES_JSON`: optional JSON object mapping roles to permissions. Permissions are `inspect`, `sessions`, `prompt`, `files`, `settings`, `auth`, and `admin`.
687
704
  - `TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS`: minimum interval for normal Telegram API sends. Defaults to `80`.
688
705
  - `TELEGRAM_EDIT_MIN_INTERVAL_MS`: minimum interval for Telegram message edits. Defaults to `1200`.
689
706
  - `TELEGRAM_TRANSPORT`: `polling` or `webhook`. Defaults to `polling`.
@@ -698,11 +715,13 @@ Telegram:
698
715
  - `TELEGRAM_QUIET_HOURS`: optional quiet-hour range in `HH-HH` format, for example `22-7`.
699
716
  - `TELEGRAM_REDACT_PATTERNS`: comma-separated regular expressions for additional Telegram/log redaction.
700
717
 
701
- Role policy example:
718
+ User management:
702
719
 
703
- ```dotenv
704
- TELEGRAM_ROLE_POLICIES_JSON={"readonly":["inspect","sessions"],"operator":["inspect","sessions","prompt","files"],"admin":"*"}
705
- ```
720
+ - Users, groups, Telegram identities, Telegram group-chat access, and web sessions are stored in `~/.nordrelay/users.json`.
721
+ - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, and `link-code`.
722
+ - Built-in groups are `admin`, `user`, and `readonly`.
723
+ - 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`.
724
+ - Custom groups can also restrict access to specific agent ids, workspace roots, and Telegram chat ids.
706
725
 
707
726
  Agent selection:
708
727
 
@@ -721,9 +740,6 @@ Dashboard:
721
740
 
722
741
  - `NORDRELAY_DASHBOARD_HOST`: dashboard bind host. Defaults to `127.0.0.1`.
723
742
  - `NORDRELAY_DASHBOARD_PORT`: dashboard bind port. Defaults to `31878`.
724
- - `NORDRELAY_DASHBOARD_TOKEN`: optional dashboard bearer/login token. Required when binding to `0.0.0.0` unless basic auth is configured.
725
- - `NORDRELAY_DASHBOARD_USER`: optional dashboard basic-auth user.
726
- - `NORDRELAY_DASHBOARD_PASSWORD`: optional dashboard basic-auth password. Required with `NORDRELAY_DASHBOARD_USER`.
727
743
  - `NORDRELAY_ENV_FILE`: optional explicit env-file path used by the wrapper and edited by the dashboard settings page. Defaults to `~/.nordrelay/nordrelay.env`.
728
744
 
729
745
  Codex:
@@ -820,6 +836,7 @@ NordRelay wrapper:
820
836
  - `NORDRELAY_HOME`: config/state/log directory override. Defaults to `~/.nordrelay`.
821
837
  - `NORDRELAY_SOURCE_ROOT`: runtime source root override. Useful when the plugin is launched from Codex cache.
822
838
  - `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.
839
+ - 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`.
823
840
  - `NORDRELAY_KEEP_PENDING_UPDATES`: set true to avoid dropping pending Telegram updates on start.
824
841
  - `NORDRELAY_FORWARD_TOOL_OUTPUT`: backward-compatible alias that sets `TOOL_VERBOSITY=all` when `TOOL_VERBOSITY` is unset.
825
842
  - `NORDRELAY_STATE_FILE`: internal state-file path passed by the wrapper.
@@ -859,14 +876,14 @@ Unsafe profiles are intentionally gated. Telegram asks for confirmation before a
859
876
 
860
877
  ## Security Notes
861
878
 
862
- - Always set `TELEGRAM_ADMIN_USER_IDS`; a fresh install refuses to start without at least one admin user id.
863
- - Prefer `TELEGRAM_ADMIN_USER_IDS` and `TELEGRAM_ALLOWED_USER_IDS` over `TELEGRAM_ALLOWED_CHAT_IDS` for private bots.
864
- - Use `TELEGRAM_ALLOWED_CHAT_IDS` for groups or forum topics only when you trust the entire chat.
865
- - Do not leave `TELEGRAM_ALLOW_ANY_CHAT=true` enabled after setup.
879
+ - Create the first admin user during setup and keep that account protected with a strong password.
880
+ - Link Telegram accounts only to active NordRelay users that should control agents remotely.
881
+ - Enable Telegram group/forum chats only when the whole chat context is trusted for the permissions granted to linked users.
882
+ - Review group permissions before granting `prompt.send`, `prompt.abort`, `files.write`, `settings.write`, `updates.run`, `system.restart`, or `users.write`.
866
883
  - Treat `danger-full-access` as equivalent to shell access on the host.
867
884
  - Treat uploaded files as untrusted input. They are staged inside the active workspace so the selected sandbox policy still matters.
868
885
  - 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.
869
- - In group chats, remember that any allowed user can prompt the selected agent in that chat context.
886
+ - In group chats, remember that any linked user with prompt permissions can prompt the selected agent in that chat context.
870
887
  - Use `TOOL_VERBOSITY=summary` or `errors-only` when command output may include sensitive data.
871
888
  - Review and unsafe launch profiles add a Telegram approve/deny gate before each turn starts.
872
889
 
@@ -1023,19 +1040,24 @@ npm run build
1023
1040
  - `plugins/nordrelay/`: Codex plugin bundle with manifest, skill, command, icon, and process wrapper.
1024
1041
  - `plugins/nordrelay/scripts/nordrelay.mjs`: process manager for `start`, `stop`, `restart`, `status`, and `foreground`.
1025
1042
  - `src/index.ts`: runtime entrypoint, config load, auth check, state-file writes, polling lifecycle, shutdown.
1026
- - `src/bot.ts`: Telegram command handlers, callbacks, message streaming, file/photo/voice handling, artifacts, and error handling.
1043
+ - `src/bot.ts`: Telegram prompt/session runtime, streaming, file/photo/voice handling, artifacts, and error handling.
1044
+ - `src/telegram-access-commands.ts`, `src/telegram-update-commands.ts`, and `src/telegram-command-menu.ts`: focused Telegram command groups for access linking, update jobs, and command menu registration.
1045
+ - `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.
1046
+ - `src/config-metadata.ts`: shared setting metadata used by the WebUI settings page and generated `.env.example`.
1047
+ - `src/webui/`: focused WebUI source assets for core runtime state/API helpers, overview rendering, live events, chat/session workflows, admin pages, and CSS sections.
1027
1048
  - `src/bot-preferences.ts`: per-context mirror, notification, quiet-hour, and voice preference persistence.
1028
1049
  - `src/telegram-rate-limit.ts`: centralized Telegram API send/edit/document rate limiting and retry-after tracking.
1029
1050
  - `src/persistence.ts`: atomic JSON/text writes with backup recovery.
1030
1051
  - `src/redaction.ts`: common secret redaction and custom redaction pattern support.
1031
1052
  - `src/workspace-policy.ts`: workspace allow/warn root evaluation.
1032
- - `src/access-control.ts`: Telegram role permissions and command/callback permission mapping.
1053
+ - `src/access-control.ts`: user/group permission definitions and command/callback/WebUI permission mapping.
1033
1054
  - `src/codex-session.ts`: Codex SDK service for new/resumed threads, streaming events, abort, model, reasoning, launch profiles, and handback.
1034
1055
  - `src/pi-session.ts`: Pi RPC service for JSONL RPC sessions, streaming events, abort, model, thinking, launch profiles, and handback.
1035
1056
  - `src/hermes-session.ts`: Hermes API Server service for streamed runs, stop, model, reasoning, launch profiles, attachments, and handback.
1036
1057
  - `src/openclaw-session.ts`: OpenClaw Gateway service for streamed runs, cancel, model, thinking, launch profiles, attachments, and handback.
1037
1058
  - `src/claude-code-session.ts`: Claude Agent SDK service for streamed runs, abort, model, effort, launch profiles, attachments, and handback.
1038
1059
  - `src/session-registry.ts`: per-chat/topic session registry and persisted context metadata.
1060
+ - `test/agent-adapter-contract.test.ts`: shared adapter contract coverage for descriptors, capability flags, reasoning options, launch profiles, and `AgentSessionService` method parity.
1039
1061
  - `src/session-format.ts`: compact Telegram rendering for session details, token usage, and limits.
1040
1062
  - `src/codex-state.ts`: reader for Codex `~/.codex/state_*.sqlite` thread, workspace, model, reasoning, sandbox, and approval metadata.
1041
1063
  - `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,138 +72,93 @@ 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
+ ["logs", "logs.read"],
83
+ ["audit", "audit.read"],
84
+ ["restart", "system.restart"],
85
+ ["update", "updates.run"],
86
+ ["workspaces", "sessions.read"],
87
+ ["session", "sessions.read"],
88
+ ["sessions", "sessions.read"],
89
+ ["pinned", "sessions.read"],
90
+ ["locks", "sessions.read"],
91
+ ["queue", "queue.read"],
92
+ ["artifacts", "files.read"],
93
+ ["agent", "settings.write"],
94
+ ["mirror", "settings.write"],
95
+ ["notify", "settings.write"],
96
+ ["launch", "settings.write"],
97
+ ["launch_profiles", "settings.write"],
98
+ ["launch-profiles", "settings.write"],
99
+ ["fast", "settings.write"],
100
+ ["model", "settings.write"],
101
+ ["reasoning", "settings.write"],
102
+ ["effort", "settings.write"],
103
+ ["login", "auth.manage"],
104
+ ["logout", "auth.manage"],
105
+ ["new", "sessions.write"],
106
+ ["switch", "sessions.write"],
107
+ ["attach", "sessions.write"],
108
+ ["handback", "sessions.write"],
109
+ ["sync", "sessions.write"],
110
+ ["pin", "sessions.write"],
111
+ ["unpin", "sessions.write"],
112
+ ["lock", "sessions.write"],
113
+ ["unlock", "sessions.write"],
114
+ ["retry", "prompt.send"],
115
+ ["clearqueue", "queue.write"],
116
+ ["cancel", "queue.write"],
117
+ ["abort", "prompt.abort"],
118
+ ["stop", "prompt.abort"],
119
+ ["register_chat", "users.write"],
120
+ ["chat_access", "users.write"],
121
+ ["link", "inspect"],
61
122
  ]);
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
123
  export function permissionForCommand(command) {
99
124
  if (!command) {
100
- return "inspect";
125
+ return null;
101
126
  }
102
- return COMMAND_PERMISSIONS.get(command.toLowerCase()) ?? "inspect";
127
+ return COMMAND_PERMISSIONS.get(command.toLowerCase()) ?? null;
103
128
  }
104
129
  export function permissionForCallbackData(callbackData) {
105
130
  if (!callbackData) {
106
- return "inspect";
131
+ return null;
107
132
  }
108
133
  if (callbackData === "noop_page") {
109
134
  return "inspect";
110
135
  }
111
136
  if (/^(sess_|ws_)/.test(callbackData)) {
112
- return "sessions";
137
+ return "sessions.write";
113
138
  }
114
139
  if (/^(launch_|launchconfirm_|model_|effort_|agent_)/.test(callbackData)) {
115
- return "settings";
140
+ return "settings.write";
141
+ }
142
+ if (callbackData.startsWith("upd_")) {
143
+ return "updates.run";
116
144
  }
117
145
  if (callbackData.startsWith("approval_") || callbackData.startsWith("codex_abort:") || callbackData.startsWith("agent_abort:")) {
118
- return "prompt";
146
+ return "prompt.abort";
119
147
  }
120
148
  if (callbackData.startsWith("queue_")) {
121
- return "prompt";
149
+ return "queue.write";
150
+ }
151
+ if (callbackData.startsWith("artifact_delete")) {
152
+ return "files.write";
122
153
  }
123
154
  if (callbackData.startsWith("artifact_")) {
124
- return "files";
155
+ return "files.read";
125
156
  }
126
- return "inspect";
127
- }
128
- export function isTelegramRole(value) {
129
- return value === "admin" || value === "operator" || value === "readonly";
157
+ return null;
130
158
  }
131
- function parsePermissionList(rawPermissions, role) {
132
- if (rawPermissions === "*") {
133
- return new Set(ALL_PERMISSIONS);
134
- }
135
- if (!Array.isArray(rawPermissions)) {
136
- throw new Error(`TELEGRAM_ROLE_POLICIES_JSON.${role} must be an array or "*"`);
137
- }
138
- const permissions = new Set();
139
- for (const rawPermission of rawPermissions) {
140
- if (rawPermission === "*") {
141
- return new Set(ALL_PERMISSIONS);
142
- }
143
- if (typeof rawPermission !== "string" || !isTelegramPermission(rawPermission)) {
144
- throw new Error(`Invalid TELEGRAM_ROLE_POLICIES_JSON permission for ${role}: ${String(rawPermission)}`);
145
- }
146
- permissions.add(rawPermission);
147
- }
148
- return permissions;
159
+ export function permissionForWebRequest(method, pathname) {
160
+ return permissionForWebRequestFromContract(method, pathname);
149
161
  }
150
- function isTelegramPermission(value) {
162
+ export function isPermission(value) {
151
163
  return ALL_PERMISSIONS.includes(value);
152
164
  }