@nordbyte/nordrelay 0.2.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 (45) hide show
  1. package/.env.example +88 -0
  2. package/Dockerfile +19 -0
  3. package/LICENSE +21 -0
  4. package/README.md +749 -0
  5. package/dist/access-control.js +146 -0
  6. package/dist/agent-factory.js +22 -0
  7. package/dist/agent.js +57 -0
  8. package/dist/artifacts.js +515 -0
  9. package/dist/attachments.js +69 -0
  10. package/dist/bot-preferences.js +146 -0
  11. package/dist/bot-ui.js +161 -0
  12. package/dist/bot.js +4520 -0
  13. package/dist/codex-auth.js +150 -0
  14. package/dist/codex-cli.js +79 -0
  15. package/dist/codex-config.js +50 -0
  16. package/dist/codex-launch.js +109 -0
  17. package/dist/codex-session.js +591 -0
  18. package/dist/codex-state.js +573 -0
  19. package/dist/config.js +385 -0
  20. package/dist/context-key.js +23 -0
  21. package/dist/error-messages.js +73 -0
  22. package/dist/format.js +121 -0
  23. package/dist/index.js +140 -0
  24. package/dist/logger.js +27 -0
  25. package/dist/operations.js +133 -0
  26. package/dist/persistence.js +65 -0
  27. package/dist/pi-cli.js +19 -0
  28. package/dist/pi-rpc.js +158 -0
  29. package/dist/pi-session.js +573 -0
  30. package/dist/pi-state.js +226 -0
  31. package/dist/prompt-store.js +241 -0
  32. package/dist/redaction.js +47 -0
  33. package/dist/session-format.js +191 -0
  34. package/dist/session-registry.js +195 -0
  35. package/dist/telegram-rate-limit.js +136 -0
  36. package/dist/voice.js +373 -0
  37. package/dist/workspace-policy.js +41 -0
  38. package/docker-compose.yml +17 -0
  39. package/launchd/start.sh +8 -0
  40. package/package.json +69 -0
  41. package/plugins/nordrelay/.codex-plugin/plugin.json +48 -0
  42. package/plugins/nordrelay/assets/nordrelay.svg +5 -0
  43. package/plugins/nordrelay/commands/remote.md +33 -0
  44. package/plugins/nordrelay/scripts/nordrelay.mjs +396 -0
  45. package/plugins/nordrelay/skills/telegram-remote/SKILL.md +26 -0
