@nordbyte/nordrelay 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.env.example +35 -0
  2. package/README.md +109 -49
  3. package/dist/activity-events.js +2 -2
  4. package/dist/adapter-conformance.js +61 -0
  5. package/dist/bot.js +18 -31
  6. package/dist/channel-adapter.js +33 -6
  7. package/dist/channel-command-catalog.js +6 -0
  8. package/dist/channel-command-core.js +60 -0
  9. package/dist/channel-command-service.js +20 -4
  10. package/dist/channel-mirror-registry.js +9 -2
  11. package/dist/channel-prompt-engine.js +177 -0
  12. package/dist/channel-turn-lifecycle.js +73 -0
  13. package/dist/config-metadata.js +67 -8
  14. package/dist/config.js +48 -1
  15. package/dist/context-key.js +32 -0
  16. package/dist/discord-bot.js +99 -327
  17. package/dist/index.js +9 -0
  18. package/dist/metrics.js +2 -0
  19. package/dist/peer-client.js +33 -1
  20. package/dist/peer-readiness.js +77 -0
  21. package/dist/peer-runtime-service.js +22 -0
  22. package/dist/peer-store.js +13 -0
  23. package/dist/relay-runtime-helpers.js +3 -1
  24. package/dist/relay-runtime.js +7 -0
  25. package/dist/settings-wizard-test.js +216 -0
  26. package/dist/slack-artifacts.js +165 -0
  27. package/dist/slack-bot.js +1461 -0
  28. package/dist/slack-channel-runtime.js +147 -0
  29. package/dist/slack-command-surface.js +46 -0
  30. package/dist/slack-diagnostics.js +116 -0
  31. package/dist/slack-rate-limit.js +139 -0
  32. package/dist/user-management-crypto.js +38 -0
  33. package/dist/user-management-normalize.js +188 -0
  34. package/dist/user-management-types.js +1 -0
  35. package/dist/user-management.js +193 -196
  36. package/dist/web-api-contract.js +8 -0
  37. package/dist/web-dashboard-access-routes.js +62 -0
  38. package/dist/web-dashboard-assets.js +1 -0
  39. package/dist/web-dashboard-pages.js +14 -4
  40. package/dist/web-dashboard-peer-routes.js +32 -11
  41. package/dist/web-dashboard.js +34 -0
  42. package/dist/web-state.js +2 -2
  43. package/dist/webui-assets/dashboard.css +193 -0
  44. package/dist/webui-assets/dashboard.js +544 -144
  45. package/package.json +3 -1
  46. package/plugins/nordrelay/scripts/nordrelay.mjs +101 -10
package/.env.example CHANGED
@@ -56,6 +56,41 @@ DISCORD_QUIET_HOURS=
56
56
  # Optional Discord override for automatic artifact summaries/uploads.
57
57
  DISCORD_AUTO_SEND_ARTIFACTS=
58
58
 
59
+ # Slack
60
+ # Slack bot settings. Slack is opt-in and uses the same NordRelay users, groups, and permissions as Telegram and Discord.
61
+ # Start the Slack bot adapter.
62
+ SLACK_ENABLED=false
63
+ # Slack bot token.
64
+ SLACK_BOT_TOKEN=
65
+ # Slack app-level token for Socket Mode.
66
+ SLACK_APP_TOKEN=
67
+ # Slack signing secret for HTTP Events mode.
68
+ SLACK_SIGNING_SECRET=
69
+ # Use Slack Socket Mode instead of an HTTP events receiver.
70
+ SLACK_SOCKET_MODE=true
71
+ # HTTP port used when Slack Socket Mode is disabled.
72
+ SLACK_PORT=3000
73
+ # Optional comma-separated Slack team/workspace allow-list.
74
+ SLACK_ALLOWED_TEAM_IDS=
75
+ # Optional comma-separated Slack channel allow-list before user/group checks.
76
+ SLACK_ALLOWED_CHANNEL_IDS=
77
+ # Read regular Slack text messages as prompts.
78
+ SLACK_MESSAGE_CONTENT_ENABLED=true
79
+ # Slash command configured in Slack.
80
+ SLACK_COMMAND=/nordrelay
81
+ # Optional Slack override for CLI mirror mode. Uses the NordRelay default when unset.
82
+ # Options: off, status, final, full
83
+ SLACK_CLI_MIRROR_MODE=
84
+ # Optional Slack override for mirrored edit interval.
85
+ SLACK_CLI_MIRROR_MIN_UPDATE_MS=
86
+ # Optional Slack override for completion notifications.
87
+ # Options: off, minimal, all
88
+ SLACK_NOTIFY_MODE=
89
+ # Optional Slack quiet hours override. Use HH-HH, off, or leave blank for default.
90
+ SLACK_QUIET_HOURS=
91
+ # Optional Slack override for automatic artifact summaries/uploads.
92
+ SLACK_AUTO_SEND_ARTIFACTS=
93
+
59
94
  # Agents
60
95
  # Agent access. Codex is enabled by default; Pi, Hermes, OpenClaw, and Claude Code are opt-in.
61
96
  # Allow Codex sessions.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # NordRelay
2
2
 
3
- NordRelay is a remote control plane for coding agents across messaging channels and paired NordRelay instances. The current implementation connects Codex, Pi, Hermes, OpenClaw, and Claude Code coding-agent sessions to Telegram, Discord, the WebUI, and trusted peer nodes, keeps independent sessions per chat, thread, forum topic, DM, or remote target, streams replies and tool activity back to the active channel, supports files, photos, voice input, model controls, session browsing, retry/abort, CLI handback, and scoped multi-host control.
3
+ NordRelay is a remote control plane for coding agents across messaging channels and paired NordRelay instances. The current implementation connects Codex, Pi, Hermes, OpenClaw, and Claude Code coding-agent sessions to Telegram, Discord, Slack, the WebUI, and trusted peer nodes, keeps independent sessions per chat, thread, forum topic, DM, or remote target, streams replies and tool activity back to the active channel, supports files, photos, voice input, model controls, session browsing, retry/abort, CLI handback, and scoped multi-host control.
4
4
 
