@nordbyte/nordrelay 0.6.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.
- package/.env.example +52 -0
- package/README.md +171 -50
- package/dist/access-control.js +6 -1
- package/dist/activity-events.js +2 -2
- package/dist/adapter-conformance.js +61 -0
- package/dist/bot-preferences.js +1 -0
- package/dist/bot.js +95 -37
- package/dist/channel-adapter.js +44 -11
- package/dist/channel-command-catalog.js +94 -0
- package/dist/channel-command-core.js +60 -0
- package/dist/channel-command-service.js +230 -1
- package/dist/channel-mirror-registry.js +84 -0
- package/dist/channel-peer-prompt.js +95 -0
- package/dist/channel-prompt-engine.js +177 -0
- package/dist/channel-runtime.js +12 -5
- package/dist/channel-turn-lifecycle.js +73 -0
- package/dist/codex-state.js +114 -78
- package/dist/config-metadata.js +82 -8
- package/dist/config.js +79 -7
- package/dist/context-key.js +42 -0
- package/dist/discord-bot.js +173 -342
- package/dist/discord-command-surface.js +11 -73
- package/dist/index.js +29 -0
- package/dist/metrics.js +48 -0
- package/dist/peer-auth.js +85 -0
- package/dist/peer-client.js +288 -0
- package/dist/peer-context.js +21 -0
- package/dist/peer-identity.js +127 -0
- package/dist/peer-readiness.js +77 -0
- package/dist/peer-runtime-service.js +658 -0
- package/dist/peer-server.js +220 -0
- package/dist/peer-store.js +307 -0
- package/dist/peer-types.js +52 -0
- package/dist/relay-runtime-helpers.js +210 -0
- package/dist/relay-runtime.js +79 -274
- package/dist/remote-prompt.js +98 -0
- package/dist/settings-wizard-test.js +216 -0
- package/dist/slack-artifacts.js +165 -0
- package/dist/slack-bot.js +1461 -0
- package/dist/slack-channel-runtime.js +147 -0
- package/dist/slack-command-surface.js +46 -0
- package/dist/slack-diagnostics.js +116 -0
- package/dist/slack-rate-limit.js +139 -0
- package/dist/telegram-command-menu.js +3 -53
- package/dist/telegram-general-commands.js +14 -0
- package/dist/telegram-preference-commands.js +23 -127
- package/dist/user-management-crypto.js +38 -0
- package/dist/user-management-normalize.js +188 -0
- package/dist/user-management-types.js +1 -0
- package/dist/user-management.js +193 -196
- package/dist/web-api-contract.js +16 -0
- package/dist/web-dashboard-access-routes.js +62 -0
- package/dist/web-dashboard-assets.js +1 -0
- package/dist/web-dashboard-pages.js +26 -4
- package/dist/web-dashboard-peer-routes.js +225 -0
- package/dist/web-dashboard-ui.js +1 -0
- package/dist/web-dashboard.js +46 -0
- package/dist/web-state.js +2 -2
- package/dist/webui-assets/dashboard.css +193 -0
- package/dist/webui-assets/dashboard.js +870 -57
- package/package.json +5 -2
- package/plugins/nordrelay/scripts/nordrelay.mjs +468 -11
package/dist/config.js
CHANGED
|
@@ -5,8 +5,9 @@ import { CLAUDE_CODE_EFFORT_LEVELS, HERMES_REASONING_EFFORTS, OPENCLAW_THINKING_
|
|
|
5
5
|
import { parseMirrorMode, parseNotifyMode, parseQuietHours, parseVoiceBackendPreference, } from "./bot-preferences.js";
|
|
6
6
|
export function loadConfig() {
|
|
7
7
|
loadEnvFile(path.resolve(process.cwd(), ".env"));
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const adapterWarnings = [];
|
|
9
|
+
const requestedTelegramEnabled = parseBooleanEnv(optionalString(process.env.TELEGRAM_ENABLED), true);
|
|
10
|
+
const telegramBotToken = optionalString(process.env.TELEGRAM_BOT_TOKEN) ?? "";
|
|
10
11
|
const telegramRateLimitMinIntervalMs = parseNonNegativeIntegerEnv(optionalString(process.env.TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS), 80, "TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS");
|
|
11
12
|
const telegramEditMinIntervalMs = parseNonNegativeIntegerEnv(optionalString(process.env.TELEGRAM_EDIT_MIN_INTERVAL_MS), 1_200, "TELEGRAM_EDIT_MIN_INTERVAL_MS");
|
|
12
13
|
const mirrorMode = parseMirrorMode(optionalString(process.env.NORDRELAY_CLI_MIRROR_MODE), "status");
|
|
@@ -25,7 +26,7 @@ export function loadConfig() {
|
|
|
25
26
|
const telegramWebhookPort = parsePositiveIntegerEnv(optionalString(process.env.TELEGRAM_WEBHOOK_PORT), 8080, "TELEGRAM_WEBHOOK_PORT");
|
|
26
27
|
const telegramWebhookPath = parseWebhookPath(optionalString(process.env.TELEGRAM_WEBHOOK_PATH));
|
|
27
28
|
const telegramWebhookSecret = optionalString(process.env.TELEGRAM_WEBHOOK_SECRET);
|
|
28
|
-
const
|
|
29
|
+
const requestedDiscordEnabled = parseBooleanEnv(optionalString(process.env.DISCORD_ENABLED), false);
|
|
29
30
|
const discordBotToken = optionalString(process.env.DISCORD_BOT_TOKEN);
|
|
30
31
|
const discordClientId = optionalString(process.env.DISCORD_CLIENT_ID);
|
|
31
32
|
const discordGuildIds = parseOptionalStringList(optionalString(process.env.DISCORD_GUILD_IDS));
|
|
@@ -38,6 +39,20 @@ export function loadConfig() {
|
|
|
38
39
|
const discordMirrorMinUpdateMs = parseNonNegativeIntegerEnv(optionalString(process.env.DISCORD_CLI_MIRROR_MIN_UPDATE_MS), mirrorMinUpdateMs, "DISCORD_CLI_MIRROR_MIN_UPDATE_MS");
|
|
39
40
|
const discordNotifyMode = parseNotifyMode(optionalString(process.env.DISCORD_NOTIFY_MODE), notifyMode);
|
|
40
41
|
const discordQuietHours = parseQuietHoursOverride(process.env.DISCORD_QUIET_HOURS, quietHours);
|
|
42
|
+
const requestedSlackEnabled = parseBooleanEnv(optionalString(process.env.SLACK_ENABLED), false);
|
|
43
|
+
const slackBotToken = optionalString(process.env.SLACK_BOT_TOKEN);
|
|
44
|
+
const slackAppToken = optionalString(process.env.SLACK_APP_TOKEN);
|
|
45
|
+
const slackSigningSecret = optionalString(process.env.SLACK_SIGNING_SECRET);
|
|
46
|
+
const slackSocketMode = parseBooleanEnv(optionalString(process.env.SLACK_SOCKET_MODE), true);
|
|
47
|
+
const slackPort = parsePositiveIntegerEnv(optionalString(process.env.SLACK_PORT), 3000, "SLACK_PORT");
|
|
48
|
+
const slackAllowedTeamIds = parseOptionalStringList(optionalString(process.env.SLACK_ALLOWED_TEAM_IDS));
|
|
49
|
+
const slackAllowedChannelIds = parseOptionalStringList(optionalString(process.env.SLACK_ALLOWED_CHANNEL_IDS));
|
|
50
|
+
const slackMessageContentEnabled = parseBooleanEnv(optionalString(process.env.SLACK_MESSAGE_CONTENT_ENABLED), true);
|
|
51
|
+
const slackCommand = parseSlackCommand(optionalString(process.env.SLACK_COMMAND));
|
|
52
|
+
const slackMirrorMode = parseMirrorMode(optionalString(process.env.SLACK_CLI_MIRROR_MODE), mirrorMode);
|
|
53
|
+
const slackMirrorMinUpdateMs = parseNonNegativeIntegerEnv(optionalString(process.env.SLACK_CLI_MIRROR_MIN_UPDATE_MS), mirrorMinUpdateMs, "SLACK_CLI_MIRROR_MIN_UPDATE_MS");
|
|
54
|
+
const slackNotifyMode = parseNotifyMode(optionalString(process.env.SLACK_NOTIFY_MODE), notifyMode);
|
|
55
|
+
const slackQuietHours = parseQuietHoursOverride(process.env.SLACK_QUIET_HOURS, quietHours);
|
|
41
56
|
const workspace = resolveWorkspace();
|
|
42
57
|
const workspaceAllowedRoots = parsePathList(optionalString(process.env.WORKSPACE_ALLOWED_ROOTS));
|
|
43
58
|
const workspaceWarnRoots = parsePathList(optionalString(process.env.WORKSPACE_WARN_ROOTS));
|
|
@@ -50,6 +65,7 @@ export function loadConfig() {
|
|
|
50
65
|
const artifactIgnoreGlobs = parseOptionalStringList(optionalString(process.env.ARTIFACT_IGNORE_GLOBS));
|
|
51
66
|
const telegramAutoSendArtifacts = parseBooleanEnv(optionalString(process.env.TELEGRAM_AUTO_SEND_ARTIFACTS), autoSendArtifacts);
|
|
52
67
|
const discordAutoSendArtifacts = parseBooleanEnv(optionalString(process.env.DISCORD_AUTO_SEND_ARTIFACTS), autoSendArtifacts);
|
|
68
|
+
const slackAutoSendArtifacts = parseBooleanEnv(optionalString(process.env.SLACK_AUTO_SEND_ARTIFACTS), autoSendArtifacts);
|
|
53
69
|
const codexEnabled = parseBooleanEnv(optionalString(process.env.NORDRELAY_CODEX_ENABLED), true);
|
|
54
70
|
const codexApiKey = optionalString(process.env.CODEX_API_KEY);
|
|
55
71
|
const codexModel = optionalString(process.env.CODEX_MODEL);
|
|
@@ -108,16 +124,46 @@ export function loadConfig() {
|
|
|
108
124
|
const sessionLockTtlMs = parseNonNegativeIntegerEnv(optionalString(process.env.NORDRELAY_SESSION_LOCK_TTL_MS), 30 * 60 * 1000, "NORDRELAY_SESSION_LOCK_TTL_MS");
|
|
109
125
|
const dashboardCacheTtlMs = parseNonNegativeIntegerEnv(optionalString(process.env.NORDRELAY_DASHBOARD_CACHE_TTL_MS), 10_000, "NORDRELAY_DASHBOARD_CACHE_TTL_MS");
|
|
110
126
|
const unifiedJobMaxItems = parsePositiveIntegerEnv(optionalString(process.env.NORDRELAY_UNIFIED_JOB_MAX_ITEMS), 1000, "NORDRELAY_UNIFIED_JOB_MAX_ITEMS");
|
|
127
|
+
const peerEnabled = parseBooleanEnv(optionalString(process.env.NORDRELAY_PEER_ENABLED), false);
|
|
128
|
+
const peerName = optionalString(process.env.NORDRELAY_PEER_NAME);
|
|
129
|
+
const peerHost = optionalString(process.env.NORDRELAY_PEER_HOST) ?? "127.0.0.1";
|
|
130
|
+
const peerPort = parsePositiveIntegerEnv(optionalString(process.env.NORDRELAY_PEER_PORT), 31979, "NORDRELAY_PEER_PORT");
|
|
131
|
+
const peerPublicUrl = optionalString(process.env.NORDRELAY_PEER_PUBLIC_URL);
|
|
132
|
+
const peerTlsEnabled = parseBooleanEnv(optionalString(process.env.NORDRELAY_PEER_TLS_ENABLED), true);
|
|
133
|
+
const peerRequireTls = parseBooleanEnv(optionalString(process.env.NORDRELAY_PEER_REQUIRE_TLS), true);
|
|
134
|
+
let telegramEnabled = requestedTelegramEnabled;
|
|
111
135
|
if (telegramEnabled && telegramTransport === "webhook" && !telegramWebhookUrl) {
|
|
112
|
-
|
|
136
|
+
telegramEnabled = false;
|
|
137
|
+
adapterWarnings.push("Telegram disabled: TELEGRAM_TRANSPORT=webhook requires TELEGRAM_WEBHOOK_URL.");
|
|
113
138
|
}
|
|
139
|
+
if (telegramEnabled && !telegramBotToken) {
|
|
140
|
+
telegramEnabled = false;
|
|
141
|
+
adapterWarnings.push("Telegram disabled: TELEGRAM_BOT_TOKEN is missing.");
|
|
142
|
+
}
|
|
143
|
+
let discordEnabled = requestedDiscordEnabled;
|
|
114
144
|
if (discordEnabled && !discordBotToken) {
|
|
115
|
-
|
|
145
|
+
discordEnabled = false;
|
|
146
|
+
adapterWarnings.push("Discord disabled: DISCORD_ENABLED=true requires DISCORD_BOT_TOKEN.");
|
|
147
|
+
}
|
|
148
|
+
let slackEnabled = requestedSlackEnabled;
|
|
149
|
+
if (slackEnabled && !slackBotToken) {
|
|
150
|
+
slackEnabled = false;
|
|
151
|
+
adapterWarnings.push("Slack disabled: SLACK_ENABLED=true requires SLACK_BOT_TOKEN.");
|
|
152
|
+
}
|
|
153
|
+
if (slackEnabled && slackSocketMode && !slackAppToken) {
|
|
154
|
+
slackEnabled = false;
|
|
155
|
+
adapterWarnings.push("Slack disabled: SLACK_SOCKET_MODE=true requires SLACK_APP_TOKEN.");
|
|
116
156
|
}
|
|
117
|
-
if (!
|
|
118
|
-
|
|
157
|
+
if (slackEnabled && !slackSocketMode && !slackSigningSecret) {
|
|
158
|
+
slackEnabled = false;
|
|
159
|
+
adapterWarnings.push("Slack disabled: SLACK_SOCKET_MODE=false requires SLACK_SIGNING_SECRET.");
|
|
160
|
+
}
|
|
161
|
+
if (!telegramEnabled && !discordEnabled && !slackEnabled) {
|
|
162
|
+
const detail = adapterWarnings.length > 0 ? ` ${adapterWarnings.join(" ")}` : "";
|
|
163
|
+
throw new Error(`At least one usable chat adapter must be enabled.${detail}`);
|
|
119
164
|
}
|
|
120
165
|
return {
|
|
166
|
+
adapterWarnings,
|
|
121
167
|
telegramEnabled,
|
|
122
168
|
telegramBotToken,
|
|
123
169
|
telegramRateLimitMinIntervalMs,
|
|
@@ -152,6 +198,21 @@ export function loadConfig() {
|
|
|
152
198
|
discordNotifyMode,
|
|
153
199
|
discordQuietHours,
|
|
154
200
|
discordAutoSendArtifacts,
|
|
201
|
+
slackEnabled,
|
|
202
|
+
slackBotToken,
|
|
203
|
+
slackAppToken,
|
|
204
|
+
slackSigningSecret,
|
|
205
|
+
slackSocketMode,
|
|
206
|
+
slackPort,
|
|
207
|
+
slackAllowedTeamIds,
|
|
208
|
+
slackAllowedChannelIds,
|
|
209
|
+
slackMessageContentEnabled,
|
|
210
|
+
slackCommand,
|
|
211
|
+
slackMirrorMode,
|
|
212
|
+
slackMirrorMinUpdateMs,
|
|
213
|
+
slackNotifyMode,
|
|
214
|
+
slackQuietHours,
|
|
215
|
+
slackAutoSendArtifacts,
|
|
155
216
|
workspace,
|
|
156
217
|
workspaceAllowedRoots,
|
|
157
218
|
workspaceWarnRoots,
|
|
@@ -220,6 +281,13 @@ export function loadConfig() {
|
|
|
220
281
|
sessionLockTtlMs,
|
|
221
282
|
dashboardCacheTtlMs,
|
|
222
283
|
unifiedJobMaxItems,
|
|
284
|
+
peerEnabled,
|
|
285
|
+
peerName,
|
|
286
|
+
peerHost,
|
|
287
|
+
peerPort,
|
|
288
|
+
peerPublicUrl,
|
|
289
|
+
peerTlsEnabled,
|
|
290
|
+
peerRequireTls,
|
|
223
291
|
};
|
|
224
292
|
}
|
|
225
293
|
/**
|
|
@@ -412,6 +480,10 @@ function parseDiscordCommandMode(raw) {
|
|
|
412
480
|
console.warn(`Invalid DISCORD_COMMAND_MODE value: "${raw}". Expected slash, message, or both. Falling back to both.`);
|
|
413
481
|
return "both";
|
|
414
482
|
}
|
|
483
|
+
function parseSlackCommand(raw) {
|
|
484
|
+
const normalized = raw?.trim() || "/nordrelay";
|
|
485
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
486
|
+
}
|
|
415
487
|
function parseWebhookPath(raw) {
|
|
416
488
|
if (!raw) {
|
|
417
489
|
return "/telegram/webhook";
|
package/dist/context-key.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parsePeerRuntimeContextKey } from "./peer-context.js";
|
|
1
2
|
export function telegramContextKeyFromMessage(chatId, messageThreadId) {
|
|
2
3
|
if (messageThreadId !== undefined) {
|
|
3
4
|
return `${chatId}:${messageThreadId}`;
|
|
@@ -76,6 +77,28 @@ export function parseDiscordContextKey(key) {
|
|
|
76
77
|
threadId,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
80
|
+
export function slackContextKey(input) {
|
|
81
|
+
const teamId = input.teamId || "team";
|
|
82
|
+
const thread = input.threadTs && input.threadTs !== input.channelId ? `:${input.threadTs}` : "";
|
|
83
|
+
return `slack:${teamId}:${input.channelId}${thread}`;
|
|
84
|
+
}
|
|
85
|
+
export function isSlackContextKey(key) {
|
|
86
|
+
return /^slack:[^:]+:[^:]+(?::[^:]+)?$/.test(key);
|
|
87
|
+
}
|
|
88
|
+
export function parseSlackContextKey(key) {
|
|
89
|
+
if (!isSlackContextKey(key)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const [, team, channelId, threadTs] = key.split(":");
|
|
93
|
+
if (!channelId) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
teamId: team === "team" ? undefined : team,
|
|
98
|
+
channelId,
|
|
99
|
+
threadTs,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
79
102
|
export function parseChannelContextKey(key) {
|
|
80
103
|
const rawKey = String(key);
|
|
81
104
|
if (isTelegramContextKey(rawKey)) {
|
|
@@ -97,6 +120,16 @@ export function parseChannelContextKey(key) {
|
|
|
97
120
|
guildId: discord.guildId,
|
|
98
121
|
};
|
|
99
122
|
}
|
|
123
|
+
const slack = parseSlackContextKey(rawKey);
|
|
124
|
+
if (slack) {
|
|
125
|
+
return {
|
|
126
|
+
channelId: "slack",
|
|
127
|
+
contextKey: rawKey,
|
|
128
|
+
chatId: slack.channelId,
|
|
129
|
+
topicId: slack.threadTs,
|
|
130
|
+
guildId: slack.teamId,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
100
133
|
if (rawKey.startsWith("web:")) {
|
|
101
134
|
return {
|
|
102
135
|
channelId: "web",
|
|
@@ -111,6 +144,15 @@ export function parseChannelContextKey(key) {
|
|
|
111
144
|
chatId: rawKey.slice("cli:".length) || "local",
|
|
112
145
|
};
|
|
113
146
|
}
|
|
147
|
+
const peer = parsePeerRuntimeContextKey(rawKey);
|
|
148
|
+
if (peer) {
|
|
149
|
+
return {
|
|
150
|
+
channelId: "peer",
|
|
151
|
+
contextKey: rawKey,
|
|
152
|
+
chatId: peer.peerId,
|
|
153
|
+
topicId: peer.sourceContextKey,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
114
156
|
return null;
|
|
115
157
|
}
|
|
116
158
|
export function channelIdForContextKey(key) {
|