@nordbyte/nordrelay 0.5.0 → 0.5.2
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 +2 -0
- package/README.md +23 -14
- package/dist/access-control.js +2 -0
- package/dist/agent-updates.js +61 -10
- package/dist/bot-ui.js +1 -0
- package/dist/bot.js +142 -1065
- package/dist/channel-actions.js +8 -8
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +2 -0
- package/dist/operations.js +233 -122
- package/dist/relay-artifact-service.js +126 -0
- package/dist/relay-external-activity-monitor.js +216 -0
- package/dist/relay-queue-service.js +66 -0
- package/dist/relay-runtime-types.js +1 -0
- package/dist/relay-runtime.js +119 -371
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +221 -0
- package/dist/telegram-agent-commands.js +212 -0
- package/dist/telegram-artifact-commands.js +139 -0
- package/dist/telegram-command-menu.js +1 -0
- package/dist/telegram-command-types.js +1 -0
- package/dist/telegram-diagnostics-command.js +102 -0
- package/dist/telegram-general-commands.js +52 -0
- package/dist/telegram-operational-commands.js +153 -0
- package/dist/telegram-preference-commands.js +198 -0
- package/dist/telegram-queue-commands.js +278 -0
- package/dist/telegram-support-command.js +53 -0
- package/dist/telegram-update-commands.js +6 -1
- package/dist/web-api-contract.js +79 -31
- package/dist/web-api-types.js +1 -0
- package/dist/web-dashboard-access-routes.js +163 -0
- package/dist/web-dashboard-artifact-routes.js +65 -0
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-http.js +143 -0
- package/dist/web-dashboard-pages.js +257 -0
- package/dist/web-dashboard-runtime-routes.js +92 -0
- package/dist/web-dashboard-session-routes.js +209 -0
- package/dist/web-dashboard.js +44 -882
- package/dist/webui-assets/dashboard.css +74 -4
- package/dist/webui-assets/dashboard.js +163 -24
- package/dist/zip-writer.js +83 -0
- package/package.json +10 -4
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +258 -5
package/dist/relay-runtime.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import {
|
|
3
|
+
import { ensureOutDir } from "./artifacts.js";
|
|
5
4
|
import { buildFileInstructions, outboxPath, stageFile, } from "./attachments.js";
|
|
6
5
|
import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
7
6
|
import { getAgentDiagnostics, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
@@ -17,16 +16,19 @@ import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
|
17
16
|
import { clearLogFile, getAgentUpdateLogPath, getConnectorHealth, getConnectorLogPath, getPackageVersion, getUpdateLogPath, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate } from "./operations.js";
|
|
18
17
|
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
19
18
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
19
|
+
import { RelayArtifactService } from "./relay-artifact-service.js";
|
|
20
|
+
import { RelayExternalActivityMonitor } from "./relay-external-activity-monitor.js";
|
|
21
|
+
import { RelayQueueService } from "./relay-queue-service.js";
|
|
20
22
|
import { renderSessionInfoPlain, renderSessionUsageRows } from "./session-format.js";
|
|
21
23
|
import { SessionLockStore } from "./session-locks.js";
|
|
22
24
|
import { SessionRegistry } from "./session-registry.js";
|
|
25
|
+
import { createSupportBundle } from "./support-bundle.js";
|
|
23
26
|
import { transcribeAudio } from "./voice.js";
|
|
24
27
|
import { WebActivityStore, WebChatStore, } from "./web-state.js";
|
|
25
28
|
import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "./workspace-policy.js";
|
|
26
29
|
const WEB_CONTEXT_KEY = "web:dashboard";
|
|
27
30
|
const MAX_WEB_SESSION_PAGE_SIZE = 50;
|
|
28
31
|
const MAX_CHAT_HISTORY = 250;
|
|
29
|
-
const MAX_TEXT_PREVIEW_BYTES = 256 * 1024;
|
|
30
32
|
export class RelayRuntime {
|
|
31
33
|
config;
|
|
32
34
|
registry;
|
|
@@ -36,15 +38,16 @@ export class RelayRuntime {
|
|
|
36
38
|
auditStore;
|
|
37
39
|
lockStore;
|
|
38
40
|
agentUpdates;
|
|
41
|
+
queueService;
|
|
42
|
+
artifactService;
|
|
43
|
+
externalActivityMonitor;
|
|
39
44
|
subscribers = new Set();
|
|
40
45
|
externalMonitor;
|
|
41
46
|
draining = false;
|
|
42
|
-
externalMonitorRunning = false;
|
|
43
47
|
currentTurnId = null;
|
|
44
48
|
accumulatedText = "";
|
|
45
49
|
currentTurnStartedAt = 0;
|
|
46
50
|
currentProgress = null;
|
|
47
|
-
externalMirror = null;
|
|
48
51
|
constructor(config) {
|
|
49
52
|
this.config = config;
|
|
50
53
|
this.registry = new SessionRegistry(config, {
|
|
@@ -56,12 +59,27 @@ export class RelayRuntime {
|
|
|
56
59
|
this.activityStore = new WebActivityStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
57
60
|
this.auditStore = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
58
61
|
this.lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
62
|
+
this.queueService = new RelayQueueService(this.promptStore, WEB_CONTEXT_KEY);
|
|
63
|
+
this.artifactService = new RelayArtifactService(config);
|
|
59
64
|
this.agentUpdates = new AgentUpdateManager({
|
|
60
65
|
onUpdate: (job) => this.broadcast({ type: "agent_update", job }),
|
|
61
66
|
});
|
|
67
|
+
this.externalActivityMonitor = new RelayExternalActivityMonitor({
|
|
68
|
+
config,
|
|
69
|
+
getSession: () => this.getSession(true),
|
|
70
|
+
publicInfo: (session) => this.publicInfo(session),
|
|
71
|
+
queueLength: () => this.queueService.length(),
|
|
72
|
+
chatStore: this.chatStore,
|
|
73
|
+
chatHistory: () => this.chatHistory(),
|
|
74
|
+
persistWorkspaceArtifactsForTurn: (workspace, turnId, startedAt) => this.artifactService.persistWorkspaceArtifactsForTurn(workspace, turnId, startedAt),
|
|
75
|
+
drainQueue: () => this.drainQueue(),
|
|
76
|
+
appendActivity: (input) => this.appendActivity(input),
|
|
77
|
+
broadcast: (event) => this.broadcast(event),
|
|
78
|
+
broadcastStatus: (message, level) => this.broadcastStatus(message, level),
|
|
79
|
+
});
|
|
62
80
|
if (config.codexExternalBusyCheckMs > 0) {
|
|
63
81
|
this.externalMonitor = setInterval(() => {
|
|
64
|
-
void this.
|
|
82
|
+
void this.externalActivityMonitor.monitorSafe();
|
|
65
83
|
}, config.codexExternalBusyCheckMs);
|
|
66
84
|
this.externalMonitor.unref?.();
|
|
67
85
|
}
|
|
@@ -87,10 +105,16 @@ export class RelayRuntime {
|
|
|
87
105
|
};
|
|
88
106
|
}
|
|
89
107
|
async status() {
|
|
108
|
+
const cliOptions = this.cliPathOptions();
|
|
109
|
+
const [health, versionChecks, snapshot] = await Promise.all([
|
|
110
|
+
getConnectorHealth(cliOptions),
|
|
111
|
+
getVersionChecks(cliOptions),
|
|
112
|
+
this.snapshot(),
|
|
113
|
+
]);
|
|
90
114
|
return {
|
|
91
|
-
health
|
|
92
|
-
versionChecks
|
|
93
|
-
snapshot
|
|
115
|
+
health,
|
|
116
|
+
versionChecks,
|
|
117
|
+
snapshot,
|
|
94
118
|
};
|
|
95
119
|
}
|
|
96
120
|
async bootstrapStatus() {
|
|
@@ -103,10 +127,16 @@ export class RelayRuntime {
|
|
|
103
127
|
};
|
|
104
128
|
}
|
|
105
129
|
async version() {
|
|
130
|
+
const cliOptions = this.cliPathOptions();
|
|
131
|
+
const [health, state, versionChecks] = await Promise.all([
|
|
132
|
+
getConnectorHealth(cliOptions),
|
|
133
|
+
readConnectorState(),
|
|
134
|
+
getVersionChecks(cliOptions),
|
|
135
|
+
]);
|
|
106
136
|
return {
|
|
107
|
-
health
|
|
108
|
-
state
|
|
109
|
-
versionChecks
|
|
137
|
+
health,
|
|
138
|
+
state,
|
|
139
|
+
versionChecks,
|
|
110
140
|
};
|
|
111
141
|
}
|
|
112
142
|
updateConnector() {
|
|
@@ -132,18 +162,18 @@ export class RelayRuntime {
|
|
|
132
162
|
agentUpdateJobs() {
|
|
133
163
|
return this.agentUpdates.list();
|
|
134
164
|
}
|
|
135
|
-
startAgentUpdate(agentId) {
|
|
165
|
+
startAgentUpdate(agentId, operation = "update") {
|
|
136
166
|
const job = this.agentUpdates.start(agentId, {
|
|
137
167
|
piCliPath: this.config.piCliPath,
|
|
138
168
|
hermesCliPath: this.config.hermesCliPath,
|
|
139
169
|
openClawCliPath: this.config.openClawCliPath,
|
|
140
170
|
claudeCodeCliPath: this.config.claudeCodeCliPath,
|
|
141
|
-
});
|
|
142
|
-
this.broadcastStatus(`${job.agentLabel}
|
|
171
|
+
}, operation);
|
|
172
|
+
this.broadcastStatus(`${job.agentLabel} ${operation} started. Log: ${job.logPath}`, "warn");
|
|
143
173
|
this.appendActivity({
|
|
144
174
|
source: "web",
|
|
145
175
|
status: "info",
|
|
146
|
-
type: "agent_update_started",
|
|
176
|
+
type: operation === "install" ? "agent_install_started" : "agent_update_started",
|
|
147
177
|
agentId,
|
|
148
178
|
threadId: null,
|
|
149
179
|
workspace: this.config.workspace,
|
|
@@ -154,7 +184,7 @@ export class RelayRuntime {
|
|
|
154
184
|
status: "ok",
|
|
155
185
|
contextKey: WEB_CONTEXT_KEY,
|
|
156
186
|
agentId,
|
|
157
|
-
description:
|
|
187
|
+
description: `${operation} ${agentId}`,
|
|
158
188
|
detail: job.summary,
|
|
159
189
|
});
|
|
160
190
|
return job;
|
|
@@ -181,22 +211,32 @@ export class RelayRuntime {
|
|
|
181
211
|
return this.agentUpdates.cancel(id);
|
|
182
212
|
}
|
|
183
213
|
async diagnostics() {
|
|
214
|
+
const cliOptions = this.cliPathOptions();
|
|
215
|
+
const [health, versionChecks, snapshot, session] = await Promise.all([
|
|
216
|
+
getConnectorHealth(cliOptions),
|
|
217
|
+
getVersionChecks(cliOptions),
|
|
218
|
+
this.snapshot(),
|
|
219
|
+
this.getSession(true),
|
|
220
|
+
]);
|
|
184
221
|
return {
|
|
185
|
-
health
|
|
186
|
-
versionChecks
|
|
187
|
-
snapshot
|
|
222
|
+
health,
|
|
223
|
+
versionChecks,
|
|
224
|
+
snapshot,
|
|
188
225
|
runtime: {
|
|
189
226
|
stateBackend: this.config.stateBackend,
|
|
190
227
|
sourceWorkspace: this.config.workspace,
|
|
191
|
-
queuePaused: this.
|
|
192
|
-
externalMirror: this.
|
|
193
|
-
agentDiagnostics: getAgentDiagnostics(
|
|
228
|
+
queuePaused: this.queueService.isPaused(),
|
|
229
|
+
externalMirror: this.externalActivityMonitor.snapshot(),
|
|
230
|
+
agentDiagnostics: getAgentDiagnostics(session, this.config),
|
|
194
231
|
},
|
|
195
232
|
};
|
|
196
233
|
}
|
|
197
234
|
async adapterHealth() {
|
|
198
|
-
const
|
|
199
|
-
const versions = await
|
|
235
|
+
const cliOptions = this.cliPathOptions();
|
|
236
|
+
const [health, versions] = await Promise.all([
|
|
237
|
+
getConnectorHealth(cliOptions),
|
|
238
|
+
getVersionChecks(cliOptions),
|
|
239
|
+
]);
|
|
200
240
|
return Promise.all(listAgentAdapterDescriptors().map(async (descriptor) => {
|
|
201
241
|
const enabled = enabledAgents(this.config).includes(descriptor.id);
|
|
202
242
|
const auth = descriptor.capabilities.auth && enabled
|
|
@@ -244,7 +284,7 @@ export class RelayRuntime {
|
|
|
244
284
|
tasks() {
|
|
245
285
|
return {
|
|
246
286
|
current: this.currentProgress ? { ...this.currentProgress, tools: [...this.currentProgress.tools] } : null,
|
|
247
|
-
external: this.
|
|
287
|
+
external: this.externalActivityMonitor.task(),
|
|
248
288
|
queue: this.queue(),
|
|
249
289
|
queuePaused: this.queuePaused(),
|
|
250
290
|
recent: this.activity({ limit: 20 }),
|
|
@@ -253,6 +293,32 @@ export class RelayRuntime {
|
|
|
253
293
|
audit(limit = 50) {
|
|
254
294
|
return this.auditStore.list(limit);
|
|
255
295
|
}
|
|
296
|
+
async supportBundle() {
|
|
297
|
+
const bundle = await createSupportBundle({
|
|
298
|
+
config: this.config,
|
|
299
|
+
diagnostics: await this.diagnostics(),
|
|
300
|
+
adapterHealth: await this.adapterHealth(),
|
|
301
|
+
auditEvents: this.auditStore.list(100),
|
|
302
|
+
agentUpdateJobs: this.agentUpdates.list(),
|
|
303
|
+
source: "web",
|
|
304
|
+
});
|
|
305
|
+
this.appendActivity({
|
|
306
|
+
source: "web",
|
|
307
|
+
status: "info",
|
|
308
|
+
type: "diagnostics_bundle_exported",
|
|
309
|
+
threadId: null,
|
|
310
|
+
workspace: this.config.workspace,
|
|
311
|
+
detail: bundle.path,
|
|
312
|
+
});
|
|
313
|
+
this.appendAudit({
|
|
314
|
+
action: "command",
|
|
315
|
+
status: "ok",
|
|
316
|
+
contextKey: WEB_CONTEXT_KEY,
|
|
317
|
+
description: "export diagnostics bundle",
|
|
318
|
+
detail: bundle.path,
|
|
319
|
+
});
|
|
320
|
+
return bundle;
|
|
321
|
+
}
|
|
256
322
|
locks() {
|
|
257
323
|
return this.lockStore.list();
|
|
258
324
|
}
|
|
@@ -463,7 +529,7 @@ export class RelayRuntime {
|
|
|
463
529
|
return this.activityStore.list(options).map((event) => this.enrichActivityEvent(event, currentInfo));
|
|
464
530
|
}
|
|
465
531
|
async retry() {
|
|
466
|
-
const cached = this.
|
|
532
|
+
const cached = this.queueService.getLastPrompt();
|
|
467
533
|
if (!cached) {
|
|
468
534
|
throw new Error("Nothing to retry. Send a message first.");
|
|
469
535
|
}
|
|
@@ -758,7 +824,7 @@ export class RelayRuntime {
|
|
|
758
824
|
const session = await this.getSession(false);
|
|
759
825
|
const external = getExternalSnapshotForSession(session, this.config, { maxEvents: 0 });
|
|
760
826
|
if (session.isProcessing() || external?.activity.active) {
|
|
761
|
-
const queued = this.
|
|
827
|
+
const queued = this.queueService.enqueue(envelope);
|
|
762
828
|
const info = this.publicInfo(session);
|
|
763
829
|
this.appendActivity({
|
|
764
830
|
source: "web",
|
|
@@ -770,7 +836,7 @@ export class RelayRuntime {
|
|
|
770
836
|
prompt: envelope.description,
|
|
771
837
|
detail: external?.activity.active
|
|
772
838
|
? `Queued because ${external.agentLabel} CLI is still processing another task.`
|
|
773
|
-
: `Queued at position ${this.
|
|
839
|
+
: `Queued at position ${this.queueService.length()}.`,
|
|
774
840
|
});
|
|
775
841
|
this.appendAudit({
|
|
776
842
|
action: "prompt_queued",
|
|
@@ -783,7 +849,7 @@ export class RelayRuntime {
|
|
|
783
849
|
description: envelope.description,
|
|
784
850
|
});
|
|
785
851
|
if (external?.activity.active) {
|
|
786
|
-
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.
|
|
852
|
+
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.queueService.length()} queued.`, "info");
|
|
787
853
|
}
|
|
788
854
|
this.broadcastQueue();
|
|
789
855
|
return { queued: true, queueId: queued.id };
|
|
@@ -794,30 +860,14 @@ export class RelayRuntime {
|
|
|
794
860
|
return { queued: false };
|
|
795
861
|
}
|
|
796
862
|
queue() {
|
|
797
|
-
return this.
|
|
863
|
+
return this.queueService.list();
|
|
798
864
|
}
|
|
799
865
|
queuePaused() {
|
|
800
|
-
return this.
|
|
866
|
+
return this.queueService.isPaused();
|
|
801
867
|
}
|
|
802
868
|
queueAction(action, id) {
|
|
803
|
-
|
|
804
|
-
this.promptStore.pause(WEB_CONTEXT_KEY);
|
|
805
|
-
if (action === "resume")
|
|
806
|
-
this.promptStore.resume(WEB_CONTEXT_KEY);
|
|
807
|
-
if (action === "clear")
|
|
808
|
-
this.promptStore.clear(WEB_CONTEXT_KEY);
|
|
809
|
-
if (id && action === "cancel")
|
|
810
|
-
this.promptStore.remove(WEB_CONTEXT_KEY, id);
|
|
811
|
-
if (id && action === "top")
|
|
812
|
-
this.promptStore.moveToTop(WEB_CONTEXT_KEY, id);
|
|
813
|
-
if (id && action === "up")
|
|
814
|
-
this.promptStore.moveUp(WEB_CONTEXT_KEY, id);
|
|
815
|
-
if (id && action === "down")
|
|
816
|
-
this.promptStore.moveDown(WEB_CONTEXT_KEY, id);
|
|
869
|
+
this.queueService.apply(action, id);
|
|
817
870
|
if (id && action === "run") {
|
|
818
|
-
const item = this.promptStore.remove(WEB_CONTEXT_KEY, id);
|
|
819
|
-
if (item)
|
|
820
|
-
this.promptStore.enqueueFront(WEB_CONTEXT_KEY, item);
|
|
821
871
|
void this.drainQueue().catch((error) => this.broadcastStatus(friendlyErrorText(error), "error"));
|
|
822
872
|
}
|
|
823
873
|
this.appendActivity({
|
|
@@ -839,58 +889,23 @@ export class RelayRuntime {
|
|
|
839
889
|
}
|
|
840
890
|
async artifacts() {
|
|
841
891
|
const session = await this.getSession(true);
|
|
842
|
-
return (
|
|
892
|
+
return this.artifactService.list(session.getInfo().workspace, 20);
|
|
843
893
|
}
|
|
844
894
|
async artifact(turnId) {
|
|
845
895
|
const session = await this.getSession(true);
|
|
846
|
-
return
|
|
896
|
+
return this.artifactService.get(session.getInfo().workspace, turnId);
|
|
847
897
|
}
|
|
848
898
|
async deleteArtifact(turnId) {
|
|
849
899
|
const session = await this.getSession(true);
|
|
850
|
-
return
|
|
900
|
+
return this.artifactService.delete(session.getInfo().workspace, turnId);
|
|
851
901
|
}
|
|
852
902
|
async createArtifactZip(turnId) {
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
return null;
|
|
856
|
-
}
|
|
857
|
-
const bundle = await createArtifactZipBundle(report.artifacts, report.outDir, {
|
|
858
|
-
maxFileSize: this.config.maxFileSize,
|
|
859
|
-
bundleName: `nordrelay-artifacts-${turnId}.zip`,
|
|
860
|
-
});
|
|
861
|
-
return bundle ? { path: bundle.localPath, name: bundle.name } : null;
|
|
903
|
+
const session = await this.getSession(true);
|
|
904
|
+
return this.artifactService.createZip(session.getInfo().workspace, turnId);
|
|
862
905
|
}
|
|
863
906
|
async artifactPreview(turnId, relativePath) {
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
if (!artifact) {
|
|
867
|
-
return null;
|
|
868
|
-
}
|
|
869
|
-
const extension = path.extname(artifact.name).toLowerCase();
|
|
870
|
-
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(extension)) {
|
|
871
|
-
return {
|
|
872
|
-
kind: "image",
|
|
873
|
-
name: artifact.name,
|
|
874
|
-
sizeBytes: artifact.sizeBytes,
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
if (!isPreviewableTextFile(extension, artifact.sizeBytes)) {
|
|
878
|
-
return {
|
|
879
|
-
kind: "unsupported",
|
|
880
|
-
name: artifact.name,
|
|
881
|
-
sizeBytes: artifact.sizeBytes,
|
|
882
|
-
detail: artifact.sizeBytes > MAX_TEXT_PREVIEW_BYTES ? "File is too large for inline preview." : "File type is not previewable.",
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
const buffer = await readFile(artifact.localPath);
|
|
886
|
-
const truncated = buffer.byteLength > MAX_TEXT_PREVIEW_BYTES;
|
|
887
|
-
return {
|
|
888
|
-
kind: "text",
|
|
889
|
-
name: artifact.name,
|
|
890
|
-
sizeBytes: artifact.sizeBytes,
|
|
891
|
-
truncated,
|
|
892
|
-
text: buffer.subarray(0, MAX_TEXT_PREVIEW_BYTES).toString("utf8"),
|
|
893
|
-
};
|
|
907
|
+
const session = await this.getSession(true);
|
|
908
|
+
return this.artifactService.preview(session.getInfo().workspace, turnId, relativePath);
|
|
894
909
|
}
|
|
895
910
|
async logs(target = "connector", lines = 100) {
|
|
896
911
|
if (target === "update") {
|
|
@@ -934,158 +949,6 @@ export class RelayRuntime {
|
|
|
934
949
|
this.registry.disposeAll();
|
|
935
950
|
this.subscribers.clear();
|
|
936
951
|
}
|
|
937
|
-
async monitorExternalActivity() {
|
|
938
|
-
const session = await this.getSession(true);
|
|
939
|
-
const info = this.publicInfo(session);
|
|
940
|
-
if (!info.capabilities.externalActivity || !info.threadId || session.isProcessing()) {
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
const snapshot = getExternalSnapshotForSession(session, this.config, {
|
|
944
|
-
afterLine: this.externalMirror?.threadId === info.threadId ? this.externalMirror.lastLine : Number.MAX_SAFE_INTEGER,
|
|
945
|
-
}) ?? getExternalSnapshotForSession(session, this.config, {
|
|
946
|
-
maxEvents: 0,
|
|
947
|
-
});
|
|
948
|
-
if (!snapshot) {
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
if (!this.externalMirror || this.externalMirror.threadId !== snapshot.threadId || this.externalMirror.rolloutPath !== snapshot.sourcePath) {
|
|
952
|
-
this.externalMirror = {
|
|
953
|
-
threadId: snapshot.threadId,
|
|
954
|
-
rolloutPath: snapshot.sourcePath,
|
|
955
|
-
lastLine: snapshot.lineCount,
|
|
956
|
-
turnId: snapshot.activity.turnId,
|
|
957
|
-
startedAt: snapshot.activity.startedAt?.toISOString() ?? null,
|
|
958
|
-
};
|
|
959
|
-
if (snapshot.activity.active) {
|
|
960
|
-
this.startExternalTurn(snapshot);
|
|
961
|
-
}
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
const mirror = this.externalMirror;
|
|
965
|
-
if (snapshot.activity.active) {
|
|
966
|
-
if (mirror.turnId !== snapshot.activity.turnId) {
|
|
967
|
-
mirror.turnId = snapshot.activity.turnId;
|
|
968
|
-
mirror.startedAt = snapshot.activity.startedAt?.toISOString() ?? null;
|
|
969
|
-
mirror.latestAgentLine = undefined;
|
|
970
|
-
this.startExternalTurn(snapshot);
|
|
971
|
-
}
|
|
972
|
-
this.broadcastExternalEvents(snapshot, snapshot.events.filter((event) => event.lineNumber > mirror.lastLine));
|
|
973
|
-
mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
|
|
974
|
-
mirror.latestStatus = externalStatusLine(snapshot, this.queue().length);
|
|
975
|
-
this.broadcastStatus(mirror.latestStatus, "info");
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
979
|
-
if (terminalEvent && terminalEvent.lineNumber > mirror.lastLine) {
|
|
980
|
-
const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
|
|
981
|
-
const finalText = finalAgent?.text ?? snapshot.latestAgentMessage;
|
|
982
|
-
const finalLine = finalAgent?.lineNumber ?? snapshot.lineCount;
|
|
983
|
-
if (finalText && finalLine !== mirror.latestAgentLine) {
|
|
984
|
-
this.chatStore.append({
|
|
985
|
-
threadId: snapshot.threadId,
|
|
986
|
-
role: "agent",
|
|
987
|
-
text: finalText,
|
|
988
|
-
source: "cli",
|
|
989
|
-
turnId: terminalEvent.turnId ?? undefined,
|
|
990
|
-
});
|
|
991
|
-
this.broadcast({ type: "text_delta", id: terminalEvent.turnId ?? "cli", delta: finalText });
|
|
992
|
-
mirror.latestAgentLine = finalLine;
|
|
993
|
-
}
|
|
994
|
-
const externalStartedAt = mirror.startedAt ? new Date(mirror.startedAt) : snapshot.activity.startedAt;
|
|
995
|
-
this.broadcast({
|
|
996
|
-
type: "turn_complete",
|
|
997
|
-
id: terminalEvent.turnId ?? "cli",
|
|
998
|
-
at: terminalEvent.timestamp?.toISOString() ?? new Date().toISOString(),
|
|
999
|
-
});
|
|
1000
|
-
this.appendActivity({
|
|
1001
|
-
source: "cli",
|
|
1002
|
-
status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
|
|
1003
|
-
type: "cli_turn_finished",
|
|
1004
|
-
threadId: snapshot.threadId,
|
|
1005
|
-
workspace: info.workspace,
|
|
1006
|
-
agentId: info.agentId,
|
|
1007
|
-
prompt: snapshot.latestUserMessage ?? undefined,
|
|
1008
|
-
detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
|
|
1009
|
-
durationMs: durationFromDates(externalStartedAt, terminalEvent.timestamp),
|
|
1010
|
-
});
|
|
1011
|
-
if (externalStartedAt && terminalEvent.turnId) {
|
|
1012
|
-
await this.persistWorkspaceArtifactsForTurn(info.workspace, terminalEvent.turnId, externalStartedAt);
|
|
1013
|
-
}
|
|
1014
|
-
mirror.latestStatus = `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`;
|
|
1015
|
-
this.broadcastStatus(`${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`, terminalEvent.status === "failed" ? "error" : terminalEvent.status === "aborted" ? "warn" : "info");
|
|
1016
|
-
this.broadcast({ type: "chat_history", messages: await this.chatHistory() });
|
|
1017
|
-
await this.drainQueue();
|
|
1018
|
-
}
|
|
1019
|
-
mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
|
|
1020
|
-
}
|
|
1021
|
-
async monitorExternalActivitySafe() {
|
|
1022
|
-
if (this.externalMonitorRunning) {
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
this.externalMonitorRunning = true;
|
|
1026
|
-
try {
|
|
1027
|
-
await this.monitorExternalActivity();
|
|
1028
|
-
}
|
|
1029
|
-
catch (error) {
|
|
1030
|
-
this.broadcastStatus(friendlyErrorText(error), "error");
|
|
1031
|
-
}
|
|
1032
|
-
finally {
|
|
1033
|
-
this.externalMonitorRunning = false;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
startExternalTurn(snapshot) {
|
|
1037
|
-
const prompt = snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`;
|
|
1038
|
-
this.chatStore.append({
|
|
1039
|
-
threadId: snapshot.threadId,
|
|
1040
|
-
role: "user",
|
|
1041
|
-
text: prompt,
|
|
1042
|
-
source: "cli",
|
|
1043
|
-
turnId: snapshot.activity.turnId ?? undefined,
|
|
1044
|
-
timestamp: snapshot.activity.startedAt?.toISOString(),
|
|
1045
|
-
});
|
|
1046
|
-
this.broadcast({
|
|
1047
|
-
type: "turn_start",
|
|
1048
|
-
id: snapshot.activity.turnId ?? "cli",
|
|
1049
|
-
prompt,
|
|
1050
|
-
at: snapshot.activity.startedAt?.toISOString() ?? new Date().toISOString(),
|
|
1051
|
-
source: "cli",
|
|
1052
|
-
});
|
|
1053
|
-
this.appendActivity({
|
|
1054
|
-
source: "cli",
|
|
1055
|
-
status: "running",
|
|
1056
|
-
type: "cli_turn_started",
|
|
1057
|
-
threadId: snapshot.threadId,
|
|
1058
|
-
prompt,
|
|
1059
|
-
detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
broadcastExternalEvents(snapshot, events) {
|
|
1063
|
-
for (const event of events) {
|
|
1064
|
-
if (event.kind === "tool" && event.status === "started") {
|
|
1065
|
-
this.broadcast({
|
|
1066
|
-
type: "tool_start",
|
|
1067
|
-
id: snapshot.activity.turnId ?? "cli",
|
|
1068
|
-
toolCallId: `cli-${event.lineNumber}`,
|
|
1069
|
-
toolName: event.toolName ?? "tool",
|
|
1070
|
-
});
|
|
1071
|
-
this.appendActivity({
|
|
1072
|
-
source: "cli",
|
|
1073
|
-
status: "running",
|
|
1074
|
-
type: "cli_tool_started",
|
|
1075
|
-
threadId: snapshot.threadId,
|
|
1076
|
-
detail: event.toolName ?? "tool",
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
if (event.kind === "tool" && event.status === "finished") {
|
|
1080
|
-
this.broadcast({
|
|
1081
|
-
type: "tool_end",
|
|
1082
|
-
id: snapshot.activity.turnId ?? "cli",
|
|
1083
|
-
toolCallId: `cli-${event.lineNumber}`,
|
|
1084
|
-
isError: false,
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
952
|
async getSession(deferThreadStart) {
|
|
1090
953
|
return this.registry.getOrCreate(WEB_CONTEXT_KEY, { deferThreadStart });
|
|
1091
954
|
}
|
|
@@ -1104,6 +967,14 @@ export class RelayRuntime {
|
|
|
1104
967
|
});
|
|
1105
968
|
return { session, dispose: true };
|
|
1106
969
|
}
|
|
970
|
+
cliPathOptions() {
|
|
971
|
+
return {
|
|
972
|
+
piCliPath: this.config.piCliPath,
|
|
973
|
+
hermesCliPath: this.config.hermesCliPath,
|
|
974
|
+
openClawCliPath: this.config.openClawCliPath,
|
|
975
|
+
claudeCodeCliPath: this.config.claudeCodeCliPath,
|
|
976
|
+
};
|
|
977
|
+
}
|
|
1107
978
|
async ensureActiveThread(session) {
|
|
1108
979
|
if (!session.hasActiveThread()) {
|
|
1109
980
|
await session.newThread();
|
|
@@ -1199,7 +1070,7 @@ export class RelayRuntime {
|
|
|
1199
1070
|
outputChars: 0,
|
|
1200
1071
|
tools: [],
|
|
1201
1072
|
};
|
|
1202
|
-
this.
|
|
1073
|
+
this.queueService.setLastPrompt(envelope);
|
|
1203
1074
|
const startedDate = new Date();
|
|
1204
1075
|
const startedAt = startedDate.toISOString();
|
|
1205
1076
|
this.chatStore.append({
|
|
@@ -1257,7 +1128,7 @@ export class RelayRuntime {
|
|
|
1257
1128
|
try {
|
|
1258
1129
|
await session.prompt(envelope.input, callbacks);
|
|
1259
1130
|
this.updateSession(session);
|
|
1260
|
-
await this.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, turnId, startedDate);
|
|
1131
|
+
await this.artifactService.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, turnId, startedDate);
|
|
1261
1132
|
if (this.accumulatedText.trim()) {
|
|
1262
1133
|
this.chatStore.append({
|
|
1263
1134
|
threadId: info.threadId ?? "pending",
|
|
@@ -1335,7 +1206,7 @@ export class RelayRuntime {
|
|
|
1335
1206
|
}
|
|
1336
1207
|
}
|
|
1337
1208
|
async drainQueue() {
|
|
1338
|
-
if (this.draining || this.
|
|
1209
|
+
if (this.draining || this.queueService.isPaused()) {
|
|
1339
1210
|
return;
|
|
1340
1211
|
}
|
|
1341
1212
|
this.draining = true;
|
|
@@ -1344,10 +1215,10 @@ export class RelayRuntime {
|
|
|
1344
1215
|
while (!session.isProcessing()) {
|
|
1345
1216
|
const external = getExternalSnapshotForSession(session, this.config, { maxEvents: 0 });
|
|
1346
1217
|
if (external?.activity.active) {
|
|
1347
|
-
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.
|
|
1218
|
+
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.queueService.length()} queued.`, "info");
|
|
1348
1219
|
return;
|
|
1349
1220
|
}
|
|
1350
|
-
const next = this.
|
|
1221
|
+
const next = this.queueService.dequeue();
|
|
1351
1222
|
this.broadcastQueue();
|
|
1352
1223
|
if (!next) {
|
|
1353
1224
|
return;
|
|
@@ -1417,31 +1288,6 @@ export class RelayRuntime {
|
|
|
1417
1288
|
}
|
|
1418
1289
|
this.updateCurrentProgress({ currentTool: toolName, lastTool: toolName });
|
|
1419
1290
|
}
|
|
1420
|
-
externalTask() {
|
|
1421
|
-
if (!this.externalMirror) {
|
|
1422
|
-
return null;
|
|
1423
|
-
}
|
|
1424
|
-
const startedAt = this.externalMirror.startedAt ?? new Date().toISOString();
|
|
1425
|
-
const startedMs = new Date(startedAt).getTime();
|
|
1426
|
-
return {
|
|
1427
|
-
id: this.externalMirror.turnId ?? "cli",
|
|
1428
|
-
source: "cli",
|
|
1429
|
-
status: this.externalMirror.latestStatus?.includes("failed")
|
|
1430
|
-
? "failed"
|
|
1431
|
-
: this.externalMirror.latestStatus?.includes("aborted")
|
|
1432
|
-
? "aborted"
|
|
1433
|
-
: this.externalMirror.latestStatus?.includes("finished") || this.externalMirror.latestStatus?.includes("completed")
|
|
1434
|
-
? "completed"
|
|
1435
|
-
: "running",
|
|
1436
|
-
threadId: this.externalMirror.threadId,
|
|
1437
|
-
startedAt,
|
|
1438
|
-
updatedAt: new Date().toISOString(),
|
|
1439
|
-
durationMs: Number.isFinite(startedMs) ? Math.max(0, Date.now() - startedMs) : 0,
|
|
1440
|
-
outputChars: 0,
|
|
1441
|
-
tools: [],
|
|
1442
|
-
detail: this.externalMirror.latestStatus ?? this.externalMirror.rolloutPath,
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
1291
|
broadcastQueue() {
|
|
1446
1292
|
this.broadcast({ type: "queue_update", queue: this.queue(), paused: this.queuePaused() });
|
|
1447
1293
|
}
|
|
@@ -1468,53 +1314,6 @@ export class RelayRuntime {
|
|
|
1468
1314
|
capabilities: info.capabilities ?? CODEX_AGENT_CAPABILITIES,
|
|
1469
1315
|
};
|
|
1470
1316
|
}
|
|
1471
|
-
async persistWorkspaceArtifactsForTurn(workspace, turnId, startedAt) {
|
|
1472
|
-
const report = await collectRecentWorkspaceArtifacts(workspace, {
|
|
1473
|
-
since: startedAt,
|
|
1474
|
-
until: new Date(),
|
|
1475
|
-
maxFileSize: this.config.maxFileSize,
|
|
1476
|
-
limit: 20,
|
|
1477
|
-
ignoreDirs: this.config.artifactIgnoreDirs,
|
|
1478
|
-
ignoreGlobs: this.config.artifactIgnoreGlobs,
|
|
1479
|
-
});
|
|
1480
|
-
if (report.artifacts.length === 0 && report.skippedCount === 0 && !report.omittedCount) {
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
await persistWorkspaceArtifactReport(workspace, turnId, report);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
function queueItemDto(item) {
|
|
1487
|
-
return {
|
|
1488
|
-
id: item.id,
|
|
1489
|
-
description: item.description,
|
|
1490
|
-
createdAt: new Date(item.createdAt).toISOString(),
|
|
1491
|
-
attempts: item.attempts ?? 0,
|
|
1492
|
-
notBefore: item.notBefore ? new Date(item.notBefore).toISOString() : undefined,
|
|
1493
|
-
lastError: item.lastError,
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
function artifactDto(report) {
|
|
1497
|
-
return {
|
|
1498
|
-
turnId: report.turnId,
|
|
1499
|
-
updatedAt: report.updatedAt.toISOString(),
|
|
1500
|
-
source: report.source,
|
|
1501
|
-
fileCount: report.artifacts.length,
|
|
1502
|
-
totalSizeBytes: totalArtifactSize(report.artifacts),
|
|
1503
|
-
skippedCount: report.skippedCount,
|
|
1504
|
-
omittedCount: report.omittedCount,
|
|
1505
|
-
artifacts: report.artifacts.map((artifact) => ({
|
|
1506
|
-
name: artifact.name,
|
|
1507
|
-
relativePath: artifact.relativePath.split(path.sep).join("/"),
|
|
1508
|
-
sizeBytes: artifact.sizeBytes,
|
|
1509
|
-
})),
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
function externalStatusLine(snapshot, queueLength) {
|
|
1513
|
-
const elapsed = snapshot.activity.startedAt
|
|
1514
|
-
? formatDuration((Date.now() - snapshot.activity.startedAt.getTime()) / 1000)
|
|
1515
|
-
: "-";
|
|
1516
|
-
const tool = snapshot.latestToolName ?? "-";
|
|
1517
|
-
return `${snapshot.agentLabel} CLI running · ${elapsed} · tool ${tool} · ${queueLength} queued`;
|
|
1518
1317
|
}
|
|
1519
1318
|
function cliHealthForAgent(agentId, health) {
|
|
1520
1319
|
if (agentId === "pi") {
|
|
@@ -1572,23 +1371,6 @@ function hostLogoutCommand(info, config) {
|
|
|
1572
1371
|
}
|
|
1573
1372
|
return "codex logout";
|
|
1574
1373
|
}
|
|
1575
|
-
function durationFromDates(start, end) {
|
|
1576
|
-
if (!start || !end) {
|
|
1577
|
-
return undefined;
|
|
1578
|
-
}
|
|
1579
|
-
return Math.max(0, end.getTime() - start.getTime());
|
|
1580
|
-
}
|
|
1581
|
-
function formatDuration(seconds) {
|
|
1582
|
-
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
1583
|
-
return "-";
|
|
1584
|
-
}
|
|
1585
|
-
if (seconds < 60) {
|
|
1586
|
-
return `${Math.round(seconds)}s`;
|
|
1587
|
-
}
|
|
1588
|
-
const minutes = Math.floor(seconds / 60);
|
|
1589
|
-
const remainder = Math.round(seconds % 60);
|
|
1590
|
-
return `${minutes}m ${remainder}s`;
|
|
1591
|
-
}
|
|
1592
1374
|
function normalizeMimeType(value, name) {
|
|
1593
1375
|
const configured = value?.trim();
|
|
1594
1376
|
if (configured) {
|
|
@@ -1622,37 +1404,3 @@ function uploadFileDtos(files) {
|
|
|
1622
1404
|
sizeBytes: file.sizeBytes,
|
|
1623
1405
|
}));
|
|
1624
1406
|
}
|
|
1625
|
-
function isPreviewableTextFile(extension, sizeBytes) {
|
|
1626
|
-
if (sizeBytes > MAX_TEXT_PREVIEW_BYTES * 4) {
|
|
1627
|
-
return false;
|
|
1628
|
-
}
|
|
1629
|
-
return [
|
|
1630
|
-
"",
|
|
1631
|
-
".c",
|
|
1632
|
-
".conf",
|
|
1633
|
-
".cpp",
|
|
1634
|
-
".css",
|
|
1635
|
-
".csv",
|
|
1636
|
-
".env",
|
|
1637
|
-
".go",
|
|
1638
|
-
".html",
|
|
1639
|
-
".java",
|
|
1640
|
-
".js",
|
|
1641
|
-
".json",
|
|
1642
|
-
".jsx",
|
|
1643
|
-
".log",
|
|
1644
|
-
".md",
|
|
1645
|
-
".py",
|
|
1646
|
-
".rb",
|
|
1647
|
-
".rs",
|
|
1648
|
-
".sh",
|
|
1649
|
-
".sql",
|
|
1650
|
-
".toml",
|
|
1651
|
-
".ts",
|
|
1652
|
-
".tsx",
|
|
1653
|
-
".txt",
|
|
1654
|
-
".xml",
|
|
1655
|
-
".yaml",
|
|
1656
|
-
".yml",
|
|
1657
|
-
].includes(extension);
|
|
1658
|
-
}
|