5
5
  The repo is both a local Codex marketplace and a standalone Node app. The plugin lives in `plugins/nordrelay/`; the full bot runtime lives in `src/` and uses `@openai/codex-sdk` for Codex, Pi RPC mode for Pi, the Hermes API Server for Hermes, the OpenClaw Gateway WebSocket RPC surface for OpenClaw, and the Claude Agent SDK for Claude Code.
6
6
 
@@ -8,46 +8,49 @@ The repo is both a local Codex marketplace and a standalone Node app. The plugin
8
8
 
9
9
  Session control:
10
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, Pi, Hermes, OpenClaw, and Claude Code.
13
- - Persistent Telegram context metadata in the active workspace under `.nordrelay/contexts.json`.
11
+ - Independent coding-agent sessions per Telegram private chat, group chat, forum topic, Discord DM/channel/thread, Slack DM/channel/thread, WebUI, and peer target.
12
+ - `/agent` switches a chat context between enabled agents such as Codex, Pi, Hermes, OpenClaw, and Claude Code.
13
+ - Persistent channel context metadata in the active workspace under `.nordrelay/contexts.json`.
14
14
  - `/new` starts a fresh thread, with workspace selection when known workspaces are available.
15
15
  - `/session` shows thread id, workspace, launch profile, launch behavior, model, reasoning, fast mode, context usage, token totals, and subscription limit remaining percentages.
16
16
  - `/sessions` opens a paginated browser for recent sessions from the selected agent.
17
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.
18
+ - `/sync` manually refreshes the active chat session from local CLI state when the selected agent supports state watching.
19
19
  - `/pin`, `/unpin`, and `/pinned` keep important threads at the top of Telegram session browsing.
20
20
  - `/switch <session-id>` switches directly to an existing session.
21
21
  - `/attach <session-id>` binds an existing agent session to the current chat or topic.
22
22
  - Existing thread metadata is imported on switch/attach, including model, reasoning effort, sandbox mode, and approval policy.
23
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
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.
25
+ - `/retry` resends the last prompt for the current chat context.
26
+ - `/queue`, inline run/top/up/down/cancel buttons, `/cancel <queue-id>`, and `/clearqueue` manage queued prompts for a busy chat context.
27
27
  - `/queue later <minutes> <prompt>` schedules a prompt for later execution, and `/queue inspect <queue-id>` shows full queue metadata.
28
28
  - `/abort`, `/stop`, and the inline Abort button cancel the active agent turn.
29
- - Busy prompts are queued per Telegram context instead of being dropped.
30
- - If the attached thread is currently active in the local agent CLI, Telegram prompts are queued until that CLI task finishes.
31
- - Active Codex, Pi, Hermes, OpenClaw, and Claude Code CLI/API turns are mirrored into Telegram with configurable `off`, `status`, `final`, or `full` modes.
32
- - `/mirror` controls CLI mirroring per Telegram context.
29
+ - Busy prompts are queued per chat context instead of being dropped.
30
+ - If the attached thread is currently active in the local agent CLI, chat prompts are queued until that CLI task finishes.
31
+ - Active Codex, Pi, Hermes, OpenClaw, and Claude Code CLI/API turns are mirrored into Telegram, Discord, and Slack with configurable `off`, `status`, `final`, or `full` modes.
32
+ - `/mirror` controls CLI mirroring per chat context.
33
33
  - Queues survive connector restarts and are resumed automatically when the external CLI turn becomes idle.
34
- - `/notify` controls completion/status notifications and quiet hours per Telegram context.
34
+ - `/notify` controls completion/status notifications and quiet hours per chat context.
35
35
  - `/workspaces` lists allowed workspaces and shows workspace guardrail warnings.
36
- - `/status`, `/health`, and `/version` report connector runtime health from Telegram.
36
+ - `/status`, `/health`, and `/version` report connector runtime health from chat adapters.
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, user/group authorization, Telegram rate-limit, mirror, voice, session, queue, and progress details.
39
+ - `/diagnostics` reports redacted runtime, config, user/group authorization, channel rate-limit, mirror, voice, session, queue, and progress details.
40
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.
41
41
  - `/lock`, `/unlock`, and `/locks` provide a team write-lock for shared sessions so one user can operate while others watch.
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
+ - `/audit` shows recent prompt, queue, lock, command, authentication, permission-denied, user, group, Telegram-link, Telegram-chat, Discord-link, Discord-channel, Slack-link, Slack-channel, and web-session audit events for admins.
43
43
 
44
44
  Adapter architecture:
45
45
 
46
46
  - Telegram supports text, typing, streaming edits, inline buttons, files, photos, voice, forum topics, and polling/webhook transport.
47
47
  - Discord supports text, typing, streaming edits, buttons, files, photos, voice/audio transcription, guild channels, threads, DMs, message commands, and slash commands.
48
- - `/channels` shows available and planned messaging adapters for Telegram, Discord, WhatsApp, Slack, and Matrix.
48
+ - Slack supports text, typing/status, streaming edits, Block Kit buttons, files, images, audio transcription, channels, DMs, threads, Socket Mode, and HTTP Events mode.
49
+ - Slack startup and `/diagnostics` include readiness checks for token/transport configuration, registered channels, Slack API auth probes, channel visibility probes, file-upload readiness notes, and rate-limit counters.
50
+ - `/channels` shows available and planned messaging adapters for Telegram, Discord, Slack, WhatsApp, and Matrix.
49
51
  - Codex, Pi, Hermes, OpenClaw, and Claude Code are implemented as agent adapters.
50
52
  - `/agents` shows available/planned agent adapters and whether Codex, Pi, Hermes, OpenClaw, and Claude Code are enabled.
53
+ - Agent and chat adapters expose a shared conformance matrix so command coverage and feature support can be tested and surfaced consistently.
51
54
  - Shared command-action renderers and a channel runtime contract keep inbound commands, outbound messages, typing, files, inline actions, and streaming-ready delivery separate from channel-specific API calls.
52
55
 
53
56
  Peer federation:
@@ -58,8 +61,8 @@ Peer federation:
58
61
  - Peer scopes restrict which remote WebUI/API actions are allowed, including read, prompt, queue, file, diagnostic, log, and session permissions.
