@nordbyte/nordrelay 0.5.2 → 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 +63 -11
- package/README.md +90 -19
- package/dist/access-control.js +1 -0
- package/dist/activity-events.js +44 -0
- package/dist/audit-log.js +40 -2
- package/dist/bot-rendering.js +10 -7
- package/dist/bot.js +458 -5
- 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/config-metadata.js +78 -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/relay-external-activity-monitor.js +47 -6
- package/dist/relay-runtime.js +986 -281
- package/dist/runtime-cache.js +57 -0
- package/dist/session-locks.js +10 -7
- package/dist/support-bundle.js +1 -0
- 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 +148 -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/scripts/nordrelay.mjs +210 -17
|
@@ -44,6 +44,58 @@ export function renderLoginPage(options) {
|
|
|
44
44
|
</body>
|
|
45
45
|
</html>`;
|
|
46
46
|
}
|
|
47
|
+
export function renderFirstRunSetupPage(options) {
|
|
48
|
+
return `<!doctype html>
|
|
49
|
+
<html lang="en">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charset="utf-8">
|
|
52
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
53
|
+
<title>NordRelay First Run</title>
|
|
54
|
+
<style>
|
|
55
|
+
body{margin:0;min-height:100vh;display:grid;place-items:center;background:#f4f5f2;color:#181c19;font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif}
|
|
56
|
+
form{width:min(460px,calc(100vw - 32px));background:white;border:1px solid #dfe3dc;border-radius:8px;padding:24px;box-shadow:0 20px 60px rgba(20,30,24,.08)}
|
|
57
|
+
h1{font-size:24px;margin:0 0 8px}
|
|
58
|
+
p{color:#5d665d;margin:0 0 18px;line-height:1.45}
|
|
59
|
+
label{display:block;font-size:13px;color:#4b544d;margin:14px 0 6px}
|
|
60
|
+
input{box-sizing:border-box;width:100%;height:40px;border:1px solid #cfd6ce;border-radius:6px;padding:0 10px;font:inherit}
|
|
61
|
+
button{margin-top:18px;width:100%;height:42px;border:0;border-radius:6px;background:#205c43;color:white;font-weight:650;cursor:pointer}
|
|
62
|
+
.error{color:#9b1c1c;min-height:22px;margin-top:12px}
|
|
63
|
+
small{display:block;color:#667267;margin-top:8px;line-height:1.4}
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<form id="setup">
|
|
68
|
+
<h1>NordRelay Setup</h1>
|
|
69
|
+
<p>Create the first admin account. After this, every dashboard page and API route requires login.</p>
|
|
70
|
+
<label>Email</label><input id="email" name="email" type="email" autocomplete="username" required>
|
|
71
|
+
<label>Name</label><input id="displayName" name="displayName" autocomplete="name" required>
|
|
72
|
+
<label>Password</label><input id="password" name="password" type="password" autocomplete="new-password" minlength="12" required>
|
|
73
|
+
<label>Setup token</label><input id="setupToken" name="setupToken" autocomplete="one-time-code" ${options.tokenRequired ? "required" : ""}>
|
|
74
|
+
<small>${options.tokenRequired ? "Use the token printed in the NordRelay console." : "Local setup does not require the token, but the console token also works."}</small>
|
|
75
|
+
<button>Create admin</button>
|
|
76
|
+
<div class="error" id="error"></div>
|
|
77
|
+
</form>
|
|
78
|
+
<script>
|
|
79
|
+
document.getElementById('setup').addEventListener('submit', async (event) => {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
const payload = {
|
|
82
|
+
email: document.getElementById('email')?.value || undefined,
|
|
83
|
+
displayName: document.getElementById('displayName')?.value || undefined,
|
|
84
|
+
password: document.getElementById('password')?.value || undefined,
|
|
85
|
+
setupToken: document.getElementById('setupToken')?.value || undefined,
|
|
86
|
+
};
|
|
87
|
+
const res = await fetch('/api/setup/admin', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify(payload) });
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const data = await res.json().catch(() => ({}));
|
|
90
|
+
document.getElementById('error').textContent = data.error || 'Setup failed';
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
location.href = '/';
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
</body>
|
|
97
|
+
</html>`;
|
|
98
|
+
}
|
|
47
99
|
export function renderDashboardApp() {
|
|
48
100
|
return `<!doctype html>
|
|
49
101
|
<html lang="en">
|
|
@@ -81,7 +133,7 @@ export function renderDashboardApp() {
|
|
|
81
133
|
<section class="page active" id="page-overview">
|
|
82
134
|
<div class="metrics" id="metrics"></div>
|
|
83
135
|
<div class="stack">
|
|
84
|
-
<div class="panel"><h2>
|
|
136
|
+
<div class="panel"><h2>Active Sessions</h2><div id="activeSessions" class="list"></div></div>
|
|
85
137
|
<div class="overview-adapter-grid">
|
|
86
138
|
<div class="panel"><h2>Agent Adapters</h2><div id="agentAdapters"></div></div>
|
|
87
139
|
<div class="panel"><h2>Chat Adapters</h2><div id="chatAdapters"></div></div>
|
|
@@ -129,6 +181,13 @@ export function renderDashboardApp() {
|
|
|
129
181
|
</div>
|
|
130
182
|
</section>
|
|
131
183
|
|
|
184
|
+
<section class="page" id="page-metrics">
|
|
185
|
+
<div class="panel">
|
|
186
|
+
<div class="row"><button id="reloadMetricsBtn">Reload metrics</button></div>
|
|
187
|
+
<div id="metricsPanel" class="list"></div>
|
|
188
|
+
</div>
|
|
189
|
+
</section>
|
|
190
|
+
|
|
132
191
|
<section class="page" id="page-sessions">
|
|
133
192
|
<div class="panel">
|
|
134
193
|
<div class="sessions-toolbar">
|
|
@@ -149,7 +208,7 @@ export function renderDashboardApp() {
|
|
|
149
208
|
|
|
150
209
|
<section class="page" id="page-activity">
|
|
151
210
|
<div class="panel">
|
|
152
|
-
<div class="row"><select id="activitySource"><option value="all">All sources</option><option value="web">Web</option><option value="cli">CLI</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="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>
|
|
211
|
+
<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>
|
|
153
212
|
<div id="activityList" class="list"></div>
|
|
154
213
|
</div>
|
|
155
214
|
</section>
|
|
@@ -171,17 +230,42 @@ export function renderDashboardApp() {
|
|
|
171
230
|
|
|
172
231
|
<section class="page" id="page-access">
|
|
173
232
|
<div class="panel">
|
|
174
|
-
<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="lockSessionBtn" class="secondary">Lock web session</button><button id="unlockSessionBtn" class="secondary">Unlock web session</button></div>
|
|
175
|
-
<div id="
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<div class="
|
|
184
|
-
|
|
233
|
+
<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>
|
|
234
|
+
<div id="accessTabs" class="tabs access-tabs">
|
|
235
|
+
<button type="button" data-access-tab="users" class="active">Users</button>
|
|
236
|
+
<button type="button" data-access-tab="groups">Groups</button>
|
|
237
|
+
<button type="button" data-access-tab="telegram">Telegram</button>
|
|
238
|
+
<button type="button" data-access-tab="discord">Discord</button>
|
|
239
|
+
<button type="button" data-access-tab="locks">Locks</button>
|
|
240
|
+
<button type="button" data-access-tab="audit">Audit</button>
|
|
241
|
+
</div>
|
|
242
|
+
<div class="access-tab active" data-access-tab-panel="users">
|
|
243
|
+
<div id="accessPanel" class="settings-grid"></div>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="access-tab" data-access-tab-panel="groups">
|
|
246
|
+
<h2>Groups</h2>
|
|
247
|
+
<div id="groupsList" class="list"></div>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="access-tab" data-access-tab-panel="telegram">
|
|
250
|
+
<h2>Telegram chats</h2>
|
|
251
|
+
<div id="telegramChatsList" class="list"></div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="access-tab" data-access-tab-panel="discord">
|
|
254
|
+
<div class="access-tab-heading">
|
|
255
|
+
<h2>Discord channels</h2>
|
|
256
|
+
<input id="discordChannelSearch" placeholder="Search Discord channels">
|
|
257
|
+
</div>
|
|
258
|
+
<div id="discordChannelsList" class="list"></div>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="access-tab" data-access-tab-panel="locks">
|
|
261
|
+
<h2>Locks</h2>
|
|
262
|
+
<div id="locksList" class="list"></div>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="access-tab" data-access-tab-panel="audit">
|
|
265
|
+
<h2>Audit</h2>
|
|
266
|
+
<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>
|
|
267
|
+
<div id="auditList" class="list"></div>
|
|
268
|
+
</div>
|
|
185
269
|
</div>
|
|
186
270
|
</section>
|
|
187
271
|
|
|
@@ -11,7 +11,7 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
13
|
if (req.method === "POST" && url.pathname === "/api/update") {
|
|
14
|
-
sendJson(res, 202, runtime.updateConnector());
|
|
14
|
+
sendJson(res, 202, runtime.updateConnector(options.activityActor));
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
if (req.method === "GET" && url.pathname === "/api/agent-updates") {
|
|
@@ -23,7 +23,7 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
23
23
|
const agentId = options.parseAgentIdRequired(stringField(body, "agentId"));
|
|
24
24
|
const operation = parseAgentUpdateOperation(optionalStringField(body, "operation"));
|
|
25
25
|
options.assertScopedAgent(authUser, agentId);
|
|
26
|
-
sendJson(res, 202, { job: runtime.startAgentUpdate(agentId, operation) });
|
|
26
|
+
sendJson(res, 202, { job: runtime.startAgentUpdate(agentId, operation, options.activityActor) });
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
const agentUpdateLogMatch = url.pathname.match(/^\/api\/agent-update\/([^/]+)\/log$/);
|
|
@@ -36,7 +36,7 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
36
36
|
if (req.method === "DELETE" && agentUpdateLogMatch?.[1]) {
|
|
37
37
|
const id = decodeURIComponent(agentUpdateLogMatch[1]);
|
|
38
38
|
options.assertAgentUpdateJobScope(authUser, id);
|
|
39
|
-
sendJson(res, 200, { deletedId: id, job: runtime.deleteAgentUpdateLog(id) });
|
|
39
|
+
sendJson(res, 200, { deletedId: id, job: runtime.deleteAgentUpdateLog(id, options.activityActor) });
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
const agentUpdateInputMatch = url.pathname.match(/^\/api\/agent-update\/([^/]+)\/input$/);
|
|
@@ -44,20 +44,53 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
44
44
|
const body = await readJsonBody(req);
|
|
45
45
|
const id = decodeURIComponent(agentUpdateInputMatch[1]);
|
|
46
46
|
options.assertAgentUpdateJobScope(authUser, id);
|
|
47
|
-
sendJson(res, 200, { job: runtime.sendAgentUpdateInput(id, stringField(body, "input")) });
|
|
47
|
+
sendJson(res, 200, { job: runtime.sendAgentUpdateInput(id, stringField(body, "input"), options.activityActor) });
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
const agentUpdateCancelMatch = url.pathname.match(/^\/api\/agent-update\/([^/]+)\/cancel$/);
|
|
51
51
|
if (req.method === "POST" && agentUpdateCancelMatch?.[1]) {
|
|
52
52
|
const id = decodeURIComponent(agentUpdateCancelMatch[1]);
|
|
53
53
|
options.assertAgentUpdateJobScope(authUser, id);
|
|
54
|
-
sendJson(res, 200, { job: runtime.cancelAgentUpdate(id) });
|
|
54
|
+
sendJson(res, 200, { job: runtime.cancelAgentUpdate(id, options.activityActor) });
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
if (req.method === "GET" && (url.pathname === "/api/tasks" || url.pathname === "/api/progress")) {
|
|
58
58
|
sendJson(res, 200, await options.scopedTasks(authUser, runtime.tasks()));
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
|
+
if (req.method === "GET" && url.pathname === "/api/metrics") {
|
|
62
|
+
sendJson(res, 200, await runtime.metrics());
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (req.method === "GET" && url.pathname === "/api/jobs") {
|
|
66
|
+
sendJson(res, 200, await runtime.jobs());
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const jobLogMatch = url.pathname.match(/^\/api\/jobs\/([^/]+)\/log$/);
|
|
70
|
+
if (req.method === "GET" && jobLogMatch?.[1]) {
|
|
71
|
+
sendJson(res, 200, await runtime.jobLog(decodeURIComponent(jobLogMatch[1])));
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const jobActionMatch = url.pathname.match(/^\/api\/jobs\/([^/]+)\/action$/);
|
|
75
|
+
if (req.method === "POST" && jobActionMatch?.[1]) {
|
|
76
|
+
const body = await readJsonBody(req);
|
|
77
|
+
const id = decodeURIComponent(jobActionMatch[1]);
|
|
78
|
+
const action = stringField(body, "action");
|
|
79
|
+
if (action !== "cancel" && action !== "retry") {
|
|
80
|
+
throw new Error("Unsupported job action.");
|
|
81
|
+
}
|
|
82
|
+
const permission = permissionForJobAction(id, action);
|
|
83
|
+
if (!users.hasPermission(authUser, permission)) {
|
|
84
|
+
sendJson(res, 403, { error: `Access denied: ${permission} permission required.` });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
sendJson(res, 200, await runtime.jobAction(id, action, options.activityActor));
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (req.method === "GET" && url.pathname === "/api/active-sessions") {
|
|
91
|
+
sendJson(res, 200, options.scopedActiveSessions(authUser, await runtime.activeSessions()));
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
61
94
|
if (req.method === "GET" && url.pathname === "/api/adapters/health") {
|
|
62
95
|
sendJson(res, 200, { adapters: (await runtime.adapterHealth()).filter((adapter) => users.canUseAgent(authUser, adapter.id)) });
|
|
63
96
|
return true;
|
|
@@ -70,7 +103,7 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
70
103
|
if (req.method === "POST" && url.pathname === "/api/logs/clear") {
|
|
71
104
|
const body = await readJsonBody(req);
|
|
72
105
|
const target = parseLogTarget(optionalStringField(body, "target"));
|
|
73
|
-
sendJson(res, 200, runtime.clearLogs(target));
|
|
106
|
+
sendJson(res, 200, runtime.clearLogs(target, options.activityActor));
|
|
74
107
|
return true;
|
|
75
108
|
}
|
|
76
109
|
if (req.method === "GET" && url.pathname === "/api/diagnostics") {
|
|
@@ -80,13 +113,25 @@ export async function handleDashboardRuntimeRoute(req, res, url, options) {
|
|
|
80
113
|
}
|
|
81
114
|
if (req.method === "GET" && url.pathname === "/api/diagnostics/bundle") {
|
|
82
115
|
await options.assertCurrentSessionScope(authUser);
|
|
83
|
-
const bundle = await runtime.supportBundle();
|
|
116
|
+
const bundle = await runtime.supportBundle(options.activityActor);
|
|
84
117
|
sendFile(res, bundle.path, bundle.name);
|
|
85
118
|
return true;
|
|
86
119
|
}
|
|
87
120
|
if (req.method === "POST" && url.pathname === "/api/runtime/restart") {
|
|
88
|
-
sendJson(res, 202, runtime.restartConnector());
|
|
121
|
+
sendJson(res, 202, runtime.restartConnector(options.activityActor));
|
|
89
122
|
return true;
|
|
90
123
|
}
|
|
91
124
|
return false;
|
|
92
125
|
}
|
|
126
|
+
function permissionForJobAction(id, action) {
|
|
127
|
+
if (id === "web:current" && action === "cancel") {
|
|
128
|
+
return "prompt.abort";
|
|
129
|
+
}
|
|
130
|
+
if (id.startsWith("queue:")) {
|
|
131
|
+
return "queue.write";
|
|
132
|
+
}
|
|
133
|
+
if (id.startsWith("support-bundle:")) {
|
|
134
|
+
return "diagnostics.read";
|
|
135
|
+
}
|
|
136
|
+
return "updates.run";
|
|
137
|
+
}
|
|
@@ -10,12 +10,12 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
10
10
|
if (req.method === "POST" && url.pathname === "/api/locks") {
|
|
11
11
|
const body = await readJsonBody(req);
|
|
12
12
|
await options.assertCurrentSessionScope(authUser);
|
|
13
|
-
sendJson(res, 200, { lock: runtime.lockWebSession(optionalStringField(body, "ownerName")), locks: runtime.locks() });
|
|
13
|
+
sendJson(res, 200, { lock: runtime.lockWebSession(optionalStringField(body, "ownerName"), options.activityActor), locks: runtime.locks() });
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
16
|
if (req.method === "DELETE" && url.pathname === "/api/locks") {
|
|
17
17
|
await options.assertCurrentSessionScope(authUser);
|
|
18
|
-
sendJson(res, 200, runtime.unlockWebSession());
|
|
18
|
+
sendJson(res, 200, runtime.unlockWebSession(options.activityActor));
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
21
|
if (req.method === "GET" && url.pathname === "/api/auth/status") {
|
|
@@ -28,14 +28,14 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
28
28
|
const body = await readJsonBody(req);
|
|
29
29
|
const agentId = options.parseAgentId(optionalStringField(body, "agentId"));
|
|
30
30
|
options.assertScopedAgent(authUser, agentId);
|
|
31
|
-
sendJson(res, 200, await runtime.login(agentId));
|
|
31
|
+
sendJson(res, 200, await runtime.login(agentId, options.activityActor));
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
34
|
if (req.method === "POST" && url.pathname === "/api/auth/logout") {
|
|
35
35
|
const body = await readJsonBody(req);
|
|
36
36
|
const agentId = options.parseAgentId(optionalStringField(body, "agentId"));
|
|
37
37
|
options.assertScopedAgent(authUser, agentId);
|
|
38
|
-
sendJson(res, 200, await runtime.logout(agentId));
|
|
38
|
+
sendJson(res, 200, await runtime.logout(agentId, options.activityActor));
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
41
|
if (req.method === "GET" && url.pathname === "/api/snapshot") {
|
|
@@ -62,7 +62,7 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
62
62
|
throw new Error(`Invalid agent: ${agentId}`);
|
|
63
63
|
}
|
|
64
64
|
options.assertScopedAgent(authUser, agentId);
|
|
65
|
-
sendJson(res, 200, { session: await runtime.setAgent(agentId) });
|
|
65
|
+
sendJson(res, 200, { session: await runtime.setAgent(agentId, options.activityActor) });
|
|
66
66
|
return true;
|
|
67
67
|
}
|
|
68
68
|
if (req.method === "POST" && url.pathname === "/api/sessions/new") {
|
|
@@ -79,7 +79,7 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
79
79
|
reasoningEffort: optionalStringField(body, "reasoningEffort"),
|
|
80
80
|
launchProfileId: optionalStringField(body, "launchProfileId"),
|
|
81
81
|
fastMode: optionalBooleanField(body, "fastMode"),
|
|
82
|
-
}),
|
|
82
|
+
}, options.activityActor),
|
|
83
83
|
});
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
@@ -90,14 +90,14 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
90
90
|
if (detail.record && typeof detail.record === "object") {
|
|
91
91
|
options.assertSessionScope(authUser, detail.record);
|
|
92
92
|
}
|
|
93
|
-
const session = await runtime.switchSession(threadId);
|
|
93
|
+
const session = await runtime.switchSession(threadId, options.activityActor);
|
|
94
94
|
options.assertSessionScope(authUser, session);
|
|
95
95
|
sendJson(res, 200, { session });
|
|
96
96
|
return true;
|
|
97
97
|
}
|
|
98
98
|
if (req.method === "POST" && url.pathname === "/api/sessions/attach") {
|
|
99
99
|
const body = await readJsonBody(req);
|
|
100
|
-
const session = await runtime.attachSession(stringField(body, "threadId"));
|
|
100
|
+
const session = await runtime.attachSession(stringField(body, "threadId"), options.activityActor);
|
|
101
101
|
options.assertSessionScope(authUser, session);
|
|
102
102
|
sendJson(res, 200, { session });
|
|
103
103
|
return true;
|
|
@@ -117,31 +117,31 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
117
117
|
if (req.method === "POST" && url.pathname === "/api/session/model") {
|
|
118
118
|
const body = await readJsonBody(req);
|
|
119
119
|
await options.assertCurrentSessionScope(authUser);
|
|
120
|
-
sendJson(res, 200, { session: await runtime.setModel(stringField(body, "model")) });
|
|
120
|
+
sendJson(res, 200, { session: await runtime.setModel(stringField(body, "model"), options.activityActor) });
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
123
|
if (req.method === "POST" && url.pathname === "/api/session/reasoning") {
|
|
124
124
|
const body = await readJsonBody(req);
|
|
125
125
|
await options.assertCurrentSessionScope(authUser);
|
|
126
|
-
sendJson(res, 200, { session: await runtime.setReasoningEffort(stringField(body, "reasoning")) });
|
|
126
|
+
sendJson(res, 200, { session: await runtime.setReasoningEffort(stringField(body, "reasoning"), options.activityActor) });
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
129
|
if (req.method === "POST" && url.pathname === "/api/session/fast") {
|
|
130
130
|
const body = await readJsonBody(req);
|
|
131
131
|
await options.assertCurrentSessionScope(authUser);
|
|
132
|
-
sendJson(res, 200, { session: await runtime.setFastMode(Boolean(body?.enabled)) });
|
|
132
|
+
sendJson(res, 200, { session: await runtime.setFastMode(Boolean(body?.enabled), options.activityActor) });
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
135
|
if (req.method === "POST" && url.pathname === "/api/session/launch") {
|
|
136
136
|
const body = await readJsonBody(req);
|
|
137
137
|
await options.assertCurrentSessionScope(authUser);
|
|
138
|
-
sendJson(res, 200, { session: await runtime.setLaunchProfile(stringField(body, "profileId")) });
|
|
138
|
+
sendJson(res, 200, { session: await runtime.setLaunchProfile(stringField(body, "profileId"), options.activityActor) });
|
|
139
139
|
return true;
|
|
140
140
|
}
|
|
141
141
|
if (req.method === "POST" && url.pathname === "/api/prompt") {
|
|
142
142
|
const body = await readJsonBody(req);
|
|
143
143
|
await options.assertCurrentSessionScope(authUser);
|
|
144
|
-
sendJson(res, 202, await runtime.sendPrompt(stringField(body, "text")));
|
|
144
|
+
sendJson(res, 202, await runtime.sendPrompt(stringField(body, "text"), options.activityActor));
|
|
145
145
|
return true;
|
|
146
146
|
}
|
|
147
147
|
if (req.method === "POST" && url.pathname === "/api/prompt/upload") {
|
|
@@ -150,28 +150,28 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
150
150
|
sendJson(res, 202, await runtime.sendUploadPrompt({
|
|
151
151
|
text: optionalStringField(body, "text"),
|
|
152
152
|
files: parseUploadFiles(body.files),
|
|
153
|
-
}));
|
|
153
|
+
}, options.activityActor));
|
|
154
154
|
return true;
|
|
155
155
|
}
|
|
156
156
|
if (req.method === "POST" && (url.pathname === "/api/abort" || url.pathname === "/api/stop")) {
|
|
157
157
|
await options.assertCurrentSessionScope(authUser);
|
|
158
|
-
await runtime.abort();
|
|
158
|
+
await runtime.abort(options.activityActor);
|
|
159
159
|
sendJson(res, 200, { ok: true });
|
|
160
160
|
return true;
|
|
161
161
|
}
|
|
162
162
|
if (req.method === "POST" && url.pathname === "/api/handback") {
|
|
163
163
|
await options.assertCurrentSessionScope(authUser);
|
|
164
|
-
sendJson(res, 200, await runtime.handback());
|
|
164
|
+
sendJson(res, 200, await runtime.handback(options.activityActor));
|
|
165
165
|
return true;
|
|
166
166
|
}
|
|
167
167
|
if (req.method === "POST" && url.pathname === "/api/retry") {
|
|
168
168
|
await options.assertCurrentSessionScope(authUser);
|
|
169
|
-
sendJson(res, 202, await runtime.retry());
|
|
169
|
+
sendJson(res, 202, await runtime.retry(options.activityActor));
|
|
170
170
|
return true;
|
|
171
171
|
}
|
|
172
172
|
if (req.method === "POST" && url.pathname === "/api/sync") {
|
|
173
173
|
await options.assertCurrentSessionScope(authUser);
|
|
174
|
-
sendJson(res, 200, await runtime.sync());
|
|
174
|
+
sendJson(res, 200, await runtime.sync(options.activityActor));
|
|
175
175
|
return true;
|
|
176
176
|
}
|
|
177
177
|
if (req.method === "GET" && url.pathname === "/api/queue") {
|
|
@@ -182,7 +182,7 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
182
182
|
if (req.method === "POST" && url.pathname === "/api/queue") {
|
|
183
183
|
const body = await readJsonBody(req);
|
|
184
184
|
await options.assertCurrentSessionScope(authUser);
|
|
185
|
-
sendJson(res, 200, { queue: runtime.queueAction(stringField(body, "action"), optionalStringField(body, "id")), paused: runtime.queuePaused() });
|
|
185
|
+
sendJson(res, 200, { queue: runtime.queueAction(stringField(body, "action"), optionalStringField(body, "id"), options.activityActor), paused: runtime.queuePaused() });
|
|
186
186
|
return true;
|
|
187
187
|
}
|
|
188
188
|
if (req.method === "GET" && url.pathname === "/api/chat/history") {
|
|
@@ -192,7 +192,7 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
192
192
|
}
|
|
193
193
|
if (req.method === "DELETE" && url.pathname === "/api/chat/history") {
|
|
194
194
|
await options.assertCurrentSessionScope(authUser);
|
|
195
|
-
sendJson(res, 200, await runtime.clearChatHistory());
|
|
195
|
+
sendJson(res, 200, await runtime.clearChatHistory(options.activityActor));
|
|
196
196
|
return true;
|
|
197
197
|
}
|
|
198
198
|
if (req.method === "GET" && url.pathname === "/api/activity") {
|
|
@@ -201,6 +201,13 @@ export async function handleDashboardSessionRoute(req, res, url, options) {
|
|
|
201
201
|
limit: numberParam(url, "limit", 100),
|
|
202
202
|
source: (url.searchParams.get("source") || "all"),
|
|
203
203
|
status: (url.searchParams.get("status") || "all"),
|
|
204
|
+
category: (url.searchParams.get("category") || "all"),
|
|
205
|
+
actor: url.searchParams.get("actor") || undefined,
|
|
206
|
+
agentId: url.searchParams.get("agent") || "all",
|
|
207
|
+
threadId: url.searchParams.get("thread") || undefined,
|
|
208
|
+
workspace: url.searchParams.get("workspace") || undefined,
|
|
209
|
+
type: url.searchParams.get("type") || undefined,
|
|
210
|
+
since: url.searchParams.get("since") || undefined,
|
|
204
211
|
})),
|
|
205
212
|
});
|
|
206
213
|
return true;
|
package/dist/web-dashboard-ui.js
CHANGED
|
@@ -4,6 +4,7 @@ export const DASHBOARD_PAGES = [
|
|
|
4
4
|
{ id: "sessions", label: "Sessions", permission: "sessions.read" },
|
|
5
5
|
{ id: "queue", label: "Queue", permission: "queue.read" },
|
|
6
6
|
{ id: "tasks", label: "Tasks", permission: "inspect" },
|
|
7
|
+
{ id: "metrics", label: "Metrics", permission: "inspect" },
|
|
7
8
|
{ id: "activity", label: "Activity", permission: "sessions.read" },
|
|
8
9
|
{ id: "artifacts", label: "Artifacts", permission: "files.read" },
|
|
9
10
|
{ id: "adapters", label: "Adapters", permission: "inspect" },
|