@nordbyte/nordrelay 0.5.0 → 0.5.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 +16 -10
- package/dist/access-control.js +2 -0
- package/dist/agent-updates.js +43 -8
- package/dist/bot-ui.js +1 -0
- package/dist/bot.js +108 -1063
- package/dist/channel-actions.js +8 -8
- package/dist/operations.js +63 -9
- 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 +77 -359
- package/dist/support-bundle.js +205 -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 +43 -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/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
|
}
|
|
@@ -132,18 +150,18 @@ export class RelayRuntime {
|
|
|
132
150
|
agentUpdateJobs() {
|
|
133
151
|
return this.agentUpdates.list();
|
|
134
152
|
}
|
|
135
|
-
startAgentUpdate(agentId) {
|
|
153
|
+
startAgentUpdate(agentId, operation = "update") {
|
|
136
154
|
const job = this.agentUpdates.start(agentId, {
|
|
137
155
|
piCliPath: this.config.piCliPath,
|
|
138
156
|
hermesCliPath: this.config.hermesCliPath,
|
|
139
157
|
openClawCliPath: this.config.openClawCliPath,
|
|
140
158
|
claudeCodeCliPath: this.config.claudeCodeCliPath,
|
|
141
|
-
});
|
|
142
|
-
this.broadcastStatus(`${job.agentLabel}
|
|
159
|
+
}, operation);
|
|
160
|
+
this.broadcastStatus(`${job.agentLabel} ${operation} started. Log: ${job.logPath}`, "warn");
|
|
143
161
|
this.appendActivity({
|
|
144
162
|
source: "web",
|
|
145
163
|
status: "info",
|
|
146
|
-
type: "agent_update_started",
|
|
164
|
+
type: operation === "install" ? "agent_install_started" : "agent_update_started",
|
|
147
165
|
agentId,
|
|
148
166
|
threadId: null,
|
|
149
167
|
workspace: this.config.workspace,
|
|
@@ -154,7 +172,7 @@ export class RelayRuntime {
|
|
|
154
172
|
status: "ok",
|
|
155
173
|
contextKey: WEB_CONTEXT_KEY,
|
|
156
174
|
agentId,
|
|
157
|
-
description:
|
|
175
|
+
description: `${operation} ${agentId}`,
|
|
158
176
|
detail: job.summary,
|
|
159
177
|
});
|
|
160
178
|
return job;
|
|
@@ -188,8 +206,8 @@ export class RelayRuntime {
|
|
|
188
206
|
runtime: {
|
|
189
207
|
stateBackend: this.config.stateBackend,
|
|
190
208
|
sourceWorkspace: this.config.workspace,
|
|
191
|
-
queuePaused: this.
|
|
192
|
-
externalMirror: this.
|
|
209
|
+
queuePaused: this.queueService.isPaused(),
|
|
210
|
+
externalMirror: this.externalActivityMonitor.snapshot(),
|
|
193
211
|
agentDiagnostics: getAgentDiagnostics(await this.getSession(true), this.config),
|
|
194
212
|
},
|
|
195
213
|
};
|
|
@@ -244,7 +262,7 @@ export class RelayRuntime {
|
|
|
244
262
|
tasks() {
|
|
245
263
|
return {
|
|
246
264
|
current: this.currentProgress ? { ...this.currentProgress, tools: [...this.currentProgress.tools] } : null,
|
|
247
|
-
external: this.
|
|
265
|
+
external: this.externalActivityMonitor.task(),
|
|
248
266
|
queue: this.queue(),
|
|
249
267
|
queuePaused: this.queuePaused(),
|
|
250
268
|
recent: this.activity({ limit: 20 }),
|
|
@@ -253,6 +271,32 @@ export class RelayRuntime {
|
|
|
253
271
|
audit(limit = 50) {
|
|
254
272
|
return this.auditStore.list(limit);
|
|
255
273
|
}
|
|
274
|
+
async supportBundle() {
|
|
275
|
+
const bundle = await createSupportBundle({
|
|
276
|
+
config: this.config,
|
|
277
|
+
diagnostics: await this.diagnostics(),
|
|
278
|
+
adapterHealth: await this.adapterHealth(),
|
|
279
|
+
auditEvents: this.auditStore.list(100),
|
|
280
|
+
agentUpdateJobs: this.agentUpdates.list(),
|
|
281
|
+
source: "web",
|
|
282
|
+
});
|
|
283
|
+
this.appendActivity({
|
|
284
|
+
source: "web",
|
|
285
|
+
status: "info",
|
|
286
|
+
type: "diagnostics_bundle_exported",
|
|
287
|
+
threadId: null,
|
|
288
|
+
workspace: this.config.workspace,
|
|
289
|
+
detail: bundle.path,
|
|
290
|
+
});
|
|
291
|
+
this.appendAudit({
|
|
292
|
+
action: "command",
|
|
293
|
+
status: "ok",
|
|
294
|
+
contextKey: WEB_CONTEXT_KEY,
|
|
295
|
+
description: "export diagnostics bundle",
|
|
296
|
+
detail: bundle.path,
|
|
297
|
+
});
|
|
298
|
+
return bundle;
|
|
299
|
+
}
|
|
256
300
|
locks() {
|
|
257
301
|
return this.lockStore.list();
|
|
258
302
|
}
|
|
@@ -463,7 +507,7 @@ export class RelayRuntime {
|
|
|
463
507
|
return this.activityStore.list(options).map((event) => this.enrichActivityEvent(event, currentInfo));
|
|
464
508
|
}
|
|
465
509
|
async retry() {
|
|
466
|
-
const cached = this.
|
|
510
|
+
const cached = this.queueService.getLastPrompt();
|
|
467
511
|
if (!cached) {
|
|
468
512
|
throw new Error("Nothing to retry. Send a message first.");
|
|
469
513
|
}
|
|
@@ -758,7 +802,7 @@ export class RelayRuntime {
|
|
|
758
802
|
const session = await this.getSession(false);
|
|
759
803
|
const external = getExternalSnapshotForSession(session, this.config, { maxEvents: 0 });
|
|
760
804
|
if (session.isProcessing() || external?.activity.active) {
|
|
761
|
-
const queued = this.
|
|
805
|
+
const queued = this.queueService.enqueue(envelope);
|
|
762
806
|
const info = this.publicInfo(session);
|
|
763
807
|
this.appendActivity({
|
|
764
808
|
source: "web",
|
|
@@ -770,7 +814,7 @@ export class RelayRuntime {
|
|
|
770
814
|
prompt: envelope.description,
|
|
771
815
|
detail: external?.activity.active
|
|
772
816
|
? `Queued because ${external.agentLabel} CLI is still processing another task.`
|
|
773
|
-
: `Queued at position ${this.
|
|
817
|
+
: `Queued at position ${this.queueService.length()}.`,
|
|
774
818
|
});
|
|
775
819
|
this.appendAudit({
|
|
776
820
|
action: "prompt_queued",
|
|
@@ -783,7 +827,7 @@ export class RelayRuntime {
|
|
|
783
827
|
description: envelope.description,
|
|
784
828
|
});
|
|
785
829
|
if (external?.activity.active) {
|
|
786
|
-
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.
|
|
830
|
+
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.queueService.length()} queued.`, "info");
|
|
787
831
|
}
|
|
788
832
|
this.broadcastQueue();
|
|
789
833
|
return { queued: true, queueId: queued.id };
|
|
@@ -794,30 +838,14 @@ export class RelayRuntime {
|
|
|
794
838
|
return { queued: false };
|
|
795
839
|
}
|
|
796
840
|
queue() {
|
|
797
|
-
return this.
|
|
841
|
+
return this.queueService.list();
|
|
798
842
|
}
|
|
799
843
|
queuePaused() {
|
|
800
|
-
return this.
|
|
844
|
+
return this.queueService.isPaused();
|
|
801
845
|
}
|
|
802
846
|
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);
|
|
847
|
+
this.queueService.apply(action, id);
|
|
817
848
|
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
849
|
void this.drainQueue().catch((error) => this.broadcastStatus(friendlyErrorText(error), "error"));
|
|
822
850
|
}
|
|
823
851
|
this.appendActivity({
|
|
@@ -839,58 +867,23 @@ export class RelayRuntime {
|
|
|
839
867
|
}
|
|
840
868
|
async artifacts() {
|
|
841
869
|
const session = await this.getSession(true);
|
|
842
|
-
return (
|
|
870
|
+
return this.artifactService.list(session.getInfo().workspace, 20);
|
|
843
871
|
}
|
|
844
872
|
async artifact(turnId) {
|
|
845
873
|
const session = await this.getSession(true);
|
|
846
|
-
return
|
|
874
|
+
return this.artifactService.get(session.getInfo().workspace, turnId);
|
|
847
875
|
}
|
|
848
876
|
async deleteArtifact(turnId) {
|
|
849
877
|
const session = await this.getSession(true);
|
|
850
|
-
return
|
|
878
|
+
return this.artifactService.delete(session.getInfo().workspace, turnId);
|
|
851
879
|
}
|
|
852
880
|
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;
|
|
881
|
+
const session = await this.getSession(true);
|
|
882
|
+
return this.artifactService.createZip(session.getInfo().workspace, turnId);
|
|
862
883
|
}
|
|
863
884
|
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
|
-
};
|
|
885
|
+
const session = await this.getSession(true);
|
|
886
|
+
return this.artifactService.preview(session.getInfo().workspace, turnId, relativePath);
|
|
894
887
|
}
|
|
895
888
|
async logs(target = "connector", lines = 100) {
|
|
896
889
|
if (target === "update") {
|
|
@@ -934,158 +927,6 @@ export class RelayRuntime {
|
|
|
934
927
|
this.registry.disposeAll();
|
|
935
928
|
this.subscribers.clear();
|
|
936
929
|
}
|
|
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
930
|
async getSession(deferThreadStart) {
|
|
1090
931
|
return this.registry.getOrCreate(WEB_CONTEXT_KEY, { deferThreadStart });
|
|
1091
932
|
}
|
|
@@ -1199,7 +1040,7 @@ export class RelayRuntime {
|
|
|
1199
1040
|
outputChars: 0,
|
|
1200
1041
|
tools: [],
|
|
1201
1042
|
};
|
|
1202
|
-
this.
|
|
1043
|
+
this.queueService.setLastPrompt(envelope);
|
|
1203
1044
|
const startedDate = new Date();
|
|
1204
1045
|
const startedAt = startedDate.toISOString();
|
|
1205
1046
|
this.chatStore.append({
|
|
@@ -1257,7 +1098,7 @@ export class RelayRuntime {
|
|
|
1257
1098
|
try {
|
|
1258
1099
|
await session.prompt(envelope.input, callbacks);
|
|
1259
1100
|
this.updateSession(session);
|
|
1260
|
-
await this.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, turnId, startedDate);
|
|
1101
|
+
await this.artifactService.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, turnId, startedDate);
|
|
1261
1102
|
if (this.accumulatedText.trim()) {
|
|
1262
1103
|
this.chatStore.append({
|
|
1263
1104
|
threadId: info.threadId ?? "pending",
|
|
@@ -1335,7 +1176,7 @@ export class RelayRuntime {
|
|
|
1335
1176
|
}
|
|
1336
1177
|
}
|
|
1337
1178
|
async drainQueue() {
|
|
1338
|
-
if (this.draining || this.
|
|
1179
|
+
if (this.draining || this.queueService.isPaused()) {
|
|
1339
1180
|
return;
|
|
1340
1181
|
}
|
|
1341
1182
|
this.draining = true;
|
|
@@ -1344,10 +1185,10 @@ export class RelayRuntime {
|
|
|
1344
1185
|
while (!session.isProcessing()) {
|
|
1345
1186
|
const external = getExternalSnapshotForSession(session, this.config, { maxEvents: 0 });
|
|
1346
1187
|
if (external?.activity.active) {
|
|
1347
|
-
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.
|
|
1188
|
+
this.broadcastStatus(`Waiting for ${external.agentLabel} CLI task... ${this.queueService.length()} queued.`, "info");
|
|
1348
1189
|
return;
|
|
1349
1190
|
}
|
|
1350
|
-
const next = this.
|
|
1191
|
+
const next = this.queueService.dequeue();
|
|
1351
1192
|
this.broadcastQueue();
|
|
1352
1193
|
if (!next) {
|
|
1353
1194
|
return;
|
|
@@ -1417,31 +1258,6 @@ export class RelayRuntime {
|
|
|
1417
1258
|
}
|
|
1418
1259
|
this.updateCurrentProgress({ currentTool: toolName, lastTool: toolName });
|
|
1419
1260
|
}
|
|
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
1261
|
broadcastQueue() {
|
|
1446
1262
|
this.broadcast({ type: "queue_update", queue: this.queue(), paused: this.queuePaused() });
|
|
1447
1263
|
}
|
|
@@ -1468,53 +1284,6 @@ export class RelayRuntime {
|
|
|
1468
1284
|
capabilities: info.capabilities ?? CODEX_AGENT_CAPABILITIES,
|
|
1469
1285
|
};
|
|
1470
1286
|
}
|
|
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
1287
|
}
|
|
1519
1288
|
function cliHealthForAgent(agentId, health) {
|
|
1520
1289
|
if (agentId === "pi") {
|
|
@@ -1572,23 +1341,6 @@ function hostLogoutCommand(info, config) {
|
|
|
1572
1341
|
}
|
|
1573
1342
|
return "codex logout";
|
|
1574
1343
|
}
|
|
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
1344
|
function normalizeMimeType(value, name) {
|
|
1593
1345
|
const configured = value?.trim();
|
|
1594
1346
|
if (configured) {
|
|
@@ -1622,37 +1374,3 @@ function uploadFileDtos(files) {
|
|
|
1622
1374
|
sizeBytes: file.sizeBytes,
|
|
1623
1375
|
}));
|
|
1624
1376
|
}
|
|
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
|
-
}
|