59
62
  - Peer records can also restrict allowed agent ids, allowed workspace roots, and per-peer workspace aliases such as `app=/srv/app`.
60
63
  - The WebUI has a Peers page plus a local/remote target selector; dashboard pages, SSE streaming, queue actions, artifact downloads, health checks, and the global session browser proxy through the selected peer when allowed.
61
- - Telegram and Discord expose `/peers` and `/target` so a linked user can choose whether prompts run locally or on a paired NordRelay instance.
62
- - Remote prompts stream text, tool status, turn completion, and errors back to the originating Telegram or Discord context.
64
+ - Telegram, Discord, and Slack expose `/peers` and `/target` so a linked user can choose whether prompts run locally or on a paired NordRelay instance.
65
+ - Remote prompts stream text, tool status, turn completion, and errors back to the originating Telegram, Discord, or Slack context.
63
66
 
64
67
  Codex runtime:
65
68
 
@@ -70,18 +73,18 @@ Codex runtime:
70
73
  - Supports launch profiles through `/launch_profiles` and `/launch`.
71
74
  - Built-in launch profiles include Default, Read Only, Review, and optional Full Access.
72
75
  - Custom launch profiles can be configured with `CODEX_LAUNCH_PROFILES_JSON`.
73
- - Unsafe `danger-full-access` profiles require `ENABLE_UNSAFE_LAUNCH_PROFILES=true` and Telegram confirmation.
74
- - Review or unsafe launch profiles require an inline Telegram approval before each prompt is executed.
76
+ - Unsafe `danger-full-access` profiles require `ENABLE_UNSAFE_LAUNCH_PROFILES=true` and channel confirmation.
77
+ - Review or unsafe launch profiles require an inline channel approval before each prompt is executed.
75
78
  - Fast mode can be toggled with `/fast` and mirrors Codex's `fast_default_opt_out` setting from `~/.codex/config.toml`.
76
- - Active Telegram sessions periodically sync model, reasoning, workspace, launch metadata, and fast-mode defaults from local agent state where supported.
77
- - Active local Codex CLI tasks are detected from rollout JSONL files so Telegram does not race the CLI on the same thread.
79
+ - Active chat sessions periodically sync model, reasoning, workspace, launch metadata, and fast-mode defaults from local agent state where supported.
80
+ - Active local Codex CLI tasks are detected from rollout JSONL files so chat adapters do not race the CLI on the same thread.
78
81
  - `/diagnostics` includes rollout path, activity status, stale/idle reason, line count, and last update time.
79
82
  - Optional per-turn token usage footer with `SHOW_TURN_TOKEN_USAGE=true`.
80
83
 
81
84
  Pi runtime:
82
85
 
83
86
  - Pi support is opt-in with `NORDRELAY_PI_ENABLED=true`.
84
- - The default Telegram agent is selected with `NORDRELAY_DEFAULT_AGENT=codex`, `pi`, `hermes`, `openclaw`, or `claude-code`.
87
+ - The default chat agent is selected with `NORDRELAY_DEFAULT_AGENT=codex`, `pi`, `hermes`, `openclaw`, or `claude-code`.
85
88
  - Pi sessions are driven through official `pi --mode rpc` JSONL commands and events.
86
89
  - Existing Pi sessions are discovered from `~/.pi/agent/sessions/` or `PI_SESSION_DIR`.
87
90
  - `/sessions`, `/switch`, `/attach`, `/new`, `/session`, `/handback`, `/model`, `/reasoning`, `/abort`, `/stop`, `/retry`, `/queue`, files, photos, and voice input work for Pi contexts.
@@ -89,15 +92,15 @@ Pi runtime:
89
92
  - Pi thinking levels use `/reasoning` and support `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`.
90
93
  - Pi token and context stats are read through `get_session_stats` when an RPC session is active.
91
94
  - Pi launch profiles expose CLI safety modes such as default, read-only tools, no tools, offline, and safe offline.
92
- - Pi external CLI activity is detected from Pi session JSONL files so Telegram/WebUI prompts queue while the same Pi session is busy.
93
- - Pi CLI turns can be mirrored into Telegram/WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
95
+ - Pi external CLI activity is detected from Pi session JSONL files so chat/WebUI prompts queue while the same Pi session is busy.
96
+ - Pi CLI turns can be mirrored into chat adapters and the WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
94
97
  - Pi provider auth checks report the environment variables expected for the selected provider.
95
98
  - Codex-only subscription limit percentages remain Codex-specific; Pi reports token/context stats when available.
96
99
 
97
100
  Hermes runtime:
98
101
 
99
102
  - Hermes support is opt-in with `NORDRELAY_HERMES_ENABLED=true`.
100
- - The default Telegram agent can be set with `NORDRELAY_DEFAULT_AGENT=hermes`.
103
+ - The default chat agent can be set with `NORDRELAY_DEFAULT_AGENT=hermes`.
101
104
  - Hermes turns are executed through the Hermes API Server `/v1/runs` endpoint and streamed through `/v1/runs/{run_id}/events`.
102
105
  - `/abort` and `/stop` use the Hermes run stop endpoint when a NordRelay-started Hermes run is active.
103
106
  - Existing Hermes sessions are discovered from `~/.hermes/state.db`, or from `HERMES_STATE_DB_PATH` when configured.
@@ -105,14 +108,14 @@ Hermes runtime:
105
108
  - Hermes model selection uses `/v1/models` when the API Server is reachable and falls back to the selected/default model.
106
109
  - Hermes reasoning uses `/reasoning` and supports `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`.
107
110
  - Hermes launch profiles include `default`, `safe`, `readonly`, and `yolo`; profiles map to run instructions and Hermes approval responses.
108
- - Hermes external activity is detected from `state.db`, so Telegram/WebUI prompts queue while the same Hermes session has an unfinished CLI turn.
109
- - Hermes CLI/API turns can be mirrored into Telegram/WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
111
+ - Hermes external activity is detected from `state.db`, so chat/WebUI prompts queue while the same Hermes session has an unfinished CLI turn.
112
+ - Hermes CLI/API turns can be mirrored into chat adapters and the WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
110
113
  - `/auth` checks that the Hermes API Server is reachable and that `HERMES_API_KEY` is usable when configured.
