@nordbyte/nordrelay 0.4.0 → 0.4.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/README.md +17 -5
- package/dist/access-control.js +3 -0
- package/dist/agent-feature-matrix.js +42 -0
- package/dist/agent-updates.js +294 -0
- package/dist/bot.js +169 -209
- package/dist/channel-actions.js +372 -0
- package/dist/operations.js +33 -8
- package/dist/relay-runtime.js +128 -24
- package/dist/session-format.js +72 -3
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-client.js +275 -0
- package/dist/web-dashboard-style.js +9 -0
- package/dist/web-dashboard.js +62 -244
- package/package.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +56 -9
package/dist/relay-runtime.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildFileInstructions, outboxPath, stageFile, } from "./attachments.js"
|
|
|
6
6
|
import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
7
7
|
import { getAgentDiagnostics, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
8
8
|
import { listAgentAdapterDescriptors } from "./agent-adapter.js";
|
|
9
|
+
import { AgentUpdateManager } from "./agent-updates.js";
|
|
9
10
|
import { createAgentSessionService, enabledAgents } from "./agent-factory.js";
|
|
10
11
|
import { AuditLogStore } from "./audit-log.js";
|
|
11
12
|
import { checkAuthStatus, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
|
|
@@ -13,10 +14,10 @@ import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout
|
|
|
13
14
|
import { friendlyErrorText } from "./error-messages.js";
|
|
14
15
|
import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
|
|
15
16
|
import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
16
|
-
import { getConnectorHealth, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate } from "./operations.js";
|
|
17
|
+
import { clearLogFile, getAgentUpdateLogPath, getConnectorHealth, getConnectorLogPath, getPackageVersion, getUpdateLogPath, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate } from "./operations.js";
|
|
17
18
|
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
18
19
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
19
|
-
import { renderSessionInfoPlain } from "./session-format.js";
|
|
20
|
+
import { renderSessionInfoPlain, renderSessionUsageRows } from "./session-format.js";
|
|
20
21
|
import { SessionLockStore } from "./session-locks.js";
|
|
21
22
|
import { SessionRegistry } from "./session-registry.js";
|
|
22
23
|
import { transcribeAudio } from "./voice.js";
|
|
@@ -34,6 +35,7 @@ export class RelayRuntime {
|
|
|
34
35
|
activityStore;
|
|
35
36
|
auditStore;
|
|
36
37
|
lockStore;
|
|
38
|
+
agentUpdates;
|
|
37
39
|
subscribers = new Set();
|
|
38
40
|
externalMonitor;
|
|
39
41
|
draining = false;
|
|
@@ -53,6 +55,9 @@ export class RelayRuntime {
|
|
|
53
55
|
this.activityStore = new WebActivityStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
54
56
|
this.auditStore = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
55
57
|
this.lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
58
|
+
this.agentUpdates = new AgentUpdateManager({
|
|
59
|
+
onUpdate: (job) => this.broadcast({ type: "agent_update", job }),
|
|
60
|
+
});
|
|
56
61
|
if (config.codexExternalBusyCheckMs > 0) {
|
|
57
62
|
this.externalMonitor = setInterval(() => {
|
|
58
63
|
void this.monitorExternalActivity().catch((error) => this.broadcastStatus(friendlyErrorText(error), "error"));
|
|
@@ -87,6 +92,15 @@ export class RelayRuntime {
|
|
|
87
92
|
snapshot: await this.snapshot(),
|
|
88
93
|
};
|
|
89
94
|
}
|
|
95
|
+
async bootstrapStatus() {
|
|
96
|
+
return {
|
|
97
|
+
health: {
|
|
98
|
+
version: await getPackageVersion(),
|
|
99
|
+
state: await readConnectorState(),
|
|
100
|
+
},
|
|
101
|
+
snapshot: await this.snapshot(),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
90
104
|
async version() {
|
|
91
105
|
return {
|
|
92
106
|
health: await getConnectorHealth({ piCliPath: this.config.piCliPath, hermesCliPath: this.config.hermesCliPath, openClawCliPath: this.config.openClawCliPath, claudeCodeCliPath: this.config.claudeCodeCliPath }),
|
|
@@ -114,6 +128,45 @@ export class RelayRuntime {
|
|
|
114
128
|
});
|
|
115
129
|
return update;
|
|
116
130
|
}
|
|
131
|
+
agentUpdateJobs() {
|
|
132
|
+
return this.agentUpdates.list();
|
|
133
|
+
}
|
|
134
|
+
startAgentUpdate(agentId) {
|
|
135
|
+
const job = this.agentUpdates.start(agentId, {
|
|
136
|
+
piCliPath: this.config.piCliPath,
|
|
137
|
+
hermesCliPath: this.config.hermesCliPath,
|
|
138
|
+
openClawCliPath: this.config.openClawCliPath,
|
|
139
|
+
claudeCodeCliPath: this.config.claudeCodeCliPath,
|
|
140
|
+
});
|
|
141
|
+
this.broadcastStatus(`${job.agentLabel} update started. Log: ${job.logPath}`, "warn");
|
|
142
|
+
this.appendActivity({
|
|
143
|
+
source: "web",
|
|
144
|
+
status: "info",
|
|
145
|
+
type: "agent_update_started",
|
|
146
|
+
agentId,
|
|
147
|
+
threadId: null,
|
|
148
|
+
workspace: this.config.workspace,
|
|
149
|
+
detail: `${job.method}: ${job.summary}`,
|
|
150
|
+
});
|
|
151
|
+
this.appendAudit({
|
|
152
|
+
action: "command",
|
|
153
|
+
status: "ok",
|
|
154
|
+
contextKey: WEB_CONTEXT_KEY,
|
|
155
|
+
agentId,
|
|
156
|
+
description: `update ${agentId}`,
|
|
157
|
+
detail: job.summary,
|
|
158
|
+
});
|
|
159
|
+
return job;
|
|
160
|
+
}
|
|
161
|
+
agentUpdateLog(id) {
|
|
162
|
+
return this.agentUpdates.readLog(id);
|
|
163
|
+
}
|
|
164
|
+
sendAgentUpdateInput(id, input) {
|
|
165
|
+
return this.agentUpdates.sendInput(id, input);
|
|
166
|
+
}
|
|
167
|
+
cancelAgentUpdate(id) {
|
|
168
|
+
return this.agentUpdates.cancel(id);
|
|
169
|
+
}
|
|
117
170
|
async diagnostics() {
|
|
118
171
|
return {
|
|
119
172
|
health: await getConnectorHealth({ piCliPath: this.config.piCliPath, hermesCliPath: this.config.hermesCliPath, openClawCliPath: this.config.openClawCliPath, claudeCodeCliPath: this.config.claudeCodeCliPath }),
|
|
@@ -380,9 +433,11 @@ export class RelayRuntime {
|
|
|
380
433
|
async sessionDetail(threadId) {
|
|
381
434
|
const session = await this.getSession(true);
|
|
382
435
|
const record = session.getSessionRecord(threadId);
|
|
436
|
+
const active = this.publicInfo(session);
|
|
383
437
|
return {
|
|
384
438
|
record,
|
|
385
|
-
active
|
|
439
|
+
active,
|
|
440
|
+
usageRows: active.threadId === threadId ? renderSessionUsageRows(active) : [],
|
|
386
441
|
messages: this.chatStore.list(threadId, 100),
|
|
387
442
|
activity: this.activity({ limit: 100 }).filter((event) => event.threadId === threadId),
|
|
388
443
|
};
|
|
@@ -395,7 +450,7 @@ export class RelayRuntime {
|
|
|
395
450
|
return { removed, messages };
|
|
396
451
|
}
|
|
397
452
|
activity(options = {}) {
|
|
398
|
-
return this.activityStore.list(options);
|
|
453
|
+
return this.activityStore.list(options).map((event) => this.enrichActivityEvent(event));
|
|
399
454
|
}
|
|
400
455
|
async retry() {
|
|
401
456
|
const cached = this.promptStore.getLastPrompt(WEB_CONTEXT_KEY);
|
|
@@ -442,25 +497,40 @@ export class RelayRuntime {
|
|
|
442
497
|
});
|
|
443
498
|
return result;
|
|
444
499
|
}
|
|
445
|
-
async listSessions(limit = 80, query = "") {
|
|
446
|
-
|
|
500
|
+
async listSessions(limit = 80, query = "", agentId) {
|
|
501
|
+
const { session, dispose } = await this.getControlSession(agentId);
|
|
502
|
+
try {
|
|
503
|
+
return this.filteredSessions(session, query, Math.max(1, limit * 3)).slice(0, limit);
|
|
504
|
+
}
|
|
505
|
+
finally {
|
|
506
|
+
if (dispose) {
|
|
507
|
+
session.dispose();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
447
510
|
}
|
|
448
|
-
async listSessionsPage(page = 1, pageSize = MAX_WEB_SESSION_PAGE_SIZE, query = "") {
|
|
449
|
-
const session = await this.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
511
|
+
async listSessionsPage(page = 1, pageSize = MAX_WEB_SESSION_PAGE_SIZE, query = "", agentId) {
|
|
512
|
+
const { session, dispose } = await this.getControlSession(agentId);
|
|
513
|
+
try {
|
|
514
|
+
const effectivePage = Math.max(1, Math.floor(page));
|
|
515
|
+
const effectivePageSize = Math.min(MAX_WEB_SESSION_PAGE_SIZE, Math.max(1, Math.floor(pageSize)));
|
|
516
|
+
const offset = (effectivePage - 1) * effectivePageSize;
|
|
517
|
+
const requested = Math.min(5_000, Math.max(100, (offset + effectivePageSize + 1) * 3));
|
|
518
|
+
const records = this.filteredSessions(session, query, requested);
|
|
519
|
+
return {
|
|
520
|
+
sessions: records.slice(offset, offset + effectivePageSize),
|
|
521
|
+
pagination: {
|
|
522
|
+
page: effectivePage,
|
|
523
|
+
pageSize: effectivePageSize,
|
|
524
|
+
hasPrevious: effectivePage > 1,
|
|
525
|
+
hasNext: records.length > offset + effectivePageSize,
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
finally {
|
|
530
|
+
if (dispose) {
|
|
531
|
+
session.dispose();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
464
534
|
}
|
|
465
535
|
filteredSessions(session, query, limit) {
|
|
466
536
|
const normalized = query.trim().toLowerCase();
|
|
@@ -814,11 +884,25 @@ export class RelayRuntime {
|
|
|
814
884
|
}
|
|
815
885
|
async logs(target = "connector", lines = 100) {
|
|
816
886
|
if (target === "update") {
|
|
817
|
-
const { getUpdateLogPath } = await import("./operations.js");
|
|
818
887
|
return readFormattedLogTail(lines, getUpdateLogPath());
|
|
819
888
|
}
|
|
889
|
+
if (target === "agent-updates") {
|
|
890
|
+
return readFormattedLogTail(lines, getAgentUpdateLogPath());
|
|
891
|
+
}
|
|
820
892
|
return readFormattedLogTail(lines);
|
|
821
893
|
}
|
|
894
|
+
clearLogs(target = "connector") {
|
|
895
|
+
const result = clearLogFile(target === "update" ? getUpdateLogPath() : target === "agent-updates" ? getAgentUpdateLogPath() : getConnectorLogPath());
|
|
896
|
+
this.appendActivity({
|
|
897
|
+
source: "web",
|
|
898
|
+
status: "info",
|
|
899
|
+
type: "logs_cleared",
|
|
900
|
+
threadId: null,
|
|
901
|
+
workspace: this.config.workspace,
|
|
902
|
+
detail: `Cleared ${target} log.`,
|
|
903
|
+
});
|
|
904
|
+
return { ok: true, filePath: result.filePath, clearedAt: result.clearedAt.toISOString() };
|
|
905
|
+
}
|
|
822
906
|
restartConnector() {
|
|
823
907
|
spawnConnectorRestart();
|
|
824
908
|
this.broadcastStatus("Restart requested. The dashboard may disconnect briefly.", "warn");
|
|
@@ -836,6 +920,7 @@ export class RelayRuntime {
|
|
|
836
920
|
if (this.externalMonitor) {
|
|
837
921
|
clearInterval(this.externalMonitor);
|
|
838
922
|
}
|
|
923
|
+
this.agentUpdates.cancelAll();
|
|
839
924
|
this.registry.disposeAll();
|
|
840
925
|
this.subscribers.clear();
|
|
841
926
|
}
|
|
@@ -1254,10 +1339,29 @@ export class RelayRuntime {
|
|
|
1254
1339
|
this.broadcast({ type: "session_update", session: this.publicInfo(session) });
|
|
1255
1340
|
}
|
|
1256
1341
|
appendActivity(input) {
|
|
1257
|
-
const event = this.activityStore.append(input);
|
|
1342
|
+
const event = this.activityStore.append(this.enrichActivityInput(input));
|
|
1258
1343
|
this.broadcast({ type: "activity_update", events: this.activity({ limit: 50 }) });
|
|
1259
1344
|
return event;
|
|
1260
1345
|
}
|
|
1346
|
+
enrichActivityInput(input) {
|
|
1347
|
+
return this.enrichActivityFields(input);
|
|
1348
|
+
}
|
|
1349
|
+
enrichActivityEvent(event) {
|
|
1350
|
+
return this.enrichActivityFields(event);
|
|
1351
|
+
}
|
|
1352
|
+
enrichActivityFields(event) {
|
|
1353
|
+
const info = this.registry.get(WEB_CONTEXT_KEY)?.getInfo();
|
|
1354
|
+
if (!info) {
|
|
1355
|
+
return !event.threadId && !event.workspace ? { ...event, workspace: this.config.workspace } : event;
|
|
1356
|
+
}
|
|
1357
|
+
if (event.threadId && info.threadId && event.threadId === info.threadId) {
|
|
1358
|
+
return { ...event, workspace: event.workspace ?? info.workspace, agentId: event.agentId ?? info.agentId };
|
|
1359
|
+
}
|
|
1360
|
+
if (!event.threadId && !event.workspace) {
|
|
1361
|
+
return { ...event, workspace: this.config.workspace };
|
|
1362
|
+
}
|
|
1363
|
+
return event;
|
|
1364
|
+
}
|
|
1261
1365
|
appendAudit(input) {
|
|
1262
1366
|
return this.auditStore.append({ ...input, channelId: "web" });
|
|
1263
1367
|
}
|
package/dist/session-format.js
CHANGED
|
@@ -17,13 +17,27 @@ export function renderSessionInfoPlain(info) {
|
|
|
17
17
|
capabilities.fastMode
|
|
18
18
|
? `Reasoning/Fast: ${info.reasoningEffort ?? "(model default)"} / ${info.fastMode ? "on" : "off"}`
|
|
19
19
|
: `${agentReasoningLabel(agentId)}: ${info.reasoningEffort ?? "(model default)"}`,
|
|
20
|
-
...
|
|
21
|
-
...renderAgentUsagePlain(info, capabilities),
|
|
22
|
-
info.sessionTokens ? formatSessionTokensPlain(info.sessionTokens) : undefined,
|
|
20
|
+
...renderSessionUsageRowsPlain(info),
|
|
23
21
|
]
|
|
24
22
|
.filter((line) => Boolean(line))
|
|
25
23
|
.join("\n");
|
|
26
24
|
}
|
|
25
|
+
export function renderSessionUsageRowsPlain(info) {
|
|
26
|
+
const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
|
|
27
|
+
return [
|
|
28
|
+
...renderCodexUsagePlain(info, capabilities),
|
|
29
|
+
...renderAgentUsagePlain(info, capabilities),
|
|
30
|
+
info.sessionTokens ? formatSessionTokensPlain(info.sessionTokens) : undefined,
|
|
31
|
+
].filter((line) => Boolean(line));
|
|
32
|
+
}
|
|
33
|
+
export function renderSessionUsageRows(info) {
|
|
34
|
+
const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
|
|
35
|
+
return [
|
|
36
|
+
...renderCodexUsageRows(info, capabilities),
|
|
37
|
+
...renderAgentUsageRows(info, capabilities),
|
|
38
|
+
info.sessionTokens ? ["Session tokens", formatSessionTokensValue(info.sessionTokens)] : undefined,
|
|
39
|
+
].filter((row) => Boolean(row));
|
|
40
|
+
}
|
|
27
41
|
export function renderSessionInfoHTML(info) {
|
|
28
42
|
const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
|
|
29
43
|
const agentId = info.agentId ?? "codex";
|
|
@@ -117,6 +131,37 @@ function renderCodexUsageHTML(info, capabilities) {
|
|
|
117
131
|
}
|
|
118
132
|
return lines;
|
|
119
133
|
}
|
|
134
|
+
function renderCodexUsageRows(info, capabilities) {
|
|
135
|
+
const usage = info.codexUsage;
|
|
136
|
+
if (!usage) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const rows = [];
|
|
140
|
+
if (capabilities.usageStats && usage.contextUsedPercent !== null && usage.contextWindow !== null && usage.lastTokenUsage) {
|
|
141
|
+
rows.push([
|
|
142
|
+
"Context used",
|
|
143
|
+
`${formatPercent(usage.contextUsedPercent)} (${formatCompactTokenCount(usage.lastTokenUsage.totalTokens)} / ${formatCompactTokenCount(usage.contextWindow)})`,
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
if (capabilities.usageStats && usage.totalTokenUsage) {
|
|
147
|
+
rows.push([
|
|
148
|
+
"Tokens",
|
|
149
|
+
[
|
|
150
|
+
`in ${formatCompactTokenCount(usage.totalTokenUsage.inputTokens)}`,
|
|
151
|
+
`cached ${formatCompactTokenCount(usage.totalTokenUsage.cachedInputTokens)}`,
|
|
152
|
+
`out ${formatCompactTokenCount(usage.totalTokenUsage.outputTokens)}`,
|
|
153
|
+
`reasoning out ${formatCompactTokenCount(usage.totalTokenUsage.reasoningOutputTokens)}`,
|
|
154
|
+
].join(" · "),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
if (capabilities.subscriptionLimits) {
|
|
158
|
+
const limits = formatLimitsLeft(usage);
|
|
159
|
+
if (limits) {
|
|
160
|
+
rows.push(["Limits left", limits]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return rows;
|
|
164
|
+
}
|
|
120
165
|
function renderAgentUsagePlain(info, capabilities) {
|
|
121
166
|
if (!capabilities.usageStats) {
|
|
122
167
|
return [];
|
|
@@ -159,6 +204,30 @@ function renderAgentUsageHTML(info, capabilities) {
|
|
|
159
204
|
}
|
|
160
205
|
return lines;
|
|
161
206
|
}
|
|
207
|
+
function renderAgentUsageRows(info, capabilities) {
|
|
208
|
+
if (!capabilities.usageStats) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
const rows = [];
|
|
212
|
+
if (info.contextUsage?.percent !== undefined && info.contextUsage.percent !== null) {
|
|
213
|
+
const contextWindow = info.contextUsage.contextWindow !== null && info.contextUsage.contextWindow !== undefined
|
|
214
|
+
? ` (${formatCompactTokenCount(info.contextUsage.tokens ?? 0)} / ${formatCompactTokenCount(info.contextUsage.contextWindow)})`
|
|
215
|
+
: "";
|
|
216
|
+
rows.push(["Context used", `${formatPercent(info.contextUsage.percent)}${contextWindow}`]);
|
|
217
|
+
}
|
|
218
|
+
if (info.sessionUsage) {
|
|
219
|
+
rows.push([
|
|
220
|
+
"Tokens",
|
|
221
|
+
[
|
|
222
|
+
`in ${formatCompactTokenCount(info.sessionUsage.input)}`,
|
|
223
|
+
`cache read ${formatCompactTokenCount(info.sessionUsage.cacheRead)}`,
|
|
224
|
+
`cache write ${formatCompactTokenCount(info.sessionUsage.cacheWrite)}`,
|
|
225
|
+
`out ${formatCompactTokenCount(info.sessionUsage.output)}`,
|
|
226
|
+
].join(" · "),
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
return rows;
|
|
230
|
+
}
|
|
162
231
|
function formatLimitsLeft(usage) {
|
|
163
232
|
const parts = [];
|
|
164
233
|
if (usage.rateLimits?.primary) {
|