package/README.md ADDED
@@ -0,0 +1,749 @@
1
+ # NordRelay
2
+
3
+ NordRelay is a remote control plane for coding agents across messaging channels. The current implementation connects Codex and Pi 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.
4
+
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 plus Pi RPC mode for Pi.
6
+
7
+ ## Features
8
+
9
+ Session control:
10
+
11
+ - Independent coding-agent sessions per Telegram private chat, group chat, and forum topic.
12
+ - `/agent` switches a Telegram context between enabled agents such as Codex and Pi.
13
+ - Persistent Telegram context metadata in the active workspace under `.nordrelay/contexts.json`.
14
+ - `/new` starts a fresh thread, with workspace selection when known workspaces are available.
15
+ - `/session` shows thread id, workspace, launch profile, launch behavior, model, reasoning, fast mode, context usage, token totals, and subscription limit remaining percentages.
16
+ - `/sessions` opens a paginated browser for recent sessions from the selected agent.
17
+ - `/sessions <query>` filters recent sessions by id, title, workspace, model, or first message.
18
+ - `/sync` manually refreshes the active Telegram session from local CLI state when the selected agent supports state watching.
19
+ - `/pin`, `/unpin`, and `/pinned` keep important threads at the top of Telegram session browsing.
20
+ - `/switch <session-id>` switches directly to an existing session.
21
+ - `/attach <session-id>` binds an existing agent session to the current chat or topic.
22
+ - Existing thread metadata is imported on switch/attach, including model, reasoning effort, sandbox mode, and approval policy.
23
+ - Codex session usage is read from local rollout JSONL files, including context-used percent, total input/output tokens, 5h limit remaining, and weekly limit remaining.
24
+ - `/handback` returns a ready-to-run CLI command for continuing in the native agent CLI.
25
+ - `/retry` resends the last prompt for the current Telegram context.
26
+ - `/queue`, inline run/top/up/down/cancel buttons, `/cancel <queue-id>`, and `/clearqueue` manage queued prompts for a busy Telegram context.
27
+ - `/abort`, `/stop`, and the inline Abort button cancel the active agent turn.
28
+ - Busy prompts are queued per Telegram context instead of being dropped.
29
+ - If the attached thread is currently active in the local Codex CLI, Telegram prompts are queued until that CLI task finishes.
30
+ - Active Codex CLI turns are mirrored into Telegram with configurable `off`, `status`, `final`, or `full` modes.
31
+ - `/mirror` controls CLI mirroring per Telegram context.
32
+ - Queues survive connector restarts and are resumed automatically when the external CLI turn becomes idle.
33
+ - `/notify` controls completion/status notifications and quiet hours per Telegram context.
34
+ - `/workspaces` lists allowed workspaces and shows workspace guardrail warnings.
35
+ - `/status`, `/health`, and `/version` report connector runtime health from Telegram.
36
+ - `/tasks` and `/progress` show the current turn status, queue length, active tool, elapsed time, and last error.
37
+ - `/activity` shows a compact timeline of recent rollout events for the active thread, with filters and export.
38
+ - `/diagnostics` reports redacted runtime, config, role, Telegram rate-limit, mirror, voice, session, queue, and progress details for admins.
39
+
40
+ Codex runtime:
41
+
42
+ - Uses `@openai/codex-sdk` to start, resume, and stream Codex threads.
43
+ - Prefers the host `codex` executable on `PATH`, so Codex CLI updates are picked up automatically; the SDK-bundled CLI is used only as fallback.
44
+ - Supports model selection through `/model`, using Codex model cache when available and fallback models otherwise.
45
+ - Supports reasoning effort selection through `/reasoning` and the backward-compatible `/effort` alias: `minimal`, `low`, `medium`, `high`, `xhigh`.
46
+ - Supports launch profiles through `/launch_profiles` and `/launch`.
47
+ - Built-in launch profiles include Default, Read Only, Review, and optional Full Access.
48
+ - Custom launch profiles can be configured with `CODEX_LAUNCH_PROFILES_JSON`.
49
+ - Unsafe `danger-full-access` profiles require `ENABLE_UNSAFE_LAUNCH_PROFILES=true` and Telegram confirmation.
50
+ - Review or unsafe launch profiles require an inline Telegram approval before each prompt is executed.
51
+ - Fast mode can be toggled with `/fast` and mirrors Codex's `fast_default_opt_out` setting from `~/.codex/config.toml`.
52
+ - Active Telegram sessions periodically sync model, reasoning, workspace, launch metadata, and fast-mode defaults from local Codex state.
53
+ - Active local Codex CLI tasks are detected from rollout JSONL files so Telegram does not race the CLI on the same thread.
54
+ - `/diagnostics` includes rollout path, activity status, stale/idle reason, line count, and last update time.
55
+ - Optional per-turn token usage footer with `SHOW_TURN_TOKEN_USAGE=true`.
56
+
57
+ Pi runtime:
58
+
59
+ - Pi support is opt-in with `NORDRELAY_PI_ENABLED=true`.
60
+ - The default Telegram agent is selected with `NORDRELAY_DEFAULT_AGENT=codex` or `pi`.
61
+ - Pi sessions are driven through official `pi --mode rpc` JSONL commands and events.
62
+ - Existing Pi sessions are discovered from `~/.pi/agent/sessions/` or `PI_SESSION_DIR`.
63
+ - `/sessions`, `/switch`, `/attach`, `/new`, `/session`, `/handback`, `/model`, `/reasoning`, `/abort`, `/stop`, `/retry`, `/queue`, files, photos, and voice input work for Pi contexts.
64
+ - Pi model selection uses `pi --list-models` and sends `set_model` through RPC for active sessions.
65
+ - Pi thinking levels use `/reasoning` and support `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`.
66
+ - Pi token and context stats are read through `get_session_stats` when an RPC session is active.
67
+ - Codex-specific features such as launch profiles, fast mode, Codex auth, rollout mirroring, Codex activity timelines, and subscription limit percentages are hidden or reported as unsupported for Pi contexts.
68
+
69
+ Telegram input:
70
+
71
+ - Plain text messages become prompts for the selected agent.
72
+ - Voice and audio messages are transcribed before being sent to the selected agent.
73
+ - Voice transcription uses local `faster-whisper` on Linux, local `parakeet-coreml` on macOS Apple Silicon, or OpenAI Whisper when `OPENAI_API_KEY` is set.
74
+ - `/voice` can select backend preference, language, and transcribe-only mode.
75
+ - Photo messages are passed to the selected agent as local image input when supported.
76
+ - Document messages are downloaded, sanitized, size-checked, and staged under `.nordrelay/inbox/<turn-id>/`.
77
+ - Telegram media groups and albums are combined into one Codex turn with all photos and documents staged together.
78
+ - Uploaded documents include prompt instructions telling the selected agent where files were staged.
79
+ - Staged document and photo prompts are persisted so `/retry` and queued execution can replay them after a restart.
80
+ - Telegram forum topics are treated as separate work contexts.
81
+
82
+ Telegram output:
83
+
84
+ - Assistant replies stream back to Telegram with debounced message edits.
85
+ - Telegram `typing` status is sent while the selected agent is working.
86
+ - Markdown is converted to Telegram HTML where possible, with fallback to plain text.
87
+ - Long replies are split to respect Telegram message limits.
88
+ - Tool activity can be displayed as summary, full output, errors only, or hidden with `TOOL_VERBOSITY`.
89
+ - Command execution, web search, file changes, MCP tool calls, error items, and todo-list updates are surfaced.
90
+ - Todo-list updates are rendered as a live plan/status message.
91
+ - Generated artifacts from `.nordrelay/turns/<turn-id>/out/` are retained for manual retrieval with `/artifacts`.
92
+ - Workspace files detected after mirrored Codex CLI turns are indexed as `/artifacts` entries, even when automatic artifact delivery is disabled.
93
+ - Automatic artifact summaries and file uploads are disabled by default; set `TELEGRAM_AUTO_SEND_ARTIFACTS=true` to send them after turns.
94
+ - Workspace artifact detection sorts by modification time and supports configurable ignored directories and globs.
95
+ - Image artifacts are sent with Telegram previews; large multi-file outputs are bundled into one ZIP when possible.
96
+ - `/artifacts` lists recent generated files and can resend the latest or a specific artifact turn.
97
+ - `/artifacts` includes inline actions to resend, ZIP, or delete artifact turns.
98
+ - Old artifact and inbox turn directories are pruned automatically with configurable retention.
99
+ - Optional Telegram message reactions can acknowledge work start and completion with `ENABLE_TELEGRAM_REACTIONS=true`.
100
+
101
+ Authentication and safety:
102
+
103
+ - Telegram access requires `TELEGRAM_ADMIN_USER_IDS` by default; a fresh install only accepts those admin user ids.
104
+ - `TELEGRAM_ALLOWED_USER_IDS` can add non-admin operators.
105
+ - `TELEGRAM_ALLOWED_CHAT_IDS` is supported for private chats, groups, and backward compatibility.
106
+ - `TELEGRAM_ALLOW_ANY_CHAT=true` is an explicit unsafe override for temporary local setup only.
107
+ - `TELEGRAM_ADMIN_USER_IDS` restricts admin-only commands such as `/logs`, `/restart`, and `/update`.
108
+ - `TELEGRAM_READONLY_USER_IDS` can inspect status and sessions but cannot send prompts or run mutating commands by default.
109
+ - `TELEGRAM_ROLE_POLICIES_JSON` can override role permissions for `admin`, `operator`, and `readonly`.
110
+ - `/auth` reports Codex authentication status.
111
+ - `/login` starts Codex CLI device auth from Telegram when enabled.
112
+ - `/logout` signs out of CLI auth when not using `CODEX_API_KEY`.
113
+ - `CODEX_API_KEY` can be used for host-side Codex authentication.
114
+ - Friendly error messages are returned for auth, network, model, rate-limit, timeout, and context-length failures.
115
+ - Outgoing Telegram messages and logs redact common token/API-key patterns, with optional custom redaction patterns.
116
+ - Workspace allow/warn roots can prevent accidental operation in the wrong project directory.
117
+
118
+ Operations:
119
+
120
+ - Plugin command/skill starts, stops, restarts, and inspects the connector process.
121
+ - Manual process commands support `start`, `stop`, `restart`, `status`, and `foreground`.
122
+ - Telegram admin commands support `/logs`, `/diagnostics`, `/restart`, and `/update`.
123
+ - `/update` pulls changes, installs dependencies, runs check, tests, and build, and restarts only after all steps pass.
124
+ - Logs can be emitted as plain text or JSON records with `CONNECTOR_LOG_FORMAT`.
125
+ - Telegram sends/edits/documents are routed through a rate-limit queue that honors Telegram retry-after responses.
126
+ - Context metadata, queues, and preferences are written atomically with backup recovery.
127
+ - Runtime state and logs are written under `~/.codex/nordrelay/`.
128
+ - `npm run dev`, `npm run build`, `npm run check`, `npm test`, `npm start`, `npm stop`, and `npm run status` are available.
129
+ - Dockerfile and `docker-compose.yml` are included for containerized operation.
130
+ - A `launchd/start.sh` helper is included for host-managed startup.
131
+
132
+ ## First Run Setup
133
+
134
+ Install the published package:
135
+
136
+ ```bash
137
+ npm install -g @nordbyte/nordrelay
138
+ ```
139
+
140
+ For package installs, put runtime configuration in a directory-local `.env` before running `nordrelay`, or in `~/.codex/nordrelay/nordrelay.env`.
141
+
142
+ Source checkout setup:
143
+
144
+ Install dependencies and build the runtime:
145
+
146
+ ```bash
147
+ npm install
148
+ npm run build
149
+ cp .env.example .env
150
+ ```
151
+
152
+ Create the Telegram bot:
153
+
154
+ 1. Open Telegram and talk to `@BotFather`.
155
+ 2. Run `/newbot`.
156
+ 3. Choose a display name and bot username.
157
+ 4. Copy the bot token into `TELEGRAM_BOT_TOKEN` in `.env`.
158
+ 5. Find your Telegram user id with a trusted id helper bot, for example `@userinfobot`, or from Telegram API tooling.
159
+ 6. Put your user id into `TELEGRAM_ADMIN_USER_IDS`.
160
+
161
+ Minimal private-bot `.env`:
162
+
163
+ ```dotenv
164
+ TELEGRAM_BOT_TOKEN=123456789:replace-me
165
+ TELEGRAM_ADMIN_USER_IDS=123456789
166
+ NORDRELAY_CODEX_ENABLED=true
167
+ NORDRELAY_PI_ENABLED=false
168
+ NORDRELAY_DEFAULT_AGENT=codex
169
+ CODEX_SANDBOX_MODE=workspace-write
170
+ CODEX_APPROVAL_POLICY=never
171
+ ```
172
+
173
+ Optional non-admin operators can be added with:
174
+
175
+ ```dotenv
176
+ TELEGRAM_ALLOWED_USER_IDS=234567890,345678901
177
+ ```
178
+
179
+ 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.
180
+
181
+ Codex authentication:
182
+
183
+ - Preferred local setup: run `codex login` on the host before starting the connector.
184
+ - Remote setup: use `/auth` and `/login` in Telegram if `ENABLE_TELEGRAM_LOGIN=true`.
185
+ - API-key setup: set `CODEX_API_KEY`; `/logout` is disabled while `CODEX_API_KEY` is in use.
186
+
187
+ Pi setup:
188
+
189
+ - Install Pi from https://pi.dev/ and confirm `pi --help` works on the host.
190
+ - Set `NORDRELAY_PI_ENABLED=true` in `.env`.
191
+ - Keep `NORDRELAY_DEFAULT_AGENT=codex` to start chats in Codex, or set `NORDRELAY_DEFAULT_AGENT=pi` to start chats in Pi.
192
+ - Optional: set `PI_SESSION_DIR` if your Pi sessions are not stored in `~/.pi/agent/sessions/`.
193
+ - Optional: set `PI_DEFAULT_MODEL=openai-codex/gpt-5.5` and `PI_DEFAULT_THINKING=medium`.
194
+
195
+ Register the local Codex marketplace:
196
+
197
+ ```bash
198
+ codex plugin marketplace add ~/projects/nordrelay
199
+ ```
200
+
201
+ An example local marketplace entry is available at `docs/nordrelay-marketplace.example.json`. Keep personal `.agents/` marketplace files outside the public repo.
202
+
203
+ ## Running
204
+
205
+ From Codex, ask:
206
+
207
+ ```text
208
+ Starte Telegram Remote
209
+ ```
210
+
211
+ Where Codex exposes namespaced plugin commands, this also works:
212
+
213
+ ```text
214
+ /nordrelay:remote
215
+ ```
216
+
217
+ 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.
218
+
219
+ Manual process commands:
220
+
221
+ ```bash
222
+ nordrelay start
223
+ nordrelay status
224
+ nordrelay restart
225
+ nordrelay stop
226
+ nordrelay foreground
227
+ ```
228
+
229
+ Source checkout process commands:
230
+
231
+ ```bash
232
+ node plugins/nordrelay/scripts/nordrelay.mjs start
233
+ node plugins/nordrelay/scripts/nordrelay.mjs status
234
+ node plugins/nordrelay/scripts/nordrelay.mjs restart
235
+ node plugins/nordrelay/scripts/nordrelay.mjs stop
236
+ node plugins/nordrelay/scripts/nordrelay.mjs foreground
237
+ ```
238
+
239
+ NPM shortcuts:
240
+
241
+ ```bash
242
+ npm start
243
+ npm run status
244
+ npm stop
245
+ npm run foreground
246
+ ```
247
+
248
+ Runtime files:
249
+
250
+ - PID file: `~/.codex/nordrelay/nordrelay.pid`
251
+ - State file: `~/.codex/nordrelay/state.json`
252
+ - Log file: `~/.codex/nordrelay/nordrelay.log`
253
+ - Home override: `NORDRELAY_HOME=/custom/path`
254
+
255
+ ## Telegram Commands
256
+
257
+ - `/start` shows welcome text and the selected launch profile.
258
+ - `/help` shows the grouped command reference.
259
+ - `/agent` selects the active agent for this Telegram context.
260
+ - `/new` starts a new thread. If the selected agent knows multiple workspaces, Telegram shows a workspace picker.
261
+ - `/session` shows current thread details.
262
+ - `/sessions` opens a paginated recent-session picker.
263
+ - `/sessions <query>` searches recent sessions.
264
+ - `/sync` syncs the active session from local CLI state when supported.
265
+ - `/pinned` opens a pinned-thread picker.
266
+ - `/pin [thread-id]` pins a thread for this Telegram context; defaults to the active thread.
267
+ - `/unpin [thread-id]` unpins a thread for this Telegram context; defaults to the active thread.
268
+ - `/switch <session-id>` switches directly to a known session.
269
+ - `/attach <session-id>` binds a known session to the current chat or forum topic.
270
+ - `/handback` detaches the active session and prints the native CLI resume command.
271
+ - `/retry` resends the last prompt for this Telegram context.
272
+ - `/queue` shows queued prompts for this Telegram context with inline run/top/up/down/cancel buttons.
273
+ - `/queue pause` pauses automatic queued prompt execution.
274
+ - `/queue resume` resumes automatic queued prompt execution.
275
+ - `/queue move <queue-id> top|up|down` changes queued prompt priority.
276
+ - `/queue run <queue-id>` resumes the queue and runs that prompt next when the session is idle.
277
+ - Queued prompt replies include a cancel button while the prompt is still waiting.
278
+ - `/cancel <queue-id>` removes one queued prompt; the queue id is the short code shown in messages such as `Queued prompt 332kmt`.
279
+ - `/clearqueue` clears queued prompts for this Telegram context.
280
+ - `/activity [all|tools|errors|user|agent|tasks] [limit] [since 1h] [export]` shows or exports rollout activity for the active thread.
281
+ - `/artifacts [latest|zip latest|turn-id]` lists or resends generated artifacts for the current workspace.
282
+ - `/workspaces` lists workspaces known to the selected agent and allowed by the workspace policy.
283
+ - `/abort` cancels the current operation.
284
+ - `/stop` is an alias for `/abort`.
285
+ - `/launch_profiles` or `/launch` opens the launch profile picker.
286
+ - `/fast [on|off]` toggles Codex fast mode. Without an argument it flips the current state.
287
+ - `/model` opens the model picker.
288
+ - `/reasoning` opens the Codex reasoning or Pi thinking picker.
289
+ - `/effort` is a backward-compatible alias for `/reasoning`.
290
+ - `/mirror [off|status|final|full]` controls local CLI mirroring for this Telegram context.
291
+ - `/notify [off|minimal|all]` controls Telegram notifications.
292
+ - `/notify quiet HH-HH` sets quiet hours; `/notify quiet off` disables them.
293
+ - `/auth` reports Codex authentication status, or explains that the selected agent uses host-side CLI auth.
294
+ - `/login` starts Telegram-initiated Codex CLI login when Codex is selected.
295
+ - `/logout` signs out from Codex CLI auth unless `CODEX_API_KEY` is active.
296
+ - `/voice` reports voice transcription backends and current voice preferences.
297
+ - `/voice backend auto|parakeet|faster-whisper|openai` selects backend preference.
298
+ - `/voice language auto|<code>` selects transcription language.
299
+ - `/voice transcribe_only on|off` controls whether voice is only transcribed or also sent to Codex.
300
+ - `/tasks` or `/progress` reports the current turn and queue progress.
301
+ - `/status` reports connector runtime status.
302
+ - `/health` reports runtime health, auth, PIDs, Codex CLI, Pi CLI, and state DB.
303
+ - `/version` reports connector, Codex CLI, and Pi CLI version context.
304
+ - `/logs [lines]` shows a redacted connector log tail. Admin only.
305
+ - `/diagnostics` shows redacted connector diagnostics. Admin only.
306
+ - `/restart` restarts the connector process. Admin only.
307
+ - `/update` pulls `origin/main`, installs dependencies, checks, tests, builds, and restarts only on success. Admin only.
308
+
309
+ ## Command Examples
310
+
311
+ Switching to an existing thread:
312
+
313
+ ```text
314
+ /sessions
315
+ ```
316
+
317
+ Tap a listed thread/session. The connector imports workspace, model, reasoning/thinking, and provider-specific metadata from the selected agent.
318
+
319
+ Direct session switch:
320
+
321
+ ```text
322
+ /switch 019e178a-f275-7d01-95d6-c244ff3e30ed
323
+ ```
324
+
325
+ Attach an existing CLI session to the current Telegram topic:
326
+
327
+ ```text
328
+ /attach 019e178a-f275-7d01-95d6-c244ff3e30ed
329
+ ```
330
+
331
+ Hand a session back to the native CLI:
332
+
333
+ ```text
334
+ /handback
335
+ ```
336
+
337
+ The bot replies with a command like:
338
+
339
+ ```bash
340
+ cd ~/projects/my-workspace && codex resume 019e178a-f275-7d01-95d6-c244ff3e30ed
341
+ ```
342
+
343
+ For Pi sessions the command looks like:
344
+
345
+ ```bash
346
+ cd ~/projects/my-workspace && pi --session ~/.pi/agent/sessions/.../session.jsonl
347
+ ```
348
+
349
+ Change model:
350
+
351
+ ```text
352
+ /model
353
+ ```
354
+
355
+ Tap the model to use for new or reattached threads.
356
+
357
+ Change reasoning effort:
358
+
359
+ ```text
360
+ /reasoning
361
+ ```
362
+
363
+ For Codex choose one of `minimal`, `low`, `medium`, `high`, or `xhigh`. For Pi choose one of `off`, `minimal`, `low`, `medium`, `high`, or `xhigh`.
364
+
365
+ Toggle fast mode:
366
+
367
+ ```text
368
+ /fast
369
+ /fast on
370
+ /fast off
371
+ ```
372
+
373
+ Fast mode maps to launch profiles: `on` selects an approval policy of `never`, while `off` selects an approval-requesting profile such as Review. If a thread is idle, `/fast` reattaches the current thread with the selected launch behavior immediately.
374
+
375
+ Choose launch profile:
376
+
377
+ ```text
378
+ /launch_profiles
379
+ ```
380
+
381
+ Tap the profile. Unsafe profiles require confirmation before they become active.
382
+
383
+ ## File, Photo, Voice, and Artifact Workflow
384
+
385
+ Text:
386
+
387
+ - Any non-command text message becomes a prompt for the selected agent.
388
+ - While the selected agent works, Telegram shows `typing`.
389
+ - Replies stream back into the same chat or topic.
390
+
391
+ Photos:
392
+
393
+ - Send a photo with or without a caption.
394
+ - The connector downloads it and passes it to the selected agent as local image input.
395
+ - The caption becomes the text prompt when present.
396
+ - Sending multiple photos as a Telegram album creates one combined agent prompt.
397
+
398
+ Documents:
399
+
400
+ - Send a document with or without a caption.
401
+ - The connector downloads it, sanitizes the filename, enforces `MAX_FILE_SIZE`, and stages it under:
402
+
403
+ ```text
404
+ <workspace>/.nordrelay/inbox/<turn-id>/
405
+ ```
406
+
407
+ - The selected agent receives prompt instructions with the staged file paths.
408
+ - The caption becomes the text prompt when present.
409
+ - Document albums and mixed media groups are processed as one turn; oversized files are skipped and reported.
410
+
411
+ Artifacts:
412
+
413
+ - For generated files that should be returned to Telegram, tell the selected agent to write them to:
414
+
415
+ ```text
416
+ <workspace>/.nordrelay/turns/<turn-id>/out/
417
+ ```
418
+
419
+ - The connector stores files in that directory and keeps them available for `/artifacts`.
420
+ - Automatic Telegram artifact delivery is off by default. Set `TELEGRAM_AUTO_SEND_ARTIFACTS=true` to collect and send files right after a turn.
421
+ - When automatic delivery or explicit `/artifacts` sending is used, image outputs are sent with Telegram previews and other outputs are sent as documents.
422
+ - When more than five artifacts are sent, the connector tries to send one ZIP bundle instead of many separate files.
423
+ - Use `/artifacts` to list recent artifact turns with inline Send/ZIP/Delete actions.
424
+ - Use `/artifacts latest`, `/artifacts zip latest`, or `/artifacts <turn-id>` from text commands.
425
+ - Telegram file delivery is capped at the configured `MAX_FILE_SIZE` per artifact or ZIP bundle.
426
+ - Old turn and inbox directories are pruned automatically to keep workspace state compact.
427
+
428
+ Voice and audio:
429
+
430
+ - Send a Telegram voice note or audio file.
431
+ - The connector transcribes it, then sends the transcript to Codex.
432
+ - Local transcription is tried first with `parakeet-coreml` or `faster-whisper` when installed.
433
+ - OpenAI Whisper is used when `OPENAI_API_KEY` is set.
434
+
435
+ Voice prerequisites:
436
+
437
+ ```bash
438
+ # macOS Apple Silicon
439
+ brew install ffmpeg
440
+ npm install parakeet-coreml
441
+ ```
442
+
443
+ ```bash
444
+ # Debian/Ubuntu
445
+ sudo apt-get install ffmpeg
446
+ python3 -m venv .venv
447
+ .venv/bin/python -m pip install --upgrade pip
448
+ .venv/bin/python -m pip install faster-whisper
449
+ ```
450
+
451
+ ```dotenv
452
+ FASTER_WHISPER_PYTHON=.venv/bin/python
453
+ FASTER_WHISPER_MODEL=base
454
+ FASTER_WHISPER_COMPUTE_TYPE=int8
455
+ ```
456
+
457
+ Whisper fallback:
458
+
459
+ ```dotenv
460
+ OPENAI_API_KEY=sk-...
461
+ ```
462
+
463
+ Voice transcription uses `OPENAI_API_KEY`, not `CODEX_API_KEY`.
464
+
465
+ ## Environment Reference
466
+
467
+ Telegram:
468
+
469
+ - `TELEGRAM_BOT_TOKEN`: required BotFather token.
470
+ - `TELEGRAM_ADMIN_USER_IDS`: required comma-separated Telegram user ids allowed to use admin commands. Admin ids are automatically allowed to use the bot.
471
+ - `TELEGRAM_ALLOWED_USER_IDS`: optional comma-separated non-admin Telegram user ids allowed to use the bot.
472
+ - `TELEGRAM_READONLY_USER_IDS`: comma-separated Telegram user ids that can inspect status and sessions but cannot run prompts or mutating commands.
473
+ - `TELEGRAM_ALLOWED_CHAT_IDS`: comma-separated chat ids allowed to use the bot. Group ids may be negative.
474
+ - `TELEGRAM_ALLOW_ANY_CHAT`: allows all chats when `true`. Keep `false` unless you intentionally want an open bot.
475
+ - `TELEGRAM_ROLE_POLICIES_JSON`: optional JSON object mapping roles to permissions. Permissions are `inspect`, `sessions`, `prompt`, `files`, `settings`, `auth`, and `admin`.
476
+ - `TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS`: minimum interval for normal Telegram API sends. Defaults to `80`.
477
+ - `TELEGRAM_EDIT_MIN_INTERVAL_MS`: minimum interval for Telegram message edits. Defaults to `1200`.
478
+ - `TELEGRAM_CLI_MIRROR_MODE`: default CLI mirror mode: `off`, `status`, `final`, or `full`. Defaults to `status`.
479
+ - `TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS`: minimum interval for mirrored CLI status edits. Defaults to `4000`.
480
+ - `TELEGRAM_NOTIFY_MODE`: default notification mode: `off`, `minimal`, or `all`. Defaults to `minimal`.
481
+ - `TELEGRAM_QUIET_HOURS`: optional quiet-hour range in `HH-HH` format, for example `22-7`.
482
+ - `TELEGRAM_REDACT_PATTERNS`: comma-separated regular expressions for additional Telegram/log redaction.
483
+
484
+ Role policy example:
485
+
486
+ ```dotenv
487
+ TELEGRAM_ROLE_POLICIES_JSON={"readonly":["inspect","sessions"],"operator":["inspect","sessions","prompt","files"],"admin":"*"}
488
+ ```
489
+
490
+ Agent selection:
491
+
492
+ - `NORDRELAY_CODEX_ENABLED`: enables Codex contexts. Defaults to `true`.
493
+ - `NORDRELAY_PI_ENABLED`: enables Pi contexts. Defaults to `false`.
494
+ - `NORDRELAY_DEFAULT_AGENT`: `codex` or `pi`, used for new Telegram contexts. Defaults to the first enabled agent.
495
+
496
+ Codex:
497
+
498
+ - `CODEX_API_KEY`: optional API key for Codex SDK auth.
499
+ - `CODEX_CLI_PATH`: optional explicit path to the Codex CLI executable.
500
+ - `CODEX_USE_BUNDLED_CLI`: set `true` to force the SDK-bundled Codex CLI instead of the host `codex` executable.
501
+ - `CODEX_MODEL`: default model for new threads.
502
+ - `CODEX_SYNC_INTERVAL_MS`: periodic local Codex-state sync interval for active Telegram sessions. Defaults to `10000`; set `0` to disable.
503
+ - `CODEX_EXTERNAL_BUSY_CHECK_MS`: how often queued Telegram prompts re-check an active local Codex CLI task. Defaults to `5000`.
504
+ - `CODEX_EXTERNAL_BUSY_STALE_MS`: maximum age for an unclosed rollout task before it is treated as stale instead of active. Defaults to `300000`.
505
+ - `CODEX_SANDBOX_MODE`: default sandbox mode, one of `read-only`, `workspace-write`, `danger-full-access`.
506
+ - `CODEX_APPROVAL_POLICY`: default approval policy, one of `never`, `on-request`, `on-failure`, `untrusted`.
507
+ - `CODEX_LAUNCH_PROFILES_JSON`: JSON array of additional launch profiles.
508
+ - `CODEX_DEFAULT_LAUNCH_PROFILE`: profile id used by default. Defaults to `default`.
509
+ - `ENABLE_UNSAFE_LAUNCH_PROFILES`: set `true` to expose `danger-full-access` profiles.
510
+
511
+ Pi:
512
+
513
+ - `PI_CLI_PATH`: optional explicit path to the Pi CLI executable. Defaults to `pi` on `PATH`.
514
+ - `PI_SESSION_DIR`: optional Pi session directory. Defaults to `~/.pi/agent/sessions/` or `PI_CODING_AGENT_SESSION_DIR`.
515
+ - `PI_DEFAULT_MODEL`: optional default model pattern for new Pi sessions, for example `openai-codex/gpt-5.5`.
516
+ - `PI_DEFAULT_THINKING`: default Pi thinking level: `off`, `minimal`, `low`, `medium`, `high`, or `xhigh`. Defaults to `medium`.
517
+
518
+ Telegram output:
519
+
520
+ - `CONNECTOR_LOG_FORMAT`: `text` or `json`. Defaults to `text`.
521
+ - `TOOL_VERBOSITY`: `all`, `summary`, `errors-only`, or `none`.
522
+ - `SHOW_TURN_TOKEN_USAGE`: appends per-turn token usage when `true`.
523
+ - `ENABLE_TELEGRAM_REACTIONS`: enables Telegram reactions when `true`.
524
+ - `MAX_FILE_SIZE`: maximum inbound Telegram document size in bytes. Defaults to 20 MB.
525
+ - `ARTIFACT_RETENTION_DAYS`: artifact/inbox turn age before pruning. Defaults to `7`.
526
+ - `ARTIFACT_MAX_TURNS`: maximum artifact turn directories to keep per workspace. Defaults to `30`.
527
+ - `ARTIFACT_MAX_INBOX_DIRS`: maximum staged inbox directories to keep per workspace. Defaults to `30`.
528
+ - `ARTIFACT_IGNORE_DIRS`: comma-separated extra directory names or relative paths ignored during workspace artifact scans.
529
+ - `ARTIFACT_IGNORE_GLOBS`: comma-separated glob patterns ignored during workspace artifact scans.
530
+ - `TELEGRAM_AUTO_SEND_ARTIFACTS`: automatically post generated artifact summaries/files after Telegram turns and mirrored CLI turns. Defaults to `false`.
531
+
532
+ Workspace policy:
533
+
534
+ - `WORKSPACE_ALLOWED_ROOTS`: comma-separated root directories allowed for session switching and workspace selection. Empty means unrestricted.
535
+ - `WORKSPACE_WARN_ROOTS`: comma-separated broad roots that should be allowed but warned about in `/session` and `/workspaces`.
536
+
537
+ Auth and voice:
538
+
539
+ - `ENABLE_TELEGRAM_LOGIN`: enables `/login` and `/logout`. Defaults to `true`.
540
+ - `FASTER_WHISPER_PYTHON`: Python executable for local Linux voice transcription. Example: `.venv/bin/python`.
541
+ - `FASTER_WHISPER_MODEL`: faster-whisper model name. Defaults to `base`.
542
+ - `FASTER_WHISPER_DEVICE`: faster-whisper device. Defaults to `cpu`.
543
+ - `FASTER_WHISPER_COMPUTE_TYPE`: faster-whisper compute type. Defaults to `int8`.
544
+ - `FASTER_WHISPER_LANGUAGE`: optional fixed transcription language.
545
+ - `FASTER_WHISPER_TIMEOUT_MS`: local transcription timeout. Defaults to `600000`.
546
+ - `OPENAI_API_KEY`: enables Whisper transcription fallback for voice/audio.
547
+ - `VOICE_PREFERRED_BACKEND`: `auto`, `parakeet`, `faster-whisper`, or `openai`. Defaults to `auto`.
548
+ - `VOICE_DEFAULT_LANGUAGE`: optional default language code, for example `de` or `en`.
549
+ - `VOICE_TRANSCRIBE_ONLY`: when `true`, voice/audio messages are transcribed but not sent to Codex.
550
+
551
+ NordRelay wrapper:
552
+
553
+ - `NORDRELAY_HOME`: state/log directory override. Defaults to `~/.codex/nordrelay`.
554
+ - `NORDRELAY_SOURCE_ROOT`: runtime source root override. Useful when the plugin is launched from Codex cache.
555
+ - `NORDRELAY_KEEP_PENDING_UPDATES`: set true to avoid dropping pending Telegram updates on start.
556
+ - `NORDRELAY_FORWARD_TOOL_OUTPUT`: backward-compatible alias that sets `TOOL_VERBOSITY=all` when `TOOL_VERBOSITY` is unset.
557
+ - `NORDRELAY_STATE_FILE`: internal state-file path passed by the wrapper.
558
+ - `NORDRELAY_WRAPPER_PID`: internal wrapper PID passed to the runtime.
559
+ - `NORDRELAY_DROP_PENDING_UPDATES`: internal polling startup flag.
560
+
561
+ ## Launch Profiles
562
+
563
+ Built-in profiles:
564
+
565
+ - `default`: uses `CODEX_SANDBOX_MODE` and `CODEX_APPROVAL_POLICY`.
566
+ - `readonly`: `read-only` with `never`.
567
+ - `review`: `workspace-write` with `on-request`.
568
+ - `full-access`: `danger-full-access` with `never`, only when unsafe profiles are enabled.
569
+
570
+ Custom profile example:
571
+
572
+ ```dotenv
573
+ CODEX_LAUNCH_PROFILES_JSON=[{"id":"review-safe","label":"Review Safe","sandboxMode":"workspace-write","approvalPolicy":"on-request"}]
574
+ CODEX_DEFAULT_LAUNCH_PROFILE=review-safe
575
+ ```
576
+
577
+ Multiple profiles:
578
+
579
+ ```dotenv
580
+ CODEX_LAUNCH_PROFILES_JSON=[{"id":"readonly-audit","label":"Readonly Audit","sandboxMode":"read-only","approvalPolicy":"never"},{"id":"interactive","label":"Interactive","sandboxMode":"workspace-write","approvalPolicy":"on-request"}]
581
+ ```
582
+
583
+ Unsafe full-access profile:
584
+
585
+ ```dotenv
586
+ ENABLE_UNSAFE_LAUNCH_PROFILES=true
587
+ CODEX_LAUNCH_PROFILES_JSON=[{"id":"host-full","label":"Host Full Access","sandboxMode":"danger-full-access","approvalPolicy":"never"}]
588
+ ```
589
+
590
+ Unsafe profiles are intentionally gated. Telegram asks for confirmation before applying them.
591
+
592
+ ## Security Notes
593
+
594
+ - Always set `TELEGRAM_ADMIN_USER_IDS`; a fresh install refuses to start without at least one admin user id.
595
+ - Prefer `TELEGRAM_ADMIN_USER_IDS` and `TELEGRAM_ALLOWED_USER_IDS` over `TELEGRAM_ALLOWED_CHAT_IDS` for private bots.
596
+ - Use `TELEGRAM_ALLOWED_CHAT_IDS` for groups or forum topics only when you trust the entire chat.
597
+ - Do not leave `TELEGRAM_ALLOW_ANY_CHAT=true` enabled after setup.
598
+ - Treat `danger-full-access` as equivalent to shell access on the host.
599
+ - Treat uploaded files as untrusted input. They are staged inside the active workspace so the selected sandbox policy still matters.
600
+ - Keep `CODEX_API_KEY` and `OPENAI_API_KEY` in `.env` or host secret management; `.env` is gitignored.
601
+ - In group chats, remember that any allowed user can prompt Codex in that chat context.
602
+ - Use `TOOL_VERBOSITY=summary` or `errors-only` when command output may include sensitive data.
603
+ - Review and unsafe launch profiles add a Telegram approve/deny gate before each turn starts.
604
+
605
+ ## Troubleshooting
606
+
607
+ Polling conflict:
608
+
609
+ - Symptom: Telegram reports conflict or only one connector receives messages.
610
+ - Cause: the same bot token is being polled by another process.
611
+ - Fix: stop the other process or run `npm stop`, then `npm start`.
612
+
613
+ Stale plugin cache:
614
+
615
+ - Symptom: Codex uses old command or skill text after a repo update.
616
+ - Fix: reinstall/update the local marketplace or copy the plugin directory into the Codex plugin cache.
617
+ - Current local cache path: `~/.codex/plugins/cache/nordrelay-local/nordrelay/<version>/`.
618
+
619
+ Missing dependencies:
620
+
621
+ - Symptom: startup says runtime is missing.
622
+ - Fix:
623
+
624
+ ```bash
625
+ npm install
626
+ npm run build
627
+ ```
628
+
629
+ Auth failures:
630
+
631
+ - Symptom: prompt execution says Codex is not authenticated.
632
+ - Fix: run `codex login` on the host, use `/login`, or set `CODEX_API_KEY`.
633
+ - Use `/auth` to check the current auth method.
634
+
635
+ No sessions listed:
636
+
637
+ - Symptom: `/sessions` says no recent threads found.
638
+ - Cause for Codex: `~/.codex/state_*.sqlite` is missing, unreadable, or has no active threads.
639
+ - Cause for Pi: `~/.pi/agent/sessions/` or `PI_SESSION_DIR` is missing, unreadable, or has no session JSONL files.
640
+ - Fix: run the selected agent locally once, resume or create a session, then try `/sessions` again.
641
+
642
+ Wrong model, reasoning, or fast mode after switching:
643
+
644
+ - The connector reads model, reasoning, sandbox, and approval policy from Codex state on `/sessions`, `/switch`, `/attach`, and `/session`; fast mode is read from `~/.codex/config.toml`.
645
+ - For Pi, the connector reads model/thinking from Pi JSONL sessions and refreshes active RPC state when a session is running.
646
+ - If values look stale, make sure the selected local CLI has finished writing session state.
647
+
648
+ Pi not available:
649
+
650
+ - Symptom: `/agent` cannot switch to Pi, or startup says Pi CLI is missing.
651
+ - Fix: install Pi from https://pi.dev/, ensure `pi` is on `PATH`, or set `PI_CLI_PATH`.
652
+ - Enable Pi with `NORDRELAY_PI_ENABLED=true`.
653
+
654
+ Voice not working:
655
+
656
+ - Run `/voice` to list available backends.
657
+ - Install `ffmpeg` and `faster-whisper` on Linux, install `parakeet-coreml` on macOS Apple Silicon, or set `OPENAI_API_KEY`.
658
+ - Check `~/.codex/nordrelay/nordrelay.log` for transcription errors.
659
+
660
+ Files not returned:
661
+
662
+ - Ensure Codex writes generated files to `.nordrelay/turns/<turn-id>/out/`.
663
+ - Files over 50 MB are skipped.
664
+ - Hidden files, temp files, and directories are ignored.
665
+ - Use `ARTIFACT_IGNORE_DIRS` and `ARTIFACT_IGNORE_GLOBS` to suppress project-specific build/cache output.
666
+ - Automatic artifact sending stays off unless `TELEGRAM_AUTO_SEND_ARTIFACTS=true`; `/artifacts` can still list and resend indexed outputs.
667
+
668
+ ## Deployment
669
+
670
+ Foreground debugging:
671
+
672
+ ```bash
673
+ npm run foreground
674
+ ```
675
+
676
+ Background process:
677
+
678
+ ```bash
679
+ npm start
680
+ npm run status
681
+ npm stop
682
+ ```
683
+
684
+ Docker Compose:
685
+
686
+ ```bash
687
+ docker compose up -d --build
688
+ docker compose logs -f
689
+ docker compose down
690
+ ```
691
+
692
+ The compose file mounts:
693
+
694
+ - `${HOME}/.codex` into the container for Codex auth and thread state.
695
+ - `./workspace` as `/workspace` for container workspaces.
696
+
697
+ launchd helper:
698
+
699
+ ```bash
700
+ NORDRELAY_SOURCE_ROOT=~/projects/nordrelay launchd/start.sh
701
+ ```
702
+
703
+ Linux systemd example:
704
+
705
+ ```ini
706
+ [Unit]
707
+ Description=NordRelay
708
+ After=network-online.target
709
+
710
+ [Service]
711
+ Type=simple
712
+ WorkingDirectory=/opt/nordrelay
713
+ Environment=NORDRELAY_SOURCE_ROOT=/opt/nordrelay
714
+ ExecStart=/usr/bin/node dist/index.js
715
+ Restart=on-failure
716
+ RestartSec=5
717
+
718
+ [Install]
719
+ WantedBy=multi-user.target
720
+ ```
721
+
722
+ Build before starting a service:
723
+
724
+ ```bash
725
+ npm install
726
+ npm run build
727
+ ```
728
+
729
+ ## Architecture
730
+
731
+ - `plugins/nordrelay/`: Codex plugin bundle with manifest, skill, command, icon, and process wrapper.
732
+ - `plugins/nordrelay/scripts/nordrelay.mjs`: process manager for `start`, `stop`, `restart`, `status`, and `foreground`.
733
+ - `src/index.ts`: runtime entrypoint, config load, auth check, state-file writes, polling lifecycle, shutdown.
734
+ - `src/bot.ts`: Telegram command handlers, callbacks, message streaming, file/photo/voice handling, artifacts, and error handling.
735
+ - `src/bot-preferences.ts`: per-context mirror, notification, quiet-hour, and voice preference persistence.
736
+ - `src/telegram-rate-limit.ts`: centralized Telegram API send/edit/document rate limiting and retry-after tracking.
737
+ - `src/persistence.ts`: atomic JSON/text writes with backup recovery.
738
+ - `src/redaction.ts`: common secret redaction and custom redaction pattern support.
739
+ - `src/workspace-policy.ts`: workspace allow/warn root evaluation.
740
+ - `src/access-control.ts`: Telegram role permissions and command/callback permission mapping.
741
+ - `src/codex-session.ts`: Codex SDK service for new/resumed threads, streaming events, abort, model, reasoning, launch profiles, and handback.
742
+ - `src/session-registry.ts`: per-chat/topic session registry and persisted context metadata.
743
+ - `src/session-format.ts`: compact Telegram rendering for session details, token usage, and limits.
744
+ - `src/codex-state.ts`: reader for Codex `~/.codex/state_*.sqlite` thread, workspace, model, reasoning, sandbox, and approval metadata.
745
+ - `src/attachments.ts`: inbound file staging and artifact output path construction.
746
+ - `src/artifacts.ts`: generated artifact discovery, ZIP bundling, retention, and Telegram delivery filtering.
747
+ - `src/voice.ts`: audio decoding and transcription backend selection.
748
+ - `src/format.ts`: Telegram-safe HTML formatting and markdown conversion.
749
+ - `src/error-messages.ts`: user-facing error translation.