111
114
 
112
115
  OpenClaw runtime:
113
116
 
114
117
  - OpenClaw support is opt-in with `NORDRELAY_OPENCLAW_ENABLED=true`.
115
- - The default Telegram agent can be set with `NORDRELAY_DEFAULT_AGENT=openclaw`.
118
+ - The default chat agent can be set with `NORDRELAY_DEFAULT_AGENT=openclaw`.
116
119
  - OpenClaw turns are executed through the OpenClaw Gateway WebSocket RPC endpoint configured by `OPENCLAW_GATEWAY_URL`.
117
120
  - `/abort` and `/stop` call the OpenClaw Gateway cancel method when a NordRelay-started OpenClaw run is active.
118
121
  - Existing OpenClaw sessions are discovered from `openclaw sessions --all-agents --json`, or from the state directory configured with `OPENCLAW_HOME` or `OPENCLAW_STATE_DIR`.
@@ -120,22 +123,22 @@ OpenClaw runtime:
120
123
  - OpenClaw model selection uses the Gateway `models.list` method when reachable and falls back to `openclaw models list --json`.
121
124
  - OpenClaw thinking uses `/reasoning` and supports `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`.
122
125
  - OpenClaw launch profiles include `default`, `safe`, `readonly`, `local`, and `deliver`; profiles map to Gateway run flags and additional instructions.
123
- - OpenClaw external activity is detected from OpenClaw session state, so Telegram/WebUI prompts queue while the same OpenClaw session has an unfinished CLI turn.
124
- - OpenClaw Gateway turns can be mirrored into Telegram/WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
126
+ - OpenClaw external activity is detected from OpenClaw session state, so chat/WebUI prompts queue while the same OpenClaw session has an unfinished CLI turn.
127
+ - OpenClaw Gateway turns can be mirrored into chat adapters and the WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
125
128
  - `/auth` checks that the OpenClaw Gateway is reachable and that `OPENCLAW_GATEWAY_TOKEN` or `OPENCLAW_GATEWAY_PASSWORD` is usable when configured.
126
129
 
127
130
  Claude Code runtime:
128
131
 
129
132
  - Claude Code support is opt-in with `NORDRELAY_CLAUDE_CODE_ENABLED=true`.
130
- - The default Telegram agent can be set with `NORDRELAY_DEFAULT_AGENT=claude-code`.
133
+ - The default chat agent can be set with `NORDRELAY_DEFAULT_AGENT=claude-code`.
131
134
  - Claude Code turns are executed through `@anthropic-ai/claude-agent-sdk`, using the host `claude` executable when available and the SDK bundled runtime otherwise.
132
135
  - Existing Claude Code sessions are discovered from `~/.claude/projects/`, or from `CLAUDE_CONFIG_DIR/projects` when configured.
133
136
  - `/sessions`, `/switch`, `/attach`, `/new`, `/session`, `/handback`, `/model`, `/reasoning`, `/abort`, `/stop`, `/retry`, `/queue`, files, photos, and voice input work for Claude Code contexts.
134
137
  - Claude Code model selection exposes common aliases and model ids; explicit values from existing sessions are preserved.
135
138
  - Claude Code effort uses `/reasoning` and supports `off`, `low`, `medium`, `high`, and `xhigh`.
136
139
  - Claude Code launch profiles include `default`, `accept-edits`, `plan`, `readonly`, `no-tools`, and optional `bypass-permissions`.
137
- - Claude Code external activity is detected from transcript JSONL files, so Telegram/WebUI prompts queue while the same Claude Code session has an unfinished CLI turn.
138
- - Claude Code SDK turns can be mirrored into Telegram/WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
140
+ - Claude Code external activity is detected from transcript JSONL files, so chat/WebUI prompts queue while the same Claude Code session has an unfinished CLI turn.
141
+ - Claude Code SDK turns can be mirrored into chat adapters and the WebUI with status, tool activity, final answers, activity timelines, diagnostics, and generated artifact discovery.
139
142
  - `/auth` checks the host Claude Code CLI auth state when `claude auth status` is available.
140
143
 
141
144
  Telegram input:
@@ -182,22 +185,33 @@ Discord input and output:
182
185
  - Discord buttons cover session picks, model/reasoning/launch picks, queue actions, artifact actions, update jobs, and abort where Discord component limits allow.
183
186
  - Discord slash commands mirror the Telegram command surface where Discord supports it: `/agent`, `/auth`, `/login`, `/logout`, `/session`, `/sessions`, `/new`, `/switch`, `/attach`, `/handback`, `/workspaces`, `/pin`, `/unpin`, `/pinned`, `/model`, `/reasoning`, `/fast`, `/launch`, `/launch_profiles`, `/queue`, `/stop`, `/retry`, `/sync`, `/progress`, `/activity`, `/audit`, `/artifacts`, `/logs`, `/version`, `/diagnostics`, `/restart`, `/update`, `/lock`, `/unlock`, `/mirror`, `/notify`, `/voice`, `/link`, `/whoami`, and `/register_channel`.
184
187
 
188
+ Slack input and output:
189
+
190
+ - Enable Slack with `SLACK_ENABLED=true`, `SLACK_BOT_TOKEN`, and `SLACK_APP_TOKEN` for Socket Mode. If Socket Mode is disabled, set `SLACK_SIGNING_SECRET` and expose the Slack HTTP receiver.
191
+ - `SLACK_MESSAGE_CONTENT_ENABLED=true` lets regular Slack messages become prompts. Keep it disabled if you only want slash-command control.
192
+ - Slack DMs, channels, and message threads get independent NordRelay contexts.
193
+ - Slack files are staged like Telegram/Discord uploads; images are passed as image inputs and audio files are transcribed before prompting.
194
+ - Slack Block Kit buttons cover session picks, model/reasoning/launch picks, queue actions, artifact actions, update jobs, and abort where Slack component limits allow.
195
+ - Slack slash/text commands mirror the shared command surface where Slack supports it: `/agent`, `/auth`, `/login`, `/logout`, `/session`, `/sessions`, `/new`, `/switch`, `/attach`, `/handback`, `/workspaces`, `/pin`, `/unpin`, `/pinned`, `/model`, `/reasoning`, `/fast`, `/launch`, `/launch_profiles`, `/queue`, `/stop`, `/retry`, `/sync`, `/progress`, `/activity`, `/audit`, `/artifacts`, `/logs`, `/version`, `/diagnostics`, `/restart`, `/update`, `/lock`, `/unlock`, `/mirror`, `/notify`, `/voice`, `/link`, `/whoami`, and `/register_channel`.
196
+
185
197
  Authentication and safety:
186
198
 
187
199
  - WebUI login is required for every dashboard page, API route, SSE stream, artifact download, and health endpoint.
188
- - Access is managed through NordRelay users, groups, permissions, web sessions, linked Telegram identities, and linked Discord identities.
189
- - Built-in groups are `Admin`, `User`, and `Read Only`; custom groups can be created in the WebUI and can restrict allowed agents, workspace roots, Telegram chats, and Discord channels.
200
+ - Access is managed through NordRelay users, groups, permissions, web sessions, linked Telegram identities, linked Discord identities, and linked Slack identities.
201
+ - Built-in groups are `Admin`, `User`, and `Read Only`; custom groups can be created in the WebUI and can restrict allowed agents, workspace roots, Telegram chats, Discord channels, and Slack channels.
190
202
  - The last active admin cannot be disabled or demoted, and web sessions are revoked when passwords or group memberships change.
191
203
  - Admins can review and revoke active WebUI sessions from the Users page.
192
204
  - Telegram private chats require a linked active NordRelay user.
193
205
  - Telegram group and forum chats must be registered before use; admins can run `/register_chat` in the chat or enable chats in the WebUI.
194
206
  - Discord DMs require a linked active NordRelay user.
195
207
  - Discord guild channels and threads must be registered before use; admins can run `/register_channel` in the channel or enable channels in the WebUI.
208
+ - Slack DMs require a linked active NordRelay user.
209
+ - Slack channels and threads must be registered before use; admins can run `/register_channel` in the channel or enable channels in the WebUI.
196
210
  - `/whoami` shows the linked NordRelay account and groups.
197
211
  - `/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`.
198
- - `/link <code>` also links a Discord account when the code was created as a Discord link code.
212
+ - `/link <code>` also links a Discord or Slack account when the code was created for that channel.
199
213
  - WebUI login and chat-account link attempts are rate-limited to reduce brute-force risk.
200
- - User, group, Telegram-link, Telegram-chat, Discord-link, Discord-channel, web-session, login, and permission-denied events are written to the audit log.
214
+ - User, group, Telegram-link, Telegram-chat, Discord-link, Discord-channel, Slack-link, Slack-channel, web-session, login, and permission-denied events are written to the audit log.
201
215
  - `/auth` reports Codex authentication, Pi provider environment health, Hermes API Server reachability, OpenClaw Gateway reachability, or Claude Code CLI auth for the selected agent.
202
216
  - `/login` starts Telegram-managed CLI auth for Codex, Hermes, or Claude Code when enabled.
203
217
  - `/logout` signs out of CLI auth for Codex, Hermes, or Claude Code; Codex logout is disabled while `CODEX_API_KEY` is in use.
@@ -217,10 +231,10 @@ Operations:
217
231
  - `/logs` renders redacted connector, NordRelay update, and agent update logs with local-time timestamps, levels, file path, last-modified time, and highlighted warnings/errors.
218
232
  - Logs can be emitted as timestamped plain text or JSON records with `CONNECTOR_LOG_FORMAT`.
219
233
  - Telegram sends/edits/documents are routed through a rate-limit queue that honors Telegram retry-after responses.
220
- - Mirror, notification, quiet-hour, and automatic artifact-delivery defaults are configured through channel-neutral `NORDRELAY_*` settings, with Telegram and Discord override keys when a channel should differ.
234
+ - Mirror, notification, quiet-hour, and automatic artifact-delivery defaults are configured through channel-neutral `NORDRELAY_*` settings, with Telegram, Discord, and Slack override keys when a channel should differ.
221
235
  - The WebUI Tasks page includes a unified Jobs view for active WebUI turns, external CLI turns, queued prompts, agent update/install jobs, self-updates, and diagnostics bundle exports, with log, cancel, and retry actions where supported.
222
236
  - Unified Jobs are persisted across restarts and retain recent prompt, queue, update, connector-update, and support-bundle history for WebUI inspection.
223
- - The WebUI Metrics page reports queue state, active/completed/failed turns, job counts, average prompt duration, and Telegram/Discord rate-limit counters.
237
+ - The WebUI Metrics page reports queue state, active/completed/failed turns, job counts, average prompt duration, and Telegram/Discord/Slack rate-limit counters.
224
238
  - Expensive dashboard views such as version checks, adapter health, and diagnostics use a short stale-while-refresh server cache so the UI can render recent data while fresh checks run in the background.
225
239
  - Context metadata, queues, and preferences are written atomically with backup recovery.
226
240
  - Context metadata, queues, preferences, audit events, and locks can use JSON files or the optional SQLite state backend with `NORDRELAY_STATE_BACKEND=sqlite`.
@@ -229,7 +243,7 @@ Operations:
229
243
  - On first WebUI startup without an admin account, NordRelay shows a setup wizard for creating the first admin; remote setup requires the one-time token printed in the server console.
230
244
  - The WebUI has responsive header/sidebar/footer navigation, live chat streaming, session controls, queue/artifact/log/diagnostic views, and settings management.
231
245
  - The WebUI supports light and dark themes, tabbed settings groups, paginated session browsing, and chat uploads for images, documents, and audio transcription.
232
- - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, diagnostics, peers, and redacted diagnostics bundle export.
246
+ - The WebUI exposes REST and SSE endpoints for chat streaming, sessions, settings, queue, artifacts, logs, health, diagnostics, peers, adapter conformance, and redacted diagnostics bundle export.
233
247
  - The dashboard can bind to `127.0.0.1` or `0.0.0.0`; user login and session cookies are mandatory in both modes.
234
248
  - Telegram can run with long polling or an HTTP webhook via `TELEGRAM_TRANSPORT=webhook`.
235
249
  - Version freshness checks are cached with `NORDRELAY_VERSION_CACHE_TTL_MS`, and installed agent CLI version checks are cached with `NORDRELAY_CLI_VERSION_CACHE_TTL_MS`, to keep `/version` and adapter health responsive.
@@ -301,6 +315,16 @@ Create the Discord bot:
301
315
  6. Link Discord from the WebUI, with `nordrelay user link-discord`, or by creating a Discord link code and sending `/link <code>` to the bot.
302
316
  7. In guild channels, run `/register_channel` once from an admin-linked Discord account.
303
317
 
318
+ Create the Slack app:
319
+
320
+ 1. Open Slack API Apps and create a new app for your workspace.
321
+ 2. Add a bot user and copy the bot token into `SLACK_BOT_TOKEN`.
322
+ 3. Enable Socket Mode and create an app-level token with `connections:write`; copy it into `SLACK_APP_TOKEN`.
323
+ 4. Add bot scopes for messages, files, channels, groups, IMs, MPIMs, commands, and chat write access.
324
+ 5. Create the slash command configured in `SLACK_COMMAND` (default `/nordrelay`) and install the app to your workspace.
325
+ 6. Link Slack from the WebUI, with `nordrelay user link-slack`, or by creating a Slack link code and sending `/link <code>` to the app.
326
+ 7. In Slack channels, run `/register_channel` once from an admin-linked Slack account.
327
+
304
328
  Minimal private-bot `~/.nordrelay/nordrelay.env`:
305
329
 
306
330
  ```dotenv
@@ -308,6 +332,9 @@ TELEGRAM_BOT_TOKEN=123456789:replace-me
308
332
  DISCORD_ENABLED=false
309
333
  DISCORD_BOT_TOKEN=
310
334
  DISCORD_CLIENT_ID=
335
+ SLACK_ENABLED=false
336
+ SLACK_BOT_TOKEN=
337
+ SLACK_APP_TOKEN=
311
338
  NORDRELAY_CODEX_ENABLED=true
312
339
  NORDRELAY_PI_ENABLED=false
313
340
  NORDRELAY_HERMES_ENABLED=false
@@ -325,10 +352,13 @@ User and chat access management:
325
352
  - `nordrelay user create --email dev@example.com --name "Dev" --group user` creates a normal user.
326
353
  - `nordrelay user link-telegram --email you@example.com --telegram-user-id 123456789` links a Telegram account directly.
327
354
  - `nordrelay user link-discord --email you@example.com --discord-user-id 123456789012345678` links a Discord account directly.
355
+ - `nordrelay user link-slack --email you@example.com --slack-user-id U123 --slack-team-id T123` links a Slack account directly.
328
356
  - `nordrelay user link-code --email you@example.com` creates a short-lived Telegram code that the user sends as `/link <code>` to the Telegram bot.
329
357
  - `nordrelay user discord-link-code --email you@example.com` creates a short-lived Discord code that the user sends as `/link <code>` to the Discord bot.
358
+ - `nordrelay user slack-link-code --email you@example.com` creates a short-lived Slack code that the user sends as `/link <code>` to the Slack app.
330
359
  - Telegram group chats are disabled until an admin enables them from the WebUI or runs `/register_chat` inside the group.
331
360
  - Discord guild channels are disabled until an admin enables them from the WebUI or runs `/register_channel` inside the channel.
361
+ - Slack channels are disabled until an admin enables them from the WebUI or runs `/register_channel` inside the channel.
332
362
 
333
363
  Peer setup:
334
364
 
@@ -355,7 +385,7 @@ nordrelay peer list
355
385
  nordrelay peer test <peer-id>
356
386
  ```
357
387
 
358
- Use `--workspace-aliases app=/srv/app,demo=/home/me/demo` on invites when a controller should be able to start remote sessions with short workspace names. Use the WebUI Peers page for the same invite, pair, enable/disable, test, alias, global-session, and revoke workflow. Use `/peers` from Telegram or Discord to inspect paired nodes and `/target <peer-id>` or `/target local` to choose where subsequent prompts run.
388
+ Use `--workspace-aliases app=/srv/app,demo=/home/me/demo` on invites when a controller should be able to start remote sessions with short workspace names. Use the WebUI Peers page for the same invite, pair, enable/disable, test, alias, global-session, and revoke workflow. Use `/peers` from Telegram, Discord, or Slack to inspect paired nodes and `/target <peer-id>` or `/target local` to choose where subsequent prompts run.
359
389
 
360
390
  Codex authentication:
361
391
 
@@ -626,6 +656,19 @@ Discord supports slash commands and `/command` text messages for the shared comm
626
656
  - Unsafe launch profiles require explicit confirmation with `/launch <profile-id> confirm`.
627
657
  - Discord does not support Telegram reactions or Telegram webhook transport; typing, message edits, attachments, files, DMs, guild channels, and threads are supported.
628
658
 
659
+ ## Slack Commands
660
+
661
+ Slack supports the configured slash command and `/command` text messages for the shared command set. The primary differences from Telegram are:
662
+
663
+ - `/register_channel` enables the current Slack channel or thread for NordRelay when the linked user has user-management permission.
664
+ - `/prompt <text>` is available through the configured slash command when regular message content is disabled.
665
+ - `/link <code>` consumes Slack link codes created in the WebUI or with `nordrelay user slack-link-code`.
666
+ - `/queue`, `/sessions`, `/agent`, `/model`, `/reasoning`, `/launch`, `/artifacts`, `/update`, and `/stop` use Slack buttons where Block Kit limits allow.
667
+ - `/peers` and `/target local|<peer-id>` use the same paired-instance target selection as Telegram and Discord.
668
+ - `/artifacts latest`, `/artifacts zip latest`, `/artifacts images`, `/artifacts docs`, `/artifacts search <text>`, and `/artifacts delete <turn-id>` are available in Slack.
669
+ - Unsafe launch profiles require explicit confirmation with `/launch <profile-id> confirm`.
670
+ - Slack does not support Telegram reactions or Telegram webhook transport; typing/status, message edits, attachments, files, DMs, channels, and threads are supported.
671
+
629
672
  ## Command Examples
630
673
 
631
674
  Switching to an existing thread:
@@ -837,13 +880,27 @@ Discord:
837
880
  - `DISCORD_AUTO_REGISTER_COMMANDS`: registers slash commands on startup when `DISCORD_CLIENT_ID` is set. Defaults to `true`.
838
881
  - `DISCORD_CLI_MIRROR_MODE`, `DISCORD_CLI_MIRROR_MIN_UPDATE_MS`, `DISCORD_NOTIFY_MODE`, `DISCORD_QUIET_HOURS`, and `DISCORD_AUTO_SEND_ARTIFACTS`: optional Discord-specific overrides for the channel-neutral defaults.
839
882
 
883
+ Slack:
884
+
885
+ - `SLACK_ENABLED`: starts the Slack adapter. Defaults to `false`.
886
+ - `SLACK_BOT_TOKEN`: Slack bot token. Required for the Slack adapter to start.
887
+ - `SLACK_APP_TOKEN`: Slack app-level token for Socket Mode. Required when `SLACK_SOCKET_MODE=true`.
888
+ - `SLACK_SIGNING_SECRET`: Slack signing secret for HTTP Events mode. Required when `SLACK_SOCKET_MODE=false`.
889
+ - `SLACK_SOCKET_MODE`: uses Slack Socket Mode instead of an HTTP Events receiver. Defaults to `true`.
890
+ - `SLACK_PORT`: HTTP receiver port when Socket Mode is disabled. Defaults to `3000`.
891
+ - `SLACK_ALLOWED_TEAM_IDS`: optional Slack workspace allow-list before user/group permissions are checked.
892
+ - `SLACK_ALLOWED_CHANNEL_IDS`: optional channel allow-list before user/group permissions are checked.
893
+ - `SLACK_MESSAGE_CONTENT_ENABLED`: reads regular Slack text messages as prompts. Defaults to `true`.
894
+ - `SLACK_COMMAND`: slash command configured in Slack. Defaults to `/nordrelay`.
895
+ - `SLACK_CLI_MIRROR_MODE`, `SLACK_CLI_MIRROR_MIN_UPDATE_MS`, `SLACK_NOTIFY_MODE`, `SLACK_QUIET_HOURS`, and `SLACK_AUTO_SEND_ARTIFACTS`: optional Slack-specific overrides for the channel-neutral defaults.
896
+
840
897
  User management:
841
898
 
842
- - Users, groups, Telegram identities, Telegram group-chat access, Discord identities, Discord channel access, and web sessions are stored in `~/.nordrelay/users.json`.
843
- - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, `link-discord`, `link-code`, and `discord-link-code`.
899
+ - Users, groups, Telegram identities, Telegram group-chat access, Discord identities, Discord channel access, Slack identities, Slack channel access, and web sessions are stored in `~/.nordrelay/users.json`.
900
+ - Manage users in the WebUI Users page or with `nordrelay user list`, `create-admin`, `create`, `reset-password`, `link-telegram`, `link-discord`, `link-slack`, `link-code`, `discord-link-code`, and `slack-link-code`.
844
901
  - Built-in groups are `admin`, `user`, and `readonly`.
845
902
  - Group permissions include `inspect`, `sessions.read`, `sessions.write`, `prompt.send`, `prompt.abort`, `files.read`, `files.write`, `settings.read`, `settings.write`, `auth.manage`, `diagnostics.read`, `logs.read`, `logs.clear`, `queue.read`, `queue.write`, `updates.run`, `system.restart`, `users.read`, `users.write`, `audit.read`, `peers.read`, `peers.write`, and `peers.connect`.
846
- - Custom groups can also restrict access to specific agent ids, workspace roots, Telegram chat ids, and Discord channel ids.
903
+ - Custom groups can also restrict access to specific agent ids, workspace roots, Telegram chat ids, Discord channel ids, and Slack channel ids.
847
904
 
848
905
  Peers:
849
906
 
@@ -883,8 +940,8 @@ Codex:
883
940
  - `CODEX_CLI_PATH`: optional explicit path to the Codex CLI executable.
884
941
  - `CODEX_USE_BUNDLED_CLI`: set `true` to force the SDK-bundled Codex CLI instead of the host `codex` executable.
885
942
  - `CODEX_MODEL`: default model for new threads.
886
- - `CODEX_SYNC_INTERVAL_MS`: periodic local Codex-state sync interval for active Telegram sessions. Defaults to `10000`; set `0` to disable.
887
- - `CODEX_EXTERNAL_BUSY_CHECK_MS`: how often queued Telegram prompts re-check an active local Codex CLI task. Defaults to `5000`.
943
+ - `CODEX_SYNC_INTERVAL_MS`: periodic local Codex-state sync interval for active chat sessions. Defaults to `10000`; set `0` to disable.
944
+ - `CODEX_EXTERNAL_BUSY_CHECK_MS`: how often queued chat prompts re-check an active local Codex CLI task. Defaults to `5000`.
888
945
  - `CODEX_EXTERNAL_BUSY_STALE_MS`: maximum age for an unclosed rollout task before it is treated as stale instead of active. Defaults to `300000`.
889
946
  - `CODEX_SANDBOX_MODE`: default sandbox mode, one of `read-only`, `workspace-write`, `danger-full-access`.
890
947
  - `CODEX_APPROVAL_POLICY`: default approval policy, one of `never`, `on-request`, `on-failure`, `untrusted`.
@@ -1007,7 +1064,7 @@ ENABLE_UNSAFE_LAUNCH_PROFILES=true
1007
1064
  CODEX_LAUNCH_PROFILES_JSON=[{"id":"host-full","label":"Host Full Access","sandboxMode":"danger-full-access","approvalPolicy":"never"}]
1008
1065
  ```
1009
1066
 
1010
- Unsafe profiles are intentionally gated. Telegram asks for confirmation before applying them.
1067
+ Unsafe profiles are intentionally gated. Chat adapters ask for confirmation before applying them.
1011
1068
 
1012
1069
  ## Security Notes
1013
1070
 
@@ -1179,7 +1236,10 @@ npm run build
1179
1236
  - `src/index.ts`: runtime entrypoint, config load, auth check, state-file writes, polling lifecycle, shutdown.
1180
1237
  - `src/bot.ts`: Telegram prompt/session runtime, streaming, file/photo/voice handling, artifacts, and error handling.
1181
1238
  - `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.
1182
- - `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.
1239
+ - `src/channel-adapter.ts`, `src/channel-runtime.ts`, `src/channel-command-core.ts`, `src/channel-actions.ts`, and `src/adapter-conformance.ts`: channel descriptors, shared command dispatch/coverage, outbound delivery contracts, channel-neutral responses, and generated feature/command conformance matrices.
1240
+ - `src/discord-bot.ts` and `src/slack-bot.ts`: Discord and Slack bridge runtimes built on the shared channel command core, channel runtimes, rate limiters, access checks, streaming replies, attachments, mirrors, and queue controls.
1241
+ - `src/slack-diagnostics.ts`: Slack readiness probes for token/transport config, auth, registered channel visibility, file-upload readiness, and rate-limit reporting.
1242
+ - `src/user-management.ts`, `src/user-management-types.ts`, `src/user-management-normalize.ts`, and `src/user-management-crypto.ts`: user/group/session/channel-access store with separated DTOs, payload normalization, password/token helpers, and public snapshots.
1183
1243
  - `src/config-metadata.ts`: shared setting metadata used by the WebUI settings page and generated `.env.example`.
1184
1244
  - `src/support-bundle.ts` and `src/zip-writer.ts`: redacted diagnostics bundle creation with a dependency-free ZIP writer.
1185
1245
  - `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.
@@ -11,7 +11,7 @@ export function activityCategoryForType(type) {
11
11
  return "artifact";
12
12
  if (/^(auth|login|logout)/.test(type))
13
13
  return "auth";
14
- if (/^(user_|group_|telegram_chat_|telegram_link|discord_channel_|discord_link|peer_|permission_|access_|lock_)/.test(type))
14
+ if (/^(user_|group_|telegram_chat_|telegram_link|discord_channel_|discord_link|slack_channel_|slack_link|peer_|permission_|access_|lock_)/.test(type))
15
15
  return "security";
16
16
  if (/^(tool_|cli_tool)/.test(type))
17
17
  return "tool";
@@ -32,7 +32,7 @@ export function auditCategoryForAction(action) {
32
32
  return "security";
33
33
  if (/^auth_/.test(action))
34
34
  return "auth";
35
- if (/^(permission_|user_|group_|telegram_|discord_|peer_)/.test(action))
35
+ if (/^(permission_|user_|group_|telegram_|discord_|slack_|peer_)/.test(action))
36
36
  return "security";
37
37
  if (/^(artifact|file)/.test(action))
38
38
  return "artifact";
@@ -0,0 +1,61 @@
1
+ import { listAgentAdapterDescriptors } from "./agent-adapter.js";
2
+ import { agentFeatureStates } from "./agent-feature-matrix.js";
3
+ import { listChannelDescriptors, } from "./channel-adapter.js";
4
+ import { channelCatalogCommandNames, } from "./channel-command-core.js";
5
+ export const CHANNEL_FEATURES = [
6
+ { key: "text", label: "Text", description: "Send and receive plain text prompts and replies." },
7
+ { key: "streaming-edits", label: "Streaming edits", description: "Update an in-flight answer instead of sending only a final message." },
8
+ { key: "typing", label: "Typing/status", description: "Show activity while an agent turn is still running." },
9
+ { key: "inline-buttons", label: "Buttons", description: "Expose interactive choices for sessions, queue items, updates, artifacts, and aborts." },
10
+ { key: "files", label: "Files", description: "Receive or send generic files." },
11
+ { key: "photos", label: "Photos", description: "Receive image inputs for multimodal-capable agents." },
12
+ { key: "voice", label: "Voice", description: "Receive audio and run transcription before prompting." },
13
+ { key: "topics", label: "Threads/topics", description: "Keep independent contexts per topic, thread, forum topic, or equivalent channel scope." },
14
+ { key: "webhooks", label: "Webhooks", description: "Support inbound HTTP webhook/event delivery where the platform provides it." },
15
+ ];
16
+ export function channelFeatureStates(capabilities) {
17
+ const supported = new Set(capabilities);
18
+ return CHANNEL_FEATURES.map((feature) => ({
19
+ ...feature,
20
+ supported: supported.has(feature.key),
21
+ }));
22
+ }
23
+ export function buildAdapterConformanceMatrix(input = {}) {
24
+ const agents = input.agents ?? listAgentAdapterDescriptors();
25
+ const channels = input.channels ?? listChannelDescriptors();
26
+ return {
27
+ generatedAt: new Date().toISOString(),
28
+ agents: agents.map((adapter) => {
29
+ const features = agentFeatureStates(adapter.capabilities);
30
+ return {
31
+ id: adapter.id,
32
+ label: adapter.label,
33
+ status: adapter.status,
34
+ features,
35
+ supported: features.filter((feature) => feature.supported).map((feature) => feature.key),
36
+ unsupported: features.filter((feature) => !feature.supported).map((feature) => feature.key),
37
+ notes: adapter.notes,
38
+ };
39
+ }),
40
+ channels: channels.map((adapter) => {
41
+ const features = channelFeatureStates(adapter.capabilities);
42
+ return {
43
+ id: adapter.id,
44
+ label: adapter.label,
45
+ status: adapter.status,
46
+ enabled: adapter.enabled,
47
+ features,
48
+ supported: features.filter((feature) => feature.supported).map((feature) => feature.key),
49
+ unsupported: features.filter((feature) => !feature.supported).map((feature) => feature.key),
50
+ commands: commandNamesForChannel(adapter.id),
51
+ notes: adapter.notes,
52
+ };
53
+ }),
54
+ };
55
+ }
56
+ function commandNamesForChannel(id) {
57
+ if (id === "telegram" || id === "discord" || id === "slack") {
58
+ return channelCatalogCommandNames(id);
59
+ }
60
+ return [];
61
+ }