@nordbyte/nordrelay 0.7.0 → 0.8.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.
- package/.env.example +35 -0
- package/README.md +118 -49
- package/dist/activity-events.js +2 -2
- package/dist/adapter-conformance.js +61 -0
- package/dist/bot.js +18 -31
- package/dist/channel-adapter.js +33 -6
- package/dist/channel-command-catalog.js +6 -0
- package/dist/channel-command-core.js +60 -0
- package/dist/channel-command-service.js +20 -4
- package/dist/channel-mirror-registry.js +9 -2
- package/dist/channel-prompt-engine.js +177 -0
- package/dist/channel-turn-lifecycle.js +73 -0
- package/dist/config-metadata.js +67 -8
- package/dist/config.js +48 -1
- package/dist/context-key.js +32 -0
- package/dist/discord-bot.js +99 -327
- package/dist/index.js +9 -0
- package/dist/metrics.js +2 -0
- package/dist/peer-client.js +90 -2
- package/dist/peer-readiness.js +77 -0
- package/dist/peer-runtime-service.js +22 -0
- package/dist/peer-server.js +20 -4
- package/dist/peer-store.js +17 -2
- package/dist/relay-runtime-helpers.js +3 -1
- package/dist/relay-runtime.js +7 -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/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 +8 -0
- package/dist/web-dashboard-access-routes.js +62 -0
- package/dist/web-dashboard-assets.js +1 -0
- package/dist/web-dashboard-pages.js +14 -4
- package/dist/web-dashboard-peer-routes.js +32 -11
- package/dist/web-dashboard.js +34 -0
- package/dist/web-state.js +2 -2
- package/dist/webui-assets/dashboard.css +193 -0
- package/dist/webui-assets/dashboard.js +546 -145
- package/package.json +3 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +105 -11
|
@@ -209,7 +209,7 @@ export function renderDashboardApp() {
|
|
|
209
209
|
|
|
210
210
|
<section class="page" id="page-activity">
|
|
211
211
|
<div class="panel">
|
|
212
|
-
<div class="row"><select id="activitySource"><option value="all">All sources</option><option value="web">Web</option><option value="telegram">Telegram</option><option value="discord">Discord</option><option value="cli">CLI</option></select><select id="activityCategory"><option value="all">All categories</option><option value="prompt">Prompt</option><option value="session">Session</option><option value="queue">Queue</option><option value="agent-update">Agent update</option><option value="artifact">Artifact</option><option value="system">System</option><option value="auth">Auth</option><option value="security">Security</option><option value="tool">Tool</option></select><select id="activityStatus"><option value="all">All statuses</option><option value="queued">Queued</option><option value="running">Running</option><option value="completed">Completed</option><option value="failed">Failed</option><option value="aborted">Aborted</option><option value="info">Info</option></select><input id="activityActor" placeholder="Actor"><input id="activityAgent" placeholder="Agent"><input id="activityThread" placeholder="Thread ID"><input id="activityWorkspace" placeholder="Workspace"><input id="activityType" placeholder="Type"><input id="activitySince" type="datetime-local"><input id="activityLimit" type="number" value="100" min="1" max="500"><button id="loadActivityBtn">Load activity</button><button id="exportActivityBtn" class="secondary">Export</button></div>
|
|
212
|
+
<div class="row"><select id="activitySource"><option value="all">All sources</option><option value="web">Web</option><option value="telegram">Telegram</option><option value="discord">Discord</option><option value="slack">Slack</option><option value="cli">CLI</option></select><select id="activityCategory"><option value="all">All categories</option><option value="prompt">Prompt</option><option value="session">Session</option><option value="queue">Queue</option><option value="agent-update">Agent update</option><option value="artifact">Artifact</option><option value="system">System</option><option value="auth">Auth</option><option value="security">Security</option><option value="tool">Tool</option></select><select id="activityStatus"><option value="all">All statuses</option><option value="queued">Queued</option><option value="running">Running</option><option value="completed">Completed</option><option value="failed">Failed</option><option value="aborted">Aborted</option><option value="info">Info</option></select><input id="activityActor" placeholder="Actor"><input id="activityAgent" placeholder="Agent"><input id="activityThread" placeholder="Thread ID"><input id="activityWorkspace" placeholder="Workspace"><input id="activityType" placeholder="Type"><input id="activitySince" type="datetime-local"><input id="activityLimit" type="number" value="100" min="1" max="500"><button id="loadActivityBtn">Load activity</button><button id="exportActivityBtn" class="secondary">Export</button></div>
|
|
213
213
|
<div id="activityList" class="list"></div>
|
|
214
214
|
</div>
|
|
215
215
|
</section>
|
|
@@ -226,6 +226,8 @@ export function renderDashboardApp() {
|
|
|
226
226
|
<div class="panel">
|
|
227
227
|
<div class="row"><button id="reloadAdaptersBtn">Reload adapters</button></div>
|
|
228
228
|
<div id="adapterHealth" class="list"></div>
|
|
229
|
+
<h2 class="task-section-title">Adapter Conformance</h2>
|
|
230
|
+
<div id="adapterConformance" class="list"></div>
|
|
229
231
|
</div>
|
|
230
232
|
</section>
|
|
231
233
|
|
|
@@ -242,12 +244,13 @@ export function renderDashboardApp() {
|
|
|
242
244
|
|
|
243
245
|
<section class="page" id="page-access">
|
|
244
246
|
<div class="panel">
|
|
245
|
-
<div class="row"><button id="loadAccessBtn">Reload users</button><button id="createUserBtn">Create user</button><button id="createGroupBtn" class="secondary">Create group</button><button id="createChatBtn" class="secondary">Add Telegram chat</button><button id="createDiscordChannelBtn" class="secondary">Add Discord channel</button><button id="lockSessionBtn" class="secondary">Lock web session</button><button id="unlockSessionBtn" class="secondary">Unlock web session</button></div>
|
|
247
|
+
<div class="row"><button id="loadAccessBtn">Reload users</button><button id="createUserBtn">Create user</button><button id="createGroupBtn" class="secondary">Create group</button><button id="createChatBtn" class="secondary">Add Telegram chat</button><button id="createDiscordChannelBtn" class="secondary">Add Discord channel</button><button id="createSlackChannelBtn" class="secondary">Add Slack channel</button><button id="lockSessionBtn" class="secondary">Lock web session</button><button id="unlockSessionBtn" class="secondary">Unlock web session</button></div>
|
|
246
248
|
<div id="accessTabs" class="tabs access-tabs">
|
|
247
249
|
<button type="button" data-access-tab="users" class="active">Users</button>
|
|
248
250
|
<button type="button" data-access-tab="groups">Groups</button>
|
|
249
251
|
<button type="button" data-access-tab="telegram">Telegram</button>
|
|
250
252
|
<button type="button" data-access-tab="discord">Discord</button>
|
|
253
|
+
<button type="button" data-access-tab="slack">Slack</button>
|
|
251
254
|
<button type="button" data-access-tab="locks">Locks</button>
|
|
252
255
|
<button type="button" data-access-tab="audit">Audit</button>
|
|
253
256
|
</div>
|
|
@@ -269,13 +272,20 @@ export function renderDashboardApp() {
|
|
|
269
272
|
</div>
|
|
270
273
|
<div id="discordChannelsList" class="list"></div>
|
|
271
274
|
</div>
|
|
275
|
+
<div class="access-tab" data-access-tab-panel="slack">
|
|
276
|
+
<div class="access-tab-heading">
|
|
277
|
+
<h2>Slack channels</h2>
|
|
278
|
+
<input id="slackChannelSearch" placeholder="Search Slack channels">
|
|
279
|
+
</div>
|
|
280
|
+
<div id="slackChannelsList" class="list"></div>
|
|
281
|
+
</div>
|
|
272
282
|
<div class="access-tab" data-access-tab-panel="locks">
|
|
273
283
|
<h2>Locks</h2>
|
|
274
284
|
<div id="locksList" class="list"></div>
|
|
275
285
|
</div>
|
|
276
286
|
<div class="access-tab" data-access-tab-panel="audit">
|
|
277
287
|
<h2>Audit</h2>
|
|
278
|
-
<div class="row"><select id="auditChannel"><option value="all">All channels</option><option value="web">Web</option><option value="telegram">Telegram</option><option value="discord">Discord</option></select><select id="auditCategory"><option value="all">All categories</option><option value="prompt">Prompt</option><option value="session">Session</option><option value="queue">Queue</option><option value="agent-update">Agent update</option><option value="artifact">Artifact</option><option value="system">System</option><option value="auth">Auth</option><option value="security">Security</option><option value="tool">Tool</option></select><select id="auditStatus"><option value="all">All statuses</option><option value="ok">OK</option><option value="failed">Failed</option><option value="denied">Denied</option></select><input id="auditActor" placeholder="Actor"><input id="auditAgent" placeholder="Agent"><input id="auditThread" placeholder="Thread ID"><input id="auditWorkspace" placeholder="Workspace"><input id="auditSince" type="datetime-local"><input id="auditLimit" type="number" value="50" min="1" max="500"><button id="loadAuditBtn">Load audit</button><button id="exportAuditBtn" class="secondary">Export</button></div>
|
|
288
|
+
<div class="row"><select id="auditChannel"><option value="all">All channels</option><option value="web">Web</option><option value="telegram">Telegram</option><option value="discord">Discord</option><option value="slack">Slack</option></select><select id="auditCategory"><option value="all">All categories</option><option value="prompt">Prompt</option><option value="session">Session</option><option value="queue">Queue</option><option value="agent-update">Agent update</option><option value="artifact">Artifact</option><option value="system">System</option><option value="auth">Auth</option><option value="security">Security</option><option value="tool">Tool</option></select><select id="auditStatus"><option value="all">All statuses</option><option value="ok">OK</option><option value="failed">Failed</option><option value="denied">Denied</option></select><input id="auditActor" placeholder="Actor"><input id="auditAgent" placeholder="Agent"><input id="auditThread" placeholder="Thread ID"><input id="auditWorkspace" placeholder="Workspace"><input id="auditSince" type="datetime-local"><input id="auditLimit" type="number" value="50" min="1" max="500"><button id="loadAuditBtn">Load audit</button><button id="exportAuditBtn" class="secondary">Export</button></div>
|
|
279
289
|
<div id="auditList" class="list"></div>
|
|
280
290
|
</div>
|
|
281
291
|
</div>
|
|
@@ -292,7 +302,7 @@ export function renderDashboardApp() {
|
|
|
292
302
|
|
|
293
303
|
<section class="page" id="page-settings">
|
|
294
304
|
<div class="panel">
|
|
295
|
-
<div class="row"><button id="saveSettingsBtn">Save settings</button><button id="restartBtn" class="secondary">Restart NordRelay</button><span id="settingsStatus"></span></div>
|
|
305
|
+
<div class="row"><button id="saveSettingsBtn">Save settings</button><button id="settingsWizardBtn" class="secondary">Setup wizard</button><button id="restartBtn" class="secondary">Restart NordRelay</button><span id="settingsStatus"></span></div>
|
|
296
306
|
<div id="settingsTabs" class="tabs"></div>
|
|
297
307
|
<div id="settingsForm" class="settings-grid"></div>
|
|
298
308
|
</div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isPermission } from "./access-control.js";
|
|
2
2
|
import { AGENT_IDS, isAgentId } from "./agent.js";
|
|
3
3
|
import { ensurePeerTlsFiles, loadOrCreatePeerIdentity, } from "./peer-identity.js";
|
|
4
|
-
import { pairPeer, RemoteRelayClient } from "./peer-client.js";
|
|
4
|
+
import { checkPeerEndpoint, pairPeer, RemoteRelayClient } from "./peer-client.js";
|
|
5
|
+
import { buildPeerReadiness, peerListenUrl } from "./peer-readiness.js";
|
|
5
6
|
import { PeerStore } from "./peer-store.js";
|
|
6
7
|
import { publicPeer } from "./peer-types.js";
|
|
7
8
|
import { arrayStringField, objectRecord, optionalBooleanField, optionalNumberField, optionalStringField, readJsonBody, sendJson, } from "./web-dashboard-http.js";
|
|
@@ -10,15 +11,18 @@ export async function handleDashboardPeerRoute(req, res, url, options) {
|
|
|
10
11
|
const identity = loadOrCreatePeerIdentity(options.home, options.config.peerName);
|
|
11
12
|
const tls = options.config.peerTlsEnabled ? ensurePeerTlsFiles(options.home, identity.public) : null;
|
|
12
13
|
if (req.method === "GET" && url.pathname === "/api/peers") {
|
|
14
|
+
const readiness = await buildPeerReadiness(options.config);
|
|
13
15
|
sendJson(res, 200, store.snapshot(identity.public, {
|
|
14
16
|
enabled: options.config.peerEnabled,
|
|
15
|
-
listenUrl:
|
|
17
|
+
listenUrl: readiness.listenUrl,
|
|
16
18
|
requireTls: options.config.peerRequireTls,
|
|
19
|
+
readiness,
|
|
17
20
|
}));
|
|
18
21
|
return true;
|
|
19
22
|
}
|
|
20
23
|
if (req.method === "POST" && url.pathname === "/api/peers/invite") {
|
|
21
24
|
const body = await readJsonBody(req);
|
|
25
|
+
const readiness = await buildPeerReadiness(options.config);
|
|
22
26
|
const created = store.createInvitation({
|
|
23
27
|
name: optionalStringField(body, "name"),
|
|
24
28
|
expiresInMs: (optionalNumberField(body, "expiresMinutes") ?? 10) * 60 * 1000,
|
|
@@ -27,7 +31,7 @@ export async function handleDashboardPeerRoute(req, res, url, options) {
|
|
|
27
31
|
allowedWorkspaceRoots: arrayStringField(body, "allowedWorkspaceRoots"),
|
|
28
32
|
workspaceAliases: parseWorkspaceAliases(body.workspaceAliases),
|
|
29
33
|
});
|
|
30
|
-
const listenUrl =
|
|
34
|
+
const listenUrl = readiness.listenUrl;
|
|
31
35
|
const command = `nordrelay peer add ${listenUrl} --code ${created.code}`;
|
|
32
36
|
sendJson(res, 201, {
|
|
33
37
|
invitation: created.invitation,
|
|
@@ -36,10 +40,35 @@ export async function handleDashboardPeerRoute(req, res, url, options) {
|
|
|
36
40
|
fingerprint: identity.public.fingerprint,
|
|
37
41
|
tlsFingerprint: tls?.fingerprint,
|
|
38
42
|
command,
|
|
43
|
+
readiness,
|
|
44
|
+
warnings: readiness.warnings,
|
|
39
45
|
});
|
|
40
46
|
options.auditPeerAction?.("peer_invite_created", created.invitation.name);
|
|
41
47
|
return true;
|
|
42
48
|
}
|
|
49
|
+
if (req.method === "POST" && url.pathname === "/api/peers/probe") {
|
|
50
|
+
const body = await readJsonBody(req);
|
|
51
|
+
const readiness = await buildPeerReadiness(options.config);
|
|
52
|
+
const peerId = optionalStringField(body, "peerId");
|
|
53
|
+
if (peerId) {
|
|
54
|
+
const probe = await new RemoteRelayClient(store).rpc(peerId, "peer.probe", {}, options.activityActor);
|
|
55
|
+
sendJson(res, 200, { type: "remote", peerId, readiness, probe });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const expectedTlsFingerprint = options.config.peerPublicUrl ? undefined : tls?.fingerprint;
|
|
59
|
+
const probe = await checkPeerEndpoint(readiness.listenUrl, { expectedTlsFingerprint });
|
|
60
|
+
sendJson(res, 200, { type: "local", readiness, probe });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const invitationMatch = url.pathname.match(/^\/api\/peers\/invitations\/([^/]+)$/);
|
|
64
|
+
if (invitationMatch?.[1] && req.method === "DELETE") {
|
|
65
|
+
const invitation = store.deleteInvitation(decodeURIComponent(invitationMatch[1]));
|
|
66
|
+
sendJson(res, 200, { removed: Boolean(invitation), invitation });
|
|
67
|
+
if (invitation) {
|
|
68
|
+
options.auditPeerAction?.("peer_invite_deleted", `${invitation.name} (${invitation.id})`);
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
43
72
|
if (req.method === "POST" && (url.pathname === "/api/peers" || url.pathname === "/api/peers/pair")) {
|
|
44
73
|
const body = await readJsonBody(req);
|
|
45
74
|
const result = await pairPeer({
|
|
@@ -157,14 +186,6 @@ export async function handleDashboardPeerRoute(req, res, url, options) {
|
|
|
157
186
|
}
|
|
158
187
|
return false;
|
|
159
188
|
}
|
|
160
|
-
function peerListenUrl(config) {
|
|
161
|
-
if (config.peerPublicUrl)
|
|
162
|
-
return config.peerPublicUrl;
|
|
163
|
-
const scheme = config.peerTlsEnabled ? "https" : "http";
|
|
164
|
-
const host = config.peerHost === "0.0.0.0" || config.peerHost === "::" ? "127.0.0.1" : config.peerHost;
|
|
165
|
-
const displayHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
166
|
-
return `${scheme}://${displayHost}:${config.peerPort}`;
|
|
167
|
-
}
|
|
168
189
|
function parseScopes(values) {
|
|
169
190
|
return values.filter(isPermission);
|
|
170
191
|
}
|
package/dist/web-dashboard.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { URL } from "node:url";
|
|
6
6
|
import { enabledAgents } from "./agent-factory.js";
|
|
7
|
+
import { buildAdapterConformanceMatrix } from "./adapter-conformance.js";
|
|
7
8
|
import { listAgentAdapterDescriptors } from "./agent-adapter.js";
|
|
8
9
|
import { isAgentId } from "./agent.js";
|
|
9
10
|
import { AuditLogStore } from "./audit-log.js";
|
|
@@ -13,6 +14,7 @@ import { loadConfig } from "./config.js";
|
|
|
13
14
|
import { friendlyErrorText } from "./error-messages.js";
|
|
14
15
|
import { RelayRuntime } from "./relay-runtime.js";
|
|
15
16
|
import { resolveDashboardEnvPath, SettingsService } from "./settings-service.js";
|
|
17
|
+
import { mergeSettingsWizardTestSettings, runSettingsWizardTest } from "./settings-wizard-test.js";
|
|
16
18
|
import { UserStore, publicUser } from "./user-management.js";
|
|
17
19
|
import { handleDashboardAccessRoute } from "./web-dashboard-access-routes.js";
|
|
18
20
|
import { handleDashboardArtifactRoute } from "./web-dashboard-artifact-routes.js";
|
|
@@ -161,6 +163,7 @@ async function handleApi(req, res, url, authUser) {
|
|
|
161
163
|
auth: currentUserDto(authUser),
|
|
162
164
|
channels: listChannelDescriptors(),
|
|
163
165
|
agentAdapters: listAgentAdapterDescriptors().filter((adapter) => users.canUseAgent(authUser, adapter.id)),
|
|
166
|
+
adapterConformance: scopedAdapterConformance(authUser),
|
|
164
167
|
enabledAgents: enabledAgents(config).filter((agentId) => users.canUseAgent(authUser, agentId)),
|
|
165
168
|
controls: scopedControlOptions(authUser, await runtime.controlOptions()),
|
|
166
169
|
status: await runtime.bootstrapStatus(),
|
|
@@ -173,6 +176,10 @@ async function handleApi(req, res, url, authUser) {
|
|
|
173
176
|
sendJson(res, 200, scopedControlOptions(authUser, await runtime.controlOptions(agentId)));
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
179
|
+
if (req.method === "GET" && url.pathname === "/api/adapters/conformance") {
|
|
180
|
+
sendJson(res, 200, scopedAdapterConformance(authUser));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
176
183
|
if (await handleDashboardAccessRoute(req, res, url, {
|
|
177
184
|
users,
|
|
178
185
|
runtime,
|
|
@@ -199,6 +206,11 @@ async function handleApi(req, res, url, authUser) {
|
|
|
199
206
|
sendJson(res, 200, await settings.update(objectRecord(body?.settings)));
|
|
200
207
|
return;
|
|
201
208
|
}
|
|
209
|
+
if (req.method === "POST" && url.pathname === "/api/settings/wizard/test") {
|
|
210
|
+
const body = await readJsonBody(req);
|
|
211
|
+
sendJson(res, 200, await runSettingsWizardTest(optionalStringField(body, "channel") ?? "", mergeSettingsWizardTestSettings(activeSettingsValues(config), objectRecord(body?.settings))));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
202
214
|
if (await handleDashboardSessionRoute(req, res, url, {
|
|
203
215
|
runtime,
|
|
204
216
|
authUser,
|
|
@@ -474,6 +486,13 @@ function scopedControlOptions(authUser, options) {
|
|
|
474
486
|
workspaces: options.workspaces.filter((workspace) => users.canUseWorkspace(authUser, workspace)),
|
|
475
487
|
};
|
|
476
488
|
}
|
|
489
|
+
function scopedAdapterConformance(authUser) {
|
|
490
|
+
const matrix = buildAdapterConformanceMatrix();
|
|
491
|
+
return {
|
|
492
|
+
...matrix,
|
|
493
|
+
agents: matrix.agents.filter((adapter) => users.canUseAgent(authUser, adapter.id)),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
477
496
|
function scopedSessionPage(authUser, page) {
|
|
478
497
|
return {
|
|
479
498
|
...page,
|
|
@@ -652,6 +671,21 @@ function activeSettingsValues(current) {
|
|
|
652
671
|
DISCORD_NOTIFY_MODE: current.discordNotifyMode === current.notifyMode ? "" : current.discordNotifyMode,
|
|
653
672
|
DISCORD_QUIET_HOURS: quietOverrideValue(current.discordQuietHours, current.quietHours),
|
|
654
673
|
DISCORD_AUTO_SEND_ARTIFACTS: current.discordAutoSendArtifacts === current.autoSendArtifacts ? "" : boolValue(current.discordAutoSendArtifacts),
|
|
674
|
+
SLACK_ENABLED: boolValue(current.slackEnabled),
|
|
675
|
+
SLACK_BOT_TOKEN: current.slackBotToken,
|
|
676
|
+
SLACK_APP_TOKEN: current.slackAppToken,
|
|
677
|
+
SLACK_SIGNING_SECRET: current.slackSigningSecret,
|
|
678
|
+
SLACK_SOCKET_MODE: boolValue(current.slackSocketMode),
|
|
679
|
+
SLACK_PORT: String(current.slackPort),
|
|
680
|
+
SLACK_ALLOWED_TEAM_IDS: current.slackAllowedTeamIds.join(","),
|
|
681
|
+
SLACK_ALLOWED_CHANNEL_IDS: current.slackAllowedChannelIds.join(","),
|
|
682
|
+
SLACK_MESSAGE_CONTENT_ENABLED: boolValue(current.slackMessageContentEnabled),
|
|
683
|
+
SLACK_COMMAND: current.slackCommand,
|
|
684
|
+
SLACK_CLI_MIRROR_MODE: current.slackMirrorMode === current.mirrorMode ? "" : current.slackMirrorMode,
|
|
685
|
+
SLACK_CLI_MIRROR_MIN_UPDATE_MS: current.slackMirrorMinUpdateMs === current.mirrorMinUpdateMs ? "" : String(current.slackMirrorMinUpdateMs),
|
|
686
|
+
SLACK_NOTIFY_MODE: current.slackNotifyMode === current.notifyMode ? "" : current.slackNotifyMode,
|
|
687
|
+
SLACK_QUIET_HOURS: quietOverrideValue(current.slackQuietHours, current.quietHours),
|
|
688
|
+
SLACK_AUTO_SEND_ARTIFACTS: current.slackAutoSendArtifacts === current.autoSendArtifacts ? "" : boolValue(current.slackAutoSendArtifacts),
|
|
655
689
|
NORDRELAY_CODEX_ENABLED: boolValue(current.codexEnabled),
|
|
656
690
|
NORDRELAY_PI_ENABLED: boolValue(current.piEnabled),
|
|
657
691
|
NORDRELAY_HERMES_ENABLED: boolValue(current.hermesEnabled),
|
package/dist/web-state.js
CHANGED
|
@@ -123,7 +123,7 @@ function isWebChatMessage(value) {
|
|
|
123
123
|
typeof candidate.text === "string" &&
|
|
124
124
|
typeof candidate.timestamp === "string" &&
|
|
125
125
|
["user", "agent", "system", "tool"].includes(candidate.role) &&
|
|
126
|
-
["web", "telegram", "discord", "cli"].includes(candidate.source);
|
|
126
|
+
["web", "telegram", "discord", "slack", "cli"].includes(candidate.source);
|
|
127
127
|
}
|
|
128
128
|
function isWebActivityEvent(value) {
|
|
129
129
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -133,7 +133,7 @@ function isWebActivityEvent(value) {
|
|
|
133
133
|
return typeof candidate.id === "string" &&
|
|
134
134
|
typeof candidate.timestamp === "string" &&
|
|
135
135
|
typeof candidate.type === "string" &&
|
|
136
|
-
["web", "telegram", "discord", "cli"].includes(candidate.source) &&
|
|
136
|
+
["web", "telegram", "discord", "slack", "cli"].includes(candidate.source) &&
|
|
137
137
|
["queued", "running", "completed", "failed", "aborted", "info"].includes(candidate.status);
|
|
138
138
|
}
|
|
139
139
|
function normalizeSince(value) {
|
|
@@ -182,6 +182,15 @@
|
|
|
182
182
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
183
183
|
gap: 16px;
|
|
184
184
|
}
|
|
185
|
+
.conformance-grid {
|
|
186
|
+
display: grid;
|
|
187
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
188
|
+
gap: 12px;
|
|
189
|
+
}
|
|
190
|
+
.conformance-grid h3 {
|
|
191
|
+
margin: 0 0 8px;
|
|
192
|
+
font-size: 15px;
|
|
193
|
+
}
|
|
185
194
|
.session-detail-section {
|
|
186
195
|
margin-top: 20px;
|
|
187
196
|
}
|
|
@@ -239,6 +248,115 @@
|
|
|
239
248
|
padding: 10px;
|
|
240
249
|
margin: 0 0 12px;
|
|
241
250
|
}
|
|
251
|
+
.settings-wizard {
|
|
252
|
+
display: grid;
|
|
253
|
+
gap: 14px;
|
|
254
|
+
margin-top: 14px;
|
|
255
|
+
}
|
|
256
|
+
.wizard-header {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: flex-start;
|
|
259
|
+
justify-content: space-between;
|
|
260
|
+
gap: 12px;
|
|
261
|
+
}
|
|
262
|
+
.wizard-header h2 {
|
|
263
|
+
margin: 0 0 4px;
|
|
264
|
+
}
|
|
265
|
+
.wizard-header p,
|
|
266
|
+
.wizard-step p,
|
|
267
|
+
.wizard-card p {
|
|
268
|
+
margin: 0;
|
|
269
|
+
color: var(--muted);
|
|
270
|
+
line-height: 1.45;
|
|
271
|
+
}
|
|
272
|
+
.wizard-choice-grid {
|
|
273
|
+
display: grid;
|
|
274
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
275
|
+
gap: 12px;
|
|
276
|
+
}
|
|
277
|
+
.wizard-card {
|
|
278
|
+
display: grid;
|
|
279
|
+
gap: 8px;
|
|
280
|
+
border: 1px solid var(--border-soft);
|
|
281
|
+
border-radius: 8px;
|
|
282
|
+
background: var(--surface-soft);
|
|
283
|
+
padding: 12px;
|
|
284
|
+
min-width: 0;
|
|
285
|
+
}
|
|
286
|
+
.wizard-links {
|
|
287
|
+
display: flex;
|
|
288
|
+
gap: 8px;
|
|
289
|
+
align-items: center;
|
|
290
|
+
flex-wrap: wrap;
|
|
291
|
+
}
|
|
292
|
+
.wizard-links a {
|
|
293
|
+
font-size: 12px;
|
|
294
|
+
border: 1px solid var(--border);
|
|
295
|
+
border-radius: 999px;
|
|
296
|
+
padding: 4px 8px;
|
|
297
|
+
color: var(--accent);
|
|
298
|
+
text-decoration: none;
|
|
299
|
+
background: var(--surface);
|
|
300
|
+
}
|
|
301
|
+
.wizard-links a:hover {
|
|
302
|
+
text-decoration: underline;
|
|
303
|
+
}
|
|
304
|
+
.wizard-progress {
|
|
305
|
+
display: flex;
|
|
306
|
+
gap: 8px;
|
|
307
|
+
flex-wrap: wrap;
|
|
308
|
+
}
|
|
309
|
+
.wizard-progress button {
|
|
310
|
+
background: var(--surface);
|
|
311
|
+
color: var(--text);
|
|
312
|
+
border-color: var(--border);
|
|
313
|
+
min-height: 32px;
|
|
314
|
+
height: 32px;
|
|
315
|
+
line-height: 1;
|
|
316
|
+
}
|
|
317
|
+
.wizard-progress button:hover {
|
|
318
|
+
background: var(--accent-strong);
|
|
319
|
+
color: white;
|
|
320
|
+
border-color: var(--accent-strong);
|
|
321
|
+
}
|
|
322
|
+
.wizard-progress button.active {
|
|
323
|
+
background: var(--accent);
|
|
324
|
+
color: white;
|
|
325
|
+
border-color: var(--accent);
|
|
326
|
+
}
|
|
327
|
+
.wizard-step {
|
|
328
|
+
display: grid;
|
|
329
|
+
gap: 12px;
|
|
330
|
+
}
|
|
331
|
+
.wizard-step h3 {
|
|
332
|
+
margin: 0;
|
|
333
|
+
}
|
|
334
|
+
.wizard-actions {
|
|
335
|
+
display: flex;
|
|
336
|
+
gap: 8px;
|
|
337
|
+
align-items: center;
|
|
338
|
+
flex-wrap: wrap;
|
|
339
|
+
}
|
|
340
|
+
.wizard-errors {
|
|
341
|
+
display: grid;
|
|
342
|
+
gap: 6px;
|
|
343
|
+
}
|
|
344
|
+
.wizard-error,
|
|
345
|
+
.wizard-warning {
|
|
346
|
+
border-radius: 8px;
|
|
347
|
+
padding: 8px 10px;
|
|
348
|
+
font-size: 13px;
|
|
349
|
+
}
|
|
350
|
+
.wizard-error {
|
|
351
|
+
border: 1px solid var(--danger);
|
|
352
|
+
color: var(--danger);
|
|
353
|
+
background: color-mix(in srgb, var(--danger) 8%, transparent);
|
|
354
|
+
}
|
|
355
|
+
.wizard-warning {
|
|
356
|
+
border: 1px solid #d9c27a;
|
|
357
|
+
color: #8a6a12;
|
|
358
|
+
background: var(--warn);
|
|
359
|
+
}
|
|
242
360
|
.task-grid,
|
|
243
361
|
.metrics-grid {
|
|
244
362
|
display: grid;
|
|
@@ -340,6 +458,69 @@
|
|
|
340
458
|
gap: 8px;
|
|
341
459
|
margin-top: 8px;
|
|
342
460
|
}
|
|
461
|
+
.peer-invite-details {
|
|
462
|
+
display: grid;
|
|
463
|
+
gap: 6px;
|
|
464
|
+
margin: 10px 0;
|
|
465
|
+
padding: 10px;
|
|
466
|
+
border: 1px solid var(--border-soft);
|
|
467
|
+
border-radius: 8px;
|
|
468
|
+
background: var(--surface);
|
|
469
|
+
}
|
|
470
|
+
.peer-invite-details small {
|
|
471
|
+
margin-top: 0;
|
|
472
|
+
font-weight: 700;
|
|
473
|
+
}
|
|
474
|
+
.peer-invite-copy,
|
|
475
|
+
.peer-invite-command {
|
|
476
|
+
display: block;
|
|
477
|
+
width: 100%;
|
|
478
|
+
text-align: left;
|
|
479
|
+
overflow-wrap: anywhere;
|
|
480
|
+
white-space: pre-wrap;
|
|
481
|
+
}
|
|
482
|
+
.copy-id.peer-invite-command {
|
|
483
|
+
padding: 8px;
|
|
484
|
+
border: 1px solid var(--border-soft);
|
|
485
|
+
border-radius: 6px;
|
|
486
|
+
background: var(--surface);
|
|
487
|
+
color: var(--text);
|
|
488
|
+
text-decoration: none;
|
|
489
|
+
}
|
|
490
|
+
.copy-id.peer-invite-command:hover {
|
|
491
|
+
background: var(--accent-soft);
|
|
492
|
+
border-color: var(--accent);
|
|
493
|
+
color: var(--link);
|
|
494
|
+
text-decoration: none;
|
|
495
|
+
}
|
|
496
|
+
.peer-warning {
|
|
497
|
+
display: grid;
|
|
498
|
+
gap: 4px;
|
|
499
|
+
margin: 10px 0;
|
|
500
|
+
padding: 10px;
|
|
501
|
+
border: 1px solid #d9c27a;
|
|
502
|
+
border-radius: 8px;
|
|
503
|
+
background: var(--warn);
|
|
504
|
+
color: #8a6a12;
|
|
505
|
+
}
|
|
506
|
+
.peer-warning small {
|
|
507
|
+
color: #8a6a12;
|
|
508
|
+
}
|
|
509
|
+
.peer-probe-result {
|
|
510
|
+
margin-top: 10px;
|
|
511
|
+
padding: 10px;
|
|
512
|
+
border: 1px solid var(--border-soft);
|
|
513
|
+
border-radius: 8px;
|
|
514
|
+
background: var(--surface);
|
|
515
|
+
}
|
|
516
|
+
.peer-probe-result.ok {
|
|
517
|
+
border-color: #8ed0aa;
|
|
518
|
+
background: color-mix(in srgb, var(--accent-soft) 45%, var(--surface));
|
|
519
|
+
}
|
|
520
|
+
.peer-probe-result.warn {
|
|
521
|
+
border-color: #d9c27a;
|
|
522
|
+
background: var(--warn);
|
|
523
|
+
}
|
|
343
524
|
* {
|
|
344
525
|
box-sizing: border-box;
|
|
345
526
|
}
|
|
@@ -991,6 +1172,9 @@ dialog::backdrop {
|
|
|
991
1172
|
.overview-adapter-grid {
|
|
992
1173
|
grid-template-columns: 1fr;
|
|
993
1174
|
}
|
|
1175
|
+
.conformance-grid {
|
|
1176
|
+
grid-template-columns: 1fr;
|
|
1177
|
+
}
|
|
994
1178
|
.chat-layout {
|
|
995
1179
|
grid-template-columns: 1fr;
|
|
996
1180
|
}
|
|
@@ -1060,4 +1244,13 @@ dialog::backdrop {
|
|
|
1060
1244
|
margin-left: 0;
|
|
1061
1245
|
justify-content: stretch;
|
|
1062
1246
|
}
|
|
1247
|
+
.wizard-header,
|
|
1248
|
+
.wizard-actions {
|
|
1249
|
+
align-items: stretch;
|
|
1250
|
+
flex-direction: column;
|
|
1251
|
+
}
|
|
1252
|
+
.wizard-header button,
|
|
1253
|
+
.wizard-actions button {
|
|
1254
|
+
width: 100%;
|
|
1255
|
+
}
|
|
1063
1256
|
}
|