@nordbyte/nordrelay 0.5.1 → 0.6.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 +65 -11
- package/README.md +97 -23
- package/dist/access-control.js +1 -0
- package/dist/activity-events.js +44 -0
- package/dist/agent-updates.js +18 -2
- package/dist/audit-log.js +40 -2
- package/dist/bot-rendering.js +10 -7
- package/dist/bot.js +492 -7
- package/dist/channel-actions.js +7 -2
- package/dist/channel-adapter.js +34 -7
- package/dist/channel-command-service.js +156 -0
- package/dist/channel-turn-service.js +237 -0
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +80 -13
- package/dist/config.js +77 -7
- package/dist/context-key.js +77 -5
- package/dist/discord-artifacts.js +165 -0
- package/dist/discord-bot.js +2014 -0
- package/dist/discord-channel-runtime.js +133 -0
- package/dist/discord-command-surface.js +119 -0
- package/dist/discord-rate-limit.js +141 -0
- package/dist/index.js +16 -5
- package/dist/job-store.js +127 -0
- package/dist/metrics.js +41 -0
- package/dist/operations.js +176 -119
- package/dist/relay-external-activity-monitor.js +47 -6
- package/dist/relay-runtime.js +1003 -268
- package/dist/runtime-cache.js +57 -0
- package/dist/session-locks.js +10 -7
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +18 -1
- package/dist/telegram-access-commands.js +15 -2
- package/dist/telegram-access-middleware.js +16 -3
- package/dist/telegram-agent-commands.js +25 -0
- package/dist/telegram-artifact-commands.js +46 -0
- package/dist/telegram-diagnostics-command.js +5 -50
- package/dist/telegram-general-commands.js +2 -6
- package/dist/telegram-operational-commands.js +14 -6
- package/dist/telegram-queue-commands.js +74 -4
- package/dist/telegram-support-command.js +7 -0
- package/dist/telegram-update-commands.js +27 -0
- package/dist/user-management.js +208 -0
- package/dist/web-api-contract.js +9 -0
- package/dist/web-dashboard-access-routes.js +74 -1
- package/dist/web-dashboard-artifact-routes.js +3 -3
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-pages.js +97 -13
- package/dist/web-dashboard-runtime-routes.js +53 -8
- package/dist/web-dashboard-session-routes.js +27 -20
- package/dist/web-dashboard-ui.js +1 -0
- package/dist/web-dashboard.js +149 -6
- package/dist/web-state.js +33 -2
- package/dist/webui-assets/dashboard.css +75 -1
- package/dist/webui-assets/dashboard.js +358 -47
- package/package.json +3 -1
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +468 -22
package/dist/web-dashboard.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { URL } from "node:url";
|
|
@@ -17,7 +18,7 @@ import { handleDashboardAccessRoute } from "./web-dashboard-access-routes.js";
|
|
|
17
18
|
import { handleDashboardArtifactRoute } from "./web-dashboard-artifact-routes.js";
|
|
18
19
|
import { dashboardCss, dashboardJs } from "./web-dashboard-assets.js";
|
|
19
20
|
import { objectRecord, optionalStringField, parseCookies, readJsonBody, sendJson, sendText, } from "./web-dashboard-http.js";
|
|
20
|
-
import { renderDashboardApp, renderLoginPage } from "./web-dashboard-pages.js";
|
|
21
|
+
import { renderDashboardApp, renderFirstRunSetupPage, renderLoginPage } from "./web-dashboard-pages.js";
|
|
21
22
|
import { handleDashboardRuntimeRoute } from "./web-dashboard-runtime-routes.js";
|
|
22
23
|
import { handleDashboardSessionRoute } from "./web-dashboard-session-routes.js";
|
|
23
24
|
const DEFAULT_HOME = path.join(os.homedir(), ".nordrelay");
|
|
@@ -28,6 +29,11 @@ const settings = new SettingsService(resolveDashboardEnvPath(options.home));
|
|
|
28
29
|
const users = new UserStore(options.home);
|
|
29
30
|
const auditLog = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
30
31
|
const loginAttempts = new Map();
|
|
32
|
+
const firstRunSetupToken = users.hasAdminUser() ? undefined : randomBytes(18).toString("base64url");
|
|
33
|
+
const firstRunSetupRequiresToken = !isLoopbackHost(options.host);
|
|
34
|
+
if (firstRunSetupToken) {
|
|
35
|
+
console.log(`NordRelay first-run setup token: ${firstRunSetupToken}`);
|
|
36
|
+
}
|
|
31
37
|
class AccessDeniedError extends Error {
|
|
32
38
|
}
|
|
33
39
|
const server = createServer((req, res) => {
|
|
@@ -45,6 +51,10 @@ async function handleRequest(req, res) {
|
|
|
45
51
|
await handleLogin(req, res);
|
|
46
52
|
return;
|
|
47
53
|
}
|
|
54
|
+
if (url.pathname === "/api/setup/admin" && req.method === "POST") {
|
|
55
|
+
await handleFirstRunSetup(req, res);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
48
58
|
if (url.pathname === "/api/dashboard/logout" && req.method === "POST") {
|
|
49
59
|
handleLogout(req, res);
|
|
50
60
|
return;
|
|
@@ -60,6 +70,10 @@ async function handleRequest(req, res) {
|
|
|
60
70
|
}
|
|
61
71
|
if (!authenticated) {
|
|
62
72
|
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
73
|
+
if (!users.hasAdminUser()) {
|
|
74
|
+
sendText(res, 200, renderFirstRunSetupPage({ tokenRequired: firstRunSetupRequiresToken || !isLoopbackRequest(req) }), "text/html; charset=utf-8");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
63
77
|
sendText(res, 200, renderLoginPage({ adminConfigured: users.hasAdminUser() }), "text/html; charset=utf-8");
|
|
64
78
|
return;
|
|
65
79
|
}
|
|
@@ -104,6 +118,7 @@ async function handleApi(req, res, url, authUser) {
|
|
|
104
118
|
status: "denied",
|
|
105
119
|
channelId: "web",
|
|
106
120
|
contextKey: "web",
|
|
121
|
+
actor: webActivityActor(authUser),
|
|
107
122
|
actorId: authUser.user.id,
|
|
108
123
|
actorRole: authUser.groups.map((group) => group.name).join(", "),
|
|
109
124
|
description: `Denied unknown endpoint ${req.method ?? "GET"} ${url.pathname}`,
|
|
@@ -117,6 +132,7 @@ async function handleApi(req, res, url, authUser) {
|
|
|
117
132
|
status: "denied",
|
|
118
133
|
channelId: "web",
|
|
119
134
|
contextKey: "web",
|
|
135
|
+
actor: webActivityActor(authUser),
|
|
120
136
|
actorId: authUser.user.id,
|
|
121
137
|
actorRole: authUser.groups.map((group) => group.name).join(", "),
|
|
122
138
|
description: `${permission} required for ${req.method ?? "GET"} ${url.pathname}`,
|
|
@@ -133,6 +149,8 @@ async function handleApi(req, res, url, authUser) {
|
|
|
133
149
|
assertAgentUpdateJobScope,
|
|
134
150
|
assertCurrentSessionScope,
|
|
135
151
|
scopedTasks,
|
|
152
|
+
scopedActiveSessions,
|
|
153
|
+
activityActor: webActivityActor(authUser),
|
|
136
154
|
})) {
|
|
137
155
|
return;
|
|
138
156
|
}
|
|
@@ -182,6 +200,7 @@ async function handleApi(req, res, url, authUser) {
|
|
|
182
200
|
assertSessionDetailScope,
|
|
183
201
|
scopedSessionPage,
|
|
184
202
|
filterActivityByScope,
|
|
203
|
+
activityActor: webActivityActor(authUser),
|
|
185
204
|
})) {
|
|
186
205
|
return;
|
|
187
206
|
}
|
|
@@ -189,6 +208,7 @@ async function handleApi(req, res, url, authUser) {
|
|
|
189
208
|
runtime,
|
|
190
209
|
authUser,
|
|
191
210
|
assertCurrentSessionScope,
|
|
211
|
+
activityActor: webActivityActor(authUser),
|
|
192
212
|
})) {
|
|
193
213
|
return;
|
|
194
214
|
}
|
|
@@ -239,6 +259,58 @@ async function handleEvents(req, res) {
|
|
|
239
259
|
unsubscribe();
|
|
240
260
|
});
|
|
241
261
|
}
|
|
262
|
+
async function handleFirstRunSetup(req, res) {
|
|
263
|
+
if (users.hasAdminUser()) {
|
|
264
|
+
sendJson(res, 409, { error: "Admin user already exists." });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const body = await readJsonBody(req);
|
|
268
|
+
const email = optionalStringField(body, "email") ?? "";
|
|
269
|
+
const displayName = optionalStringField(body, "displayName") ?? email;
|
|
270
|
+
const password = optionalStringField(body, "password") ?? "";
|
|
271
|
+
const setupToken = optionalStringField(body, "setupToken") ?? "";
|
|
272
|
+
if ((firstRunSetupRequiresToken || !isLoopbackRequest(req)) && setupToken !== firstRunSetupToken) {
|
|
273
|
+
audit({
|
|
274
|
+
action: "auth_login_failed",
|
|
275
|
+
status: "denied",
|
|
276
|
+
channelId: "web",
|
|
277
|
+
contextKey: "web",
|
|
278
|
+
description: `Rejected remote first-run setup for ${email || "unknown"}`,
|
|
279
|
+
});
|
|
280
|
+
sendJson(res, 403, { error: "Setup token required." });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (setupToken && setupToken !== firstRunSetupToken) {
|
|
284
|
+
sendJson(res, 403, { error: "Invalid setup token." });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!email || !password || password.length < 12) {
|
|
288
|
+
sendJson(res, 400, { error: "Email and a password with at least 12 characters are required." });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const authUser = users.createAdmin({ email, displayName, password });
|
|
292
|
+
const session = users.createWebSession(authUser.user.id);
|
|
293
|
+
audit({
|
|
294
|
+
action: "user_created",
|
|
295
|
+
status: "ok",
|
|
296
|
+
channelId: "web",
|
|
297
|
+
contextKey: "web",
|
|
298
|
+
actor: webActivityActor(authUser),
|
|
299
|
+
actorId: authUser.user.id,
|
|
300
|
+
actorRole: authUser.groups.map((group) => group.name).join(", "),
|
|
301
|
+
description: `First admin created: ${authUser.user.email}`,
|
|
302
|
+
});
|
|
303
|
+
runtime.recordActivity({
|
|
304
|
+
source: "web",
|
|
305
|
+
status: "info",
|
|
306
|
+
type: "first_run_admin_created",
|
|
307
|
+
threadId: null,
|
|
308
|
+
actor: webActivityActor(authUser),
|
|
309
|
+
detail: authUser.user.email,
|
|
310
|
+
});
|
|
311
|
+
setSessionCookie(res, session.token);
|
|
312
|
+
sendJson(res, 201, currentUserDto(authUser));
|
|
313
|
+
}
|
|
242
314
|
async function handleLogin(req, res) {
|
|
243
315
|
const body = await readJsonBody(req);
|
|
244
316
|
const email = optionalStringField(body, "email");
|
|
@@ -280,13 +352,32 @@ async function handleLogin(req, res) {
|
|
|
280
352
|
status: "ok",
|
|
281
353
|
channelId: "web",
|
|
282
354
|
contextKey: "web",
|
|
355
|
+
actor: webActivityActor(authUser),
|
|
283
356
|
actorId: authUser.user.id,
|
|
284
357
|
actorRole: authUser.groups.map((group) => group.name).join(", "),
|
|
285
358
|
description: `Login ${authUser.user.email}`,
|
|
286
359
|
});
|
|
360
|
+
runtime.recordActivity({
|
|
361
|
+
source: "web",
|
|
362
|
+
status: "info",
|
|
363
|
+
type: "auth_login",
|
|
364
|
+
threadId: null,
|
|
365
|
+
actor: webActivityActor(authUser),
|
|
366
|
+
detail: authUser.user.email,
|
|
367
|
+
});
|
|
287
368
|
setSessionCookie(res, session.token);
|
|
288
369
|
sendJson(res, 200, currentUserDto(authUser));
|
|
289
370
|
}
|
|
371
|
+
function isLoopbackRequest(req) {
|
|
372
|
+
const address = req.socket.remoteAddress ?? "";
|
|
373
|
+
return address === "127.0.0.1" ||
|
|
374
|
+
address === "::1" ||
|
|
375
|
+
address === "::ffff:127.0.0.1" ||
|
|
376
|
+
address === "localhost";
|
|
377
|
+
}
|
|
378
|
+
function isLoopbackHost(host) {
|
|
379
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
380
|
+
}
|
|
290
381
|
function handleLogout(req, res) {
|
|
291
382
|
const authUser = authenticateRequest(req);
|
|
292
383
|
users.destroyWebSession(parseCookies(req.headers.cookie ?? "").nr_session);
|
|
@@ -345,10 +436,27 @@ function auditUserAction(authUser, action, description) {
|
|
|
345
436
|
status: "ok",
|
|
346
437
|
channelId: "web",
|
|
347
438
|
contextKey: "web",
|
|
439
|
+
actor: webActivityActor(authUser),
|
|
348
440
|
actorId: authUser.user.id,
|
|
349
441
|
actorRole: authUser.groups.map((group) => group.name).join(", "),
|
|
350
442
|
description,
|
|
351
443
|
});
|
|
444
|
+
runtime.recordActivity({
|
|
445
|
+
source: "web",
|
|
446
|
+
status: "info",
|
|
447
|
+
type: action,
|
|
448
|
+
threadId: null,
|
|
449
|
+
actor: webActivityActor(authUser),
|
|
450
|
+
detail: description,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function webActivityActor(authUser) {
|
|
454
|
+
return {
|
|
455
|
+
channel: "web",
|
|
456
|
+
id: authUser.user.id,
|
|
457
|
+
label: authUser.user.displayName || authUser.user.email,
|
|
458
|
+
username: authUser.user.email,
|
|
459
|
+
};
|
|
352
460
|
}
|
|
353
461
|
function scopedControlOptions(authUser, options) {
|
|
354
462
|
return {
|
|
@@ -372,6 +480,12 @@ async function scopedTasks(authUser, tasks) {
|
|
|
372
480
|
recent: filterActivityByScope(authUser, tasks.recent),
|
|
373
481
|
};
|
|
374
482
|
}
|
|
483
|
+
function scopedActiveSessions(authUser, active) {
|
|
484
|
+
return {
|
|
485
|
+
...active,
|
|
486
|
+
sessions: active.sessions.filter((session) => canUseSession(authUser, session)),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
375
489
|
async function scopeRelayEvent(authUser, event, canUseCurrentSession = () => canUseCurrentSessionScope(authUser)) {
|
|
376
490
|
switch (event.type) {
|
|
377
491
|
case "snapshot":
|
|
@@ -504,6 +618,7 @@ function optionalEnv(key) {
|
|
|
504
618
|
}
|
|
505
619
|
function activeSettingsValues(current) {
|
|
506
620
|
return {
|
|
621
|
+
TELEGRAM_ENABLED: boolValue(current.telegramEnabled),
|
|
507
622
|
TELEGRAM_BOT_TOKEN: current.telegramBotToken,
|
|
508
623
|
TELEGRAM_TRANSPORT: current.telegramTransport,
|
|
509
624
|
TELEGRAM_WEBHOOK_URL: current.telegramWebhookUrl,
|
|
@@ -511,6 +626,20 @@ function activeSettingsValues(current) {
|
|
|
511
626
|
TELEGRAM_WEBHOOK_PORT: String(current.telegramWebhookPort),
|
|
512
627
|
TELEGRAM_WEBHOOK_PATH: current.telegramWebhookPath,
|
|
513
628
|
TELEGRAM_WEBHOOK_SECRET: current.telegramWebhookSecret,
|
|
629
|
+
DISCORD_ENABLED: boolValue(current.discordEnabled),
|
|
630
|
+
DISCORD_BOT_TOKEN: current.discordBotToken,
|
|
631
|
+
DISCORD_CLIENT_ID: current.discordClientId,
|
|
632
|
+
DISCORD_GUILD_IDS: current.discordGuildIds.join(","),
|
|
633
|
+
DISCORD_ALLOWED_GUILD_IDS: current.discordAllowedGuildIds.join(","),
|
|
634
|
+
DISCORD_ALLOWED_CHANNEL_IDS: current.discordAllowedChannelIds.join(","),
|
|
635
|
+
DISCORD_MESSAGE_CONTENT_ENABLED: boolValue(current.discordMessageContentEnabled),
|
|
636
|
+
DISCORD_COMMAND_MODE: current.discordCommandMode,
|
|
637
|
+
DISCORD_AUTO_REGISTER_COMMANDS: boolValue(current.discordAutoRegisterCommands),
|
|
638
|
+
DISCORD_CLI_MIRROR_MODE: current.discordMirrorMode === current.mirrorMode ? "" : current.discordMirrorMode,
|
|
639
|
+
DISCORD_CLI_MIRROR_MIN_UPDATE_MS: current.discordMirrorMinUpdateMs === current.mirrorMinUpdateMs ? "" : String(current.discordMirrorMinUpdateMs),
|
|
640
|
+
DISCORD_NOTIFY_MODE: current.discordNotifyMode === current.notifyMode ? "" : current.discordNotifyMode,
|
|
641
|
+
DISCORD_QUIET_HOURS: quietOverrideValue(current.discordQuietHours, current.quietHours),
|
|
642
|
+
DISCORD_AUTO_SEND_ARTIFACTS: current.discordAutoSendArtifacts === current.autoSendArtifacts ? "" : boolValue(current.discordAutoSendArtifacts),
|
|
514
643
|
NORDRELAY_CODEX_ENABLED: boolValue(current.codexEnabled),
|
|
515
644
|
NORDRELAY_PI_ENABLED: boolValue(current.piEnabled),
|
|
516
645
|
NORDRELAY_HERMES_ENABLED: boolValue(current.hermesEnabled),
|
|
@@ -565,10 +694,15 @@ function activeSettingsValues(current) {
|
|
|
565
694
|
ENABLE_TELEGRAM_REACTIONS: boolValue(current.enableTelegramReactions),
|
|
566
695
|
TELEGRAM_RATE_LIMIT_MIN_INTERVAL_MS: String(current.telegramRateLimitMinIntervalMs),
|
|
567
696
|
TELEGRAM_EDIT_MIN_INTERVAL_MS: String(current.telegramEditMinIntervalMs),
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
697
|
+
NORDRELAY_CLI_MIRROR_MODE: current.mirrorMode,
|
|
698
|
+
NORDRELAY_CLI_MIRROR_MIN_UPDATE_MS: String(current.mirrorMinUpdateMs),
|
|
699
|
+
NORDRELAY_NOTIFY_MODE: current.notifyMode,
|
|
700
|
+
NORDRELAY_QUIET_HOURS: quietValue(current.quietHours),
|
|
701
|
+
NORDRELAY_AUTO_SEND_ARTIFACTS: boolValue(current.autoSendArtifacts),
|
|
702
|
+
TELEGRAM_CLI_MIRROR_MODE: current.telegramMirrorMode === current.mirrorMode ? "" : current.telegramMirrorMode,
|
|
703
|
+
TELEGRAM_CLI_MIRROR_MIN_UPDATE_MS: current.telegramMirrorMinUpdateMs === current.mirrorMinUpdateMs ? "" : String(current.telegramMirrorMinUpdateMs),
|
|
704
|
+
TELEGRAM_NOTIFY_MODE: current.telegramNotifyMode === current.notifyMode ? "" : current.telegramNotifyMode,
|
|
705
|
+
TELEGRAM_QUIET_HOURS: quietOverrideValue(current.telegramQuietHours, current.quietHours),
|
|
572
706
|
TELEGRAM_REDACT_PATTERNS: current.telegramRedactPatterns.join(","),
|
|
573
707
|
NORDRELAY_UPDATE_METHOD: process.env.NORDRELAY_UPDATE_METHOD || "auto",
|
|
574
708
|
MAX_FILE_SIZE: String(current.maxFileSize),
|
|
@@ -577,13 +711,16 @@ function activeSettingsValues(current) {
|
|
|
577
711
|
ARTIFACT_MAX_INBOX_DIRS: String(current.artifactMaxInboxDirs),
|
|
578
712
|
ARTIFACT_IGNORE_DIRS: current.artifactIgnoreDirs.join(","),
|
|
579
713
|
ARTIFACT_IGNORE_GLOBS: current.artifactIgnoreGlobs.join(","),
|
|
580
|
-
TELEGRAM_AUTO_SEND_ARTIFACTS: boolValue(current.telegramAutoSendArtifacts),
|
|
714
|
+
TELEGRAM_AUTO_SEND_ARTIFACTS: current.telegramAutoSendArtifacts === current.autoSendArtifacts ? "" : boolValue(current.telegramAutoSendArtifacts),
|
|
581
715
|
WORKSPACE_ALLOWED_ROOTS: current.workspaceAllowedRoots.join(","),
|
|
582
716
|
WORKSPACE_WARN_ROOTS: current.workspaceWarnRoots.join(","),
|
|
583
717
|
NORDRELAY_STATE_BACKEND: current.stateBackend,
|
|
584
718
|
NORDRELAY_AUDIT_MAX_EVENTS: String(current.auditMaxEvents),
|
|
585
719
|
NORDRELAY_SESSION_LOCK_TTL_MS: String(current.sessionLockTtlMs),
|
|
720
|
+
NORDRELAY_DASHBOARD_CACHE_TTL_MS: String(current.dashboardCacheTtlMs),
|
|
721
|
+
NORDRELAY_UNIFIED_JOB_MAX_ITEMS: String(current.unifiedJobMaxItems),
|
|
586
722
|
NORDRELAY_VERSION_CACHE_TTL_MS: process.env.NORDRELAY_VERSION_CACHE_TTL_MS,
|
|
723
|
+
NORDRELAY_CLI_VERSION_CACHE_TTL_MS: process.env.NORDRELAY_CLI_VERSION_CACHE_TTL_MS,
|
|
587
724
|
VOICE_PREFERRED_BACKEND: current.voicePreferredBackend,
|
|
588
725
|
VOICE_DEFAULT_LANGUAGE: current.voiceDefaultLanguage,
|
|
589
726
|
VOICE_TRANSCRIBE_ONLY: boolValue(current.voiceTranscribeOnly),
|
|
@@ -600,6 +737,12 @@ function activeSettingsValues(current) {
|
|
|
600
737
|
function boolValue(value) {
|
|
601
738
|
return value ? "true" : "false";
|
|
602
739
|
}
|
|
740
|
+
function quietValue(value) {
|
|
741
|
+
return value ? `${value.startHour}-${value.endHour}` : "";
|
|
742
|
+
}
|
|
743
|
+
function quietOverrideValue(channelValue, defaultValue) {
|
|
744
|
+
return quietValue(channelValue) === quietValue(defaultValue) ? "" : quietValue(channelValue);
|
|
745
|
+
}
|
|
603
746
|
function requireArg(argv, index, flag) {
|
|
604
747
|
const value = argv[index];
|
|
605
748
|
if (!value || value.startsWith("--")) {
|
package/dist/web-state.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { activityActorLabel, activityCategoryForType, } from "./activity-events.js";
|
|
2
3
|
import { createDocumentStore } from "./state-backend.js";
|
|
3
4
|
const DEFAULT_CHAT_LIMIT = 300;
|
|
4
5
|
const DEFAULT_ACTIVITY_LIMIT = 1000;
|
|
@@ -76,6 +77,7 @@ export class WebActivityStore {
|
|
|
76
77
|
id: randomId(),
|
|
77
78
|
timestamp: input.timestamp ?? new Date().toISOString(),
|
|
78
79
|
...input,
|
|
80
|
+
category: input.category ?? activityCategoryForType(input.type),
|
|
79
81
|
};
|
|
80
82
|
payload.events.push(event);
|
|
81
83
|
if (payload.events.length > this.maxEvents) {
|
|
@@ -86,9 +88,17 @@ export class WebActivityStore {
|
|
|
86
88
|
}
|
|
87
89
|
list(options = {}) {
|
|
88
90
|
const limit = Math.max(1, Math.min(500, options.limit ?? 100));
|
|
91
|
+
const since = normalizeSince(options.since);
|
|
89
92
|
return this.readPayload().events
|
|
90
93
|
.filter((event) => !options.source || options.source === "all" || event.source === options.source)
|
|
91
94
|
.filter((event) => !options.status || options.status === "all" || event.status === options.status)
|
|
95
|
+
.filter((event) => !options.category || options.category === "all" || (event.category ?? activityCategoryForType(event.type)) === options.category)
|
|
96
|
+
.filter((event) => !options.agentId || options.agentId === "all" || event.agentId === options.agentId)
|
|
97
|
+
.filter((event) => !options.threadId || event.threadId === options.threadId)
|
|
98
|
+
.filter((event) => !options.workspace || event.workspace === options.workspace)
|
|
99
|
+
.filter((event) => !options.type || event.type.toLowerCase().includes(options.type.toLowerCase()))
|
|
100
|
+
.filter((event) => !options.actor || activityActorMatches(event.actor, options.actor))
|
|
101
|
+
.filter((event) => !since || Date.parse(event.timestamp) >= since)
|
|
92
102
|
.slice(-limit)
|
|
93
103
|
.reverse();
|
|
94
104
|
}
|
|
@@ -113,7 +123,7 @@ function isWebChatMessage(value) {
|
|
|
113
123
|
typeof candidate.text === "string" &&
|
|
114
124
|
typeof candidate.timestamp === "string" &&
|
|
115
125
|
["user", "agent", "system", "tool"].includes(candidate.role) &&
|
|
116
|
-
["web", "cli"].includes(candidate.source);
|
|
126
|
+
["web", "telegram", "discord", "cli"].includes(candidate.source);
|
|
117
127
|
}
|
|
118
128
|
function isWebActivityEvent(value) {
|
|
119
129
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -123,9 +133,30 @@ function isWebActivityEvent(value) {
|
|
|
123
133
|
return typeof candidate.id === "string" &&
|
|
124
134
|
typeof candidate.timestamp === "string" &&
|
|
125
135
|
typeof candidate.type === "string" &&
|
|
126
|
-
["web", "cli"].includes(candidate.source) &&
|
|
136
|
+
["web", "telegram", "discord", "cli"].includes(candidate.source) &&
|
|
127
137
|
["queued", "running", "completed", "failed", "aborted", "info"].includes(candidate.status);
|
|
128
138
|
}
|
|
139
|
+
function normalizeSince(value) {
|
|
140
|
+
if (value === undefined || value === null || value === "") {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const time = typeof value === "number" ? value : Date.parse(value);
|
|
144
|
+
return Number.isFinite(time) ? time : null;
|
|
145
|
+
}
|
|
146
|
+
function activityActorMatches(actor, query) {
|
|
147
|
+
const needle = query.trim().toLowerCase();
|
|
148
|
+
if (!needle) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return [
|
|
152
|
+
activityActorLabel(actor),
|
|
153
|
+
actor?.id,
|
|
154
|
+
actor?.username,
|
|
155
|
+
actor?.channelUserId,
|
|
156
|
+
actor?.channel,
|
|
157
|
+
].some((value) => String(value ?? "").toLowerCase().includes(needle));
|
|
158
|
+
}
|
|
129
159
|
function randomId() {
|
|
130
160
|
return randomUUID().replace(/-/g, "").slice(0, 12);
|
|
131
161
|
}
|
|
162
|
+
export { activityActorLabel, activityCategoryForType, auditCategoryForAction, } from "./activity-events.js";
|
|
@@ -79,6 +79,41 @@
|
|
|
79
79
|
width: 100%;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
.access-tab {
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
.access-tab.active {
|
|
86
|
+
display: block;
|
|
87
|
+
}
|
|
88
|
+
.access-tab-heading {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: space-between;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
margin: 0 0 10px;
|
|
94
|
+
}
|
|
95
|
+
.access-tab-heading h2 {
|
|
96
|
+
margin: 0;
|
|
97
|
+
}
|
|
98
|
+
.access-tab-heading input {
|
|
99
|
+
max-width: 320px;
|
|
100
|
+
}
|
|
101
|
+
.access-id-row {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 8px;
|
|
105
|
+
flex-wrap: wrap;
|
|
106
|
+
}
|
|
107
|
+
@media (max-width: 560px) {
|
|
108
|
+
.access-tab-heading {
|
|
109
|
+
align-items: stretch;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
}
|
|
112
|
+
.access-tab-heading input {
|
|
113
|
+
max-width: none;
|
|
114
|
+
width: 100%;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
82
117
|
.drop-active {
|
|
83
118
|
outline: 2px dashed var(--accent);
|
|
84
119
|
outline-offset: -8px;
|
|
@@ -132,6 +167,16 @@
|
|
|
132
167
|
text-overflow: ellipsis;
|
|
133
168
|
white-space: nowrap;
|
|
134
169
|
}
|
|
170
|
+
#activeSessions {
|
|
171
|
+
display: grid;
|
|
172
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
173
|
+
gap: 8px;
|
|
174
|
+
}
|
|
175
|
+
@media (min-width: 1320px) {
|
|
176
|
+
#activeSessions {
|
|
177
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
135
180
|
.overview-adapter-grid {
|
|
136
181
|
display: grid;
|
|
137
182
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -152,6 +197,31 @@
|
|
|
152
197
|
.setting.dirty {
|
|
153
198
|
border-color: var(--accent);
|
|
154
199
|
}
|
|
200
|
+
.setting-label {
|
|
201
|
+
display: flex !important;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 6px;
|
|
204
|
+
}
|
|
205
|
+
.setting-info {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
width: 18px;
|
|
210
|
+
height: 18px;
|
|
211
|
+
border: 1px solid var(--border);
|
|
212
|
+
border-radius: 50%;
|
|
213
|
+
color: var(--muted);
|
|
214
|
+
background: var(--surface);
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
line-height: 1;
|
|
217
|
+
cursor: help;
|
|
218
|
+
font-weight: 700;
|
|
219
|
+
flex: 0 0 auto;
|
|
220
|
+
}
|
|
221
|
+
.setting-info:focus {
|
|
222
|
+
outline: 2px solid var(--accent);
|
|
223
|
+
outline-offset: 2px;
|
|
224
|
+
}
|
|
155
225
|
.setting-actions {
|
|
156
226
|
display: flex;
|
|
157
227
|
gap: 8px;
|
|
@@ -169,7 +239,8 @@
|
|
|
169
239
|
padding: 10px;
|
|
170
240
|
margin: 0 0 12px;
|
|
171
241
|
}
|
|
172
|
-
.task-grid
|
|
242
|
+
.task-grid,
|
|
243
|
+
.metrics-grid {
|
|
173
244
|
display: grid;
|
|
174
245
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
175
246
|
gap: 10px;
|
|
@@ -914,6 +985,9 @@ dialog::backdrop {
|
|
|
914
985
|
.page {
|
|
915
986
|
padding: 14px;
|
|
916
987
|
}
|
|
988
|
+
#activeSessions {
|
|
989
|
+
grid-template-columns: 1fr;
|
|
990
|
+
}
|
|
917
991
|
.overview-adapter-grid {
|
|
918
992
|
grid-template-columns: 1fr;
|
|
919
993
|
}
|