@nordbyte/nordrelay 0.8.0 → 0.8.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.
Files changed (173) hide show
  1. package/.env.example +9 -0
  2. package/README.md +81 -1197
  3. package/dist/{access-control.js → access/access-control.js} +1 -1
  4. package/dist/{audit-log.js → access/audit-log.js} +2 -2
  5. package/dist/{session-locks.js → access/session-locks.js} +1 -1
  6. package/dist/{user-management.js → access/user-management.js} +1 -1
  7. package/dist/{claude-code-cli.js → agents/claude-code/claude-code-cli.js} +2 -2
  8. package/dist/{claude-code-session.js → agents/claude-code/claude-code-session.js} +1 -1
  9. package/dist/{codex-cli.js → agents/codex/codex-cli.js} +14 -5
  10. package/dist/{codex-session.js → agents/codex/codex-session.js} +2 -4
  11. package/dist/{hermes-cli.js → agents/hermes/hermes-cli.js} +2 -2
  12. package/dist/{hermes-launch.js → agents/hermes/hermes-launch.js} +1 -1
  13. package/dist/{hermes-session.js → agents/hermes/hermes-session.js} +1 -1
  14. package/dist/{openclaw-cli.js → agents/openclaw/openclaw-cli.js} +2 -2
  15. package/dist/{openclaw-launch.js → agents/openclaw/openclaw-launch.js} +1 -1
  16. package/dist/{openclaw-session.js → agents/openclaw/openclaw-session.js} +1 -1
  17. package/dist/{pi-cli.js → agents/pi/pi-cli.js} +2 -2
  18. package/dist/{pi-launch.js → agents/pi/pi-launch.js} +1 -1
  19. package/dist/{pi-session.js → agents/pi/pi-session.js} +1 -1
  20. package/dist/{adapter-conformance.js → agents/shared/adapter-conformance.js} +2 -2
  21. package/dist/{agent-activity.js → agents/shared/agent-activity.js} +5 -5
  22. package/dist/agents/shared/agent-auth-commands.js +30 -0
  23. package/dist/{agent-factory.js → agents/shared/agent-factory.js} +5 -5
  24. package/dist/{agent-feature-matrix.js → agents/shared/agent-feature-matrix.js} +2 -2
  25. package/dist/{agent-updates.js → agents/shared/agent-updates.js} +7 -7
  26. package/dist/{discord-artifacts.js → channels/discord/discord-artifacts.js} +4 -4
  27. package/dist/{discord-bot.js → channels/discord/discord-bot.js} +164 -424
  28. package/dist/{discord-channel-runtime.js → channels/discord/discord-channel-runtime.js} +2 -2
  29. package/dist/{discord-command-surface.js → channels/discord/discord-command-surface.js} +3 -3
  30. package/dist/{bot-rendering.js → channels/shared/bot-rendering.js} +6 -6
  31. package/dist/{channel-actions.js → channels/shared/channel-actions.js} +4 -4
  32. package/dist/channels/shared/channel-bridge-controller.js +69 -0
  33. package/dist/channels/shared/channel-cli-artifacts.js +51 -0
  34. package/dist/{channel-command-service.js → channels/shared/channel-command-service.js} +51 -28
  35. package/dist/channels/shared/channel-external-mirror-controller.js +193 -0
  36. package/dist/channels/shared/channel-external-monitor.js +52 -0
  37. package/dist/{channel-mirror-registry.js → channels/shared/channel-mirror-registry.js} +14 -6
  38. package/dist/{channel-peer-prompt.js → channels/shared/channel-peer-prompt.js} +3 -3
  39. package/dist/{channel-turn-service.js → channels/shared/channel-turn-service.js} +2 -2
  40. package/dist/{context-key.js → channels/shared/context-key.js} +1 -1
  41. package/dist/{session-format.js → channels/shared/session-format.js} +2 -2
  42. package/dist/{slack-artifacts.js → channels/slack/slack-artifacts.js} +4 -4
  43. package/dist/{slack-bot.js → channels/slack/slack-bot.js} +159 -294
  44. package/dist/{slack-channel-runtime.js → channels/slack/slack-channel-runtime.js} +2 -2
  45. package/dist/{slack-command-surface.js → channels/slack/slack-command-surface.js} +2 -2
  46. package/dist/{slack-diagnostics.js → channels/slack/slack-diagnostics.js} +2 -2
  47. package/dist/{bot-ui.js → channels/telegram/bot-ui.js} +1 -1
  48. package/dist/{bot.js → channels/telegram/bot.js} +178 -427
  49. package/dist/{telegram-access-commands.js → channels/telegram/telegram-access-commands.js} +3 -3
  50. package/dist/{telegram-access-middleware.js → channels/telegram/telegram-access-middleware.js} +4 -4
  51. package/dist/{telegram-agent-commands.js → channels/telegram/telegram-agent-commands.js} +9 -9
  52. package/dist/{telegram-artifact-commands.js → channels/telegram/telegram-artifact-commands.js} +4 -4
  53. package/dist/{telegram-channel-runtime.js → channels/telegram/telegram-channel-runtime.js} +2 -2
  54. package/dist/{telegram-command-menu.js → channels/telegram/telegram-command-menu.js} +1 -1
  55. package/dist/{telegram-diagnostics-command.js → channels/telegram/telegram-diagnostics-command.js} +7 -7
  56. package/dist/{telegram-general-commands.js → channels/telegram/telegram-general-commands.js} +4 -4
  57. package/dist/{telegram-operational-commands.js → channels/telegram/telegram-operational-commands.js} +5 -5
  58. package/dist/{telegram-output.js → channels/telegram/telegram-output.js} +2 -2
  59. package/dist/{telegram-preference-commands.js → channels/telegram/telegram-preference-commands.js} +3 -3
  60. package/dist/{telegram-queue-commands.js → channels/telegram/telegram-queue-commands.js} +6 -6
  61. package/dist/{telegram-support-command.js → channels/telegram/telegram-support-command.js} +4 -4
  62. package/dist/{telegram-update-commands.js → channels/telegram/telegram-update-commands.js} +5 -5
  63. package/dist/{config-metadata.js → core/config-metadata.js} +8 -0
  64. package/dist/{config.js → core/config.js} +11 -3
  65. package/dist/index.js +27 -23
  66. package/dist/{peer-client.js → peers/peer-client.js} +57 -1
  67. package/dist/peers/peer-discovery-jobs.js +206 -0
  68. package/dist/peers/peer-discovery.js +223 -0
  69. package/dist/peers/peer-health-monitor.js +49 -0
  70. package/dist/{peer-identity.js → peers/peer-identity.js} +50 -1
  71. package/dist/{peer-runtime-service.js → peers/peer-runtime-service.js} +29 -7
  72. package/dist/{peer-server.js → peers/peer-server.js} +23 -6
  73. package/dist/{peer-store.js → peers/peer-store.js} +84 -11
  74. package/dist/{peer-types.js → peers/peer-types.js} +9 -0
  75. package/dist/peers/peer-web-proxy-contract.js +127 -0
  76. package/dist/{metrics.js → runtime/metrics.js} +5 -3
  77. package/dist/{relay-artifact-service.js → runtime/relay-artifact-service.js} +1 -1
  78. package/dist/runtime/relay-auth-service.js +63 -0
  79. package/dist/runtime/relay-dashboard-service.js +139 -0
  80. package/dist/{relay-external-activity-monitor.js → runtime/relay-external-activity-monitor.js} +140 -53
  81. package/dist/runtime/relay-runtime-active-sessions.js +387 -0
  82. package/dist/runtime/relay-runtime-dashboard.js +201 -0
  83. package/dist/runtime/relay-runtime-prompt-queue-artifacts.js +307 -0
  84. package/dist/runtime/relay-runtime-sessions.js +623 -0
  85. package/dist/runtime/relay-runtime-types.js +1 -0
  86. package/dist/runtime/relay-runtime-updates-jobs.js +360 -0
  87. package/dist/runtime/relay-runtime.js +451 -0
  88. package/dist/runtime/runtime-cache.js +117 -0
  89. package/dist/{session-registry.js → state/session-registry.js} +3 -3
  90. package/dist/{operations.js → support/operations.js} +7 -7
  91. package/dist/{support-bundle.js → support/support-bundle.js} +1 -1
  92. package/dist/{web-api-contract.js → web/web-api-contract.js} +17 -3
  93. package/dist/web/web-api-types.js +1 -0
  94. package/dist/{web-dashboard-access-routes.js → web/web-dashboard-access-routes.js} +2 -2
  95. package/dist/{web-dashboard-assets.js → web/web-dashboard-assets.js} +24 -2
  96. package/dist/{web-dashboard-http.js → web/web-dashboard-http.js} +41 -5
  97. package/dist/{web-dashboard-pages.js → web/web-dashboard-pages.js} +37 -10
  98. package/dist/{web-dashboard-peer-routes.js → web/web-dashboard-peer-routes.js} +102 -7
  99. package/dist/web/web-dashboard-security.js +14 -0
  100. package/dist/{web-dashboard-session-routes.js → web/web-dashboard-session-routes.js} +12 -1
  101. package/dist/{web-dashboard.js → web/web-dashboard.js} +132 -48
  102. package/dist/web/web-performance.js +60 -0
  103. package/dist/web/web-rate-limit.js +19 -0
  104. package/dist/{web-state.js → web/web-state.js} +74 -5
  105. package/dist/webui-assets/dashboard.css +171 -10
  106. package/dist/webui-assets/dashboard.js +515 -48
  107. package/dist/webui-assets/favicon.ico +0 -0
  108. package/dist/webui-assets/favicon.png +0 -0
  109. package/dist/webui-assets/logo.png +0 -0
  110. package/package.json +4 -3
  111. package/plugins/nordrelay/scripts/nordrelay.mjs +17 -5
  112. package/{launchd/start.sh → scripts/launchd-start.sh} +1 -1
  113. package/dist/relay-runtime.js +0 -1916
  114. package/dist/runtime-cache.js +0 -57
  115. /package/dist/{user-management-crypto.js → access/user-management-crypto.js} +0 -0
  116. /package/dist/{user-management-normalize.js → access/user-management-normalize.js} +0 -0
  117. /package/dist/{user-management-types.js → access/user-management-types.js} +0 -0
  118. /package/dist/{claude-code-auth.js → agents/claude-code/claude-code-auth.js} +0 -0
  119. /package/dist/{claude-code-launch.js → agents/claude-code/claude-code-launch.js} +0 -0
  120. /package/dist/{claude-code-state.js → agents/claude-code/claude-code-state.js} +0 -0
  121. /package/dist/{codex-auth.js → agents/codex/codex-auth.js} +0 -0
  122. /package/dist/{codex-config.js → agents/codex/codex-config.js} +0 -0
  123. /package/dist/{codex-launch.js → agents/codex/codex-launch.js} +0 -0
  124. /package/dist/{codex-state.js → agents/codex/codex-state.js} +0 -0
  125. /package/dist/{hermes-api.js → agents/hermes/hermes-api.js} +0 -0
  126. /package/dist/{hermes-auth.js → agents/hermes/hermes-auth.js} +0 -0
  127. /package/dist/{hermes-state.js → agents/hermes/hermes-state.js} +0 -0
  128. /package/dist/{openclaw-auth.js → agents/openclaw/openclaw-auth.js} +0 -0
  129. /package/dist/{openclaw-gateway.js → agents/openclaw/openclaw-gateway.js} +0 -0
  130. /package/dist/{openclaw-state.js → agents/openclaw/openclaw-state.js} +0 -0
  131. /package/dist/{pi-auth.js → agents/pi/pi-auth.js} +0 -0
  132. /package/dist/{pi-rpc.js → agents/pi/pi-rpc.js} +0 -0
  133. /package/dist/{pi-state.js → agents/pi/pi-state.js} +0 -0
  134. /package/dist/{agent-adapter.js → agents/shared/agent-adapter.js} +0 -0
  135. /package/dist/{agent.js → agents/shared/agent.js} +0 -0
  136. /package/dist/{artifacts.js → artifacts/artifacts.js} +0 -0
  137. /package/dist/{attachments.js → artifacts/attachments.js} +0 -0
  138. /package/dist/{voice.js → artifacts/voice.js} +0 -0
  139. /package/dist/{discord-rate-limit.js → channels/discord/discord-rate-limit.js} +0 -0
  140. /package/dist/{channel-adapter.js → channels/shared/channel-adapter.js} +0 -0
  141. /package/dist/{relay-runtime-types.js → channels/shared/channel-bridge-state.js} +0 -0
  142. /package/dist/{channel-command-catalog.js → channels/shared/channel-command-catalog.js} +0 -0
  143. /package/dist/{channel-command-core.js → channels/shared/channel-command-core.js} +0 -0
  144. /package/dist/{channel-prompt-engine.js → channels/shared/channel-prompt-engine.js} +0 -0
  145. /package/dist/{channel-runtime.js → channels/shared/channel-runtime.js} +0 -0
  146. /package/dist/{channel-turn-lifecycle.js → channels/shared/channel-turn-lifecycle.js} +0 -0
  147. /package/dist/{slack-rate-limit.js → channels/slack/slack-rate-limit.js} +0 -0
  148. /package/dist/{telegram-command-types.js → channels/telegram/telegram-command-types.js} +0 -0
  149. /package/dist/{telegram-rate-limit.js → channels/telegram/telegram-rate-limit.js} +0 -0
  150. /package/dist/{activity-events.js → core/activity-events.js} +0 -0
  151. /package/dist/{error-messages.js → core/error-messages.js} +0 -0
  152. /package/dist/{format.js → core/format.js} +0 -0
  153. /package/dist/{logger.js → core/logger.js} +0 -0
  154. /package/dist/{redaction.js → core/redaction.js} +0 -0
  155. /package/dist/{settings-service.js → core/settings-service.js} +0 -0
  156. /package/dist/{settings-wizard-test.js → core/settings-wizard-test.js} +0 -0
  157. /package/dist/{workspace-policy.js → core/workspace-policy.js} +0 -0
  158. /package/dist/{peer-auth.js → peers/peer-auth.js} +0 -0
  159. /package/dist/{peer-context.js → peers/peer-context.js} +0 -0
  160. /package/dist/{peer-readiness.js → peers/peer-readiness.js} +0 -0
  161. /package/dist/{relay-queue-service.js → runtime/relay-queue-service.js} +0 -0
  162. /package/dist/{web-api-types.js → runtime/relay-runtime-delegate.js} +0 -0
  163. /package/dist/{relay-runtime-helpers.js → runtime/relay-runtime-helpers.js} +0 -0
  164. /package/dist/{remote-prompt.js → runtime/remote-prompt.js} +0 -0
  165. /package/dist/{bot-preferences.js → state/bot-preferences.js} +0 -0
  166. /package/dist/{job-store.js → state/job-store.js} +0 -0
  167. /package/dist/{persistence.js → state/persistence.js} +0 -0
  168. /package/dist/{prompt-store.js → state/prompt-store.js} +0 -0
  169. /package/dist/{state-backend.js → state/state-backend.js} +0 -0
  170. /package/dist/{zip-writer.js → support/zip-writer.js} +0 -0
  171. /package/dist/{web-dashboard-artifact-routes.js → web/web-dashboard-artifact-routes.js} +0 -0
  172. /package/dist/{web-dashboard-runtime-routes.js → web/web-dashboard-runtime-routes.js} +0 -0
  173. /package/dist/{web-dashboard-ui.js → web/web-dashboard-ui.js} +0 -0
@@ -0,0 +1,360 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ensureOutDir } from "../artifacts/artifacts.js";
3
+ import { buildFileInstructions, outboxPath, stageFile, } from "../artifacts/attachments.js";
4
+ import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, isAgentId, } from "../agents/shared/agent.js";
5
+ import { getExternalSnapshotForSession } from "../agents/shared/agent-activity.js";
6
+ import { listAgentAdapterDescriptors } from "../agents/shared/agent-adapter.js";
7
+ import { AgentUpdateManager } from "../agents/shared/agent-updates.js";
8
+ import { createAgentSessionService, enabledAgents } from "../agents/shared/agent-factory.js";
9
+ import { AuditLogStore } from "../access/audit-log.js";
10
+ import { BotPreferencesStore } from "../state/bot-preferences.js";
11
+ import { ChannelCommandService } from "../channels/shared/channel-command-service.js";
12
+ import { ChannelTurnService } from "../channels/shared/channel-turn-service.js";
13
+ import { activeSessionSourceForContextKey, ChannelMirrorRegistry } from "../channels/shared/channel-mirror-registry.js";
14
+ import { listThreads as listCodexThreads } from "../agents/codex/codex-state.js";
15
+ import { friendlyErrorText } from "../core/error-messages.js";
16
+ import { clearLogFile, getAgentUpdateLogPath, getConnectorHealth, getConnectorLogPath, getPackageVersion, getUpdateLogPath, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate } from "../support/operations.js";
17
+ import { PromptStore, toPromptEnvelope } from "../state/prompt-store.js";
18
+ import { UnifiedJobStore } from "../state/job-store.js";
19
+ import { buildRuntimeMetrics } from "./metrics.js";
20
+ import { RelayArtifactService } from "./relay-artifact-service.js";
21
+ import { RelayAuthService } from "./relay-auth-service.js";
22
+ import { RelayExternalActivityMonitor } from "./relay-external-activity-monitor.js";
23
+ import { RelayQueueService } from "./relay-queue-service.js";
24
+ import { RuntimeSnapshotCache } from "./runtime-cache.js";
25
+ import { activeSessionPriority, activityToUnifiedJob, agentUpdateStatusToUnified, dedupeJobs, hostLoginCommand, hostLogoutCommand, isPromptTerminalActivity, normalizeMimeType, promptActivityToUnifiedJob, shouldRefreshActiveSessions, taskToUnifiedJob, uploadFileDtos, } from "./relay-runtime-helpers.js";
26
+ import { RelayDashboardService } from "./relay-dashboard-service.js";
27
+ import { capabilitiesOf } from "../channels/shared/bot-rendering.js";
28
+ import { renderSessionInfoPlain, renderSessionUsageRows } from "../channels/shared/session-format.js";
29
+ import { SessionLockStore } from "../access/session-locks.js";
30
+ import { SessionRegistry } from "../state/session-registry.js";
31
+ import { createSupportBundle } from "../support/support-bundle.js";
32
+ import { transcribeAudio } from "../artifacts/voice.js";
33
+ import { WebActivityStore, WebChatStore, } from "../web/web-state.js";
34
+ import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "../core/workspace-policy.js";
35
+ export const WEB_CONTEXT_KEY = "web:dashboard";
36
+ const ACTIVE_CODEX_DISCOVERY_LIMIT = 200;
37
+ const ACTIVE_ACTIVITY_TTL_MS = 6 * 60 * 60 * 1000;
38
+ const MAX_WEB_SESSION_PAGE_SIZE = 50;
39
+ const MAX_CHAT_HISTORY = 250;
40
+ export function relayRuntimeUpdateConnector(runtime, actor) {
41
+ runtime.dashboardService.invalidate("version");
42
+ const update = spawnSelfUpdate();
43
+ runtime.broadcastStatus(`Update started with ${update.method}. Log: ${update.logPath}`, "warn");
44
+ runtime.appendActivity({
45
+ source: "web",
46
+ status: "info",
47
+ type: "update_started",
48
+ threadId: null,
49
+ workspace: runtime.config.workspace,
50
+ actor,
51
+ detail: `${update.method}: ${update.summary}`,
52
+ });
53
+ runtime.appendAudit({
54
+ action: "command",
55
+ status: "ok",
56
+ contextKey: runtime.contextKey,
57
+ actor,
58
+ description: "update",
59
+ detail: update.summary,
60
+ });
61
+ return update;
62
+ }
63
+ export function relayRuntimeAgentUpdateJobs(runtime) {
64
+ return runtime.agentUpdates.list();
65
+ }
66
+ export function relayRuntimeStartAgentUpdate(runtime, agentId, operation = "update", actor) {
67
+ runtime.dashboardService.invalidate("adapterHealth");
68
+ runtime.dashboardService.invalidate("version");
69
+ const job = runtime.agentUpdates.start(agentId, {
70
+ piCliPath: runtime.config.piCliPath,
71
+ hermesCliPath: runtime.config.hermesCliPath,
72
+ openClawCliPath: runtime.config.openClawCliPath,
73
+ claudeCodeCliPath: runtime.config.claudeCodeCliPath,
74
+ }, operation);
75
+ if (actor) {
76
+ runtime.agentUpdateActors.set(job.id, actor);
77
+ }
78
+ runtime.agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
79
+ runtime.broadcastStatus(`${job.agentLabel} ${operation} started. Log: ${job.logPath}`, "warn");
80
+ runtime.appendActivity({
81
+ source: "web",
82
+ status: "info",
83
+ type: operation === "install" ? "agent_install_started" : "agent_update_started",
84
+ agentId,
85
+ threadId: null,
86
+ workspace: runtime.config.workspace,
87
+ actor,
88
+ detail: `${job.method}: ${job.summary}`,
89
+ });
90
+ runtime.appendAudit({
91
+ action: "command",
92
+ status: "ok",
93
+ contextKey: runtime.contextKey,
94
+ agentId,
95
+ actor,
96
+ description: `${operation} ${agentId}`,
97
+ detail: job.summary,
98
+ });
99
+ return job;
100
+ }
101
+ export function relayRuntimeAgentUpdateLog(runtime, id) {
102
+ return runtime.agentUpdates.readLog(id);
103
+ }
104
+ export function relayRuntimeDeleteAgentUpdateLog(runtime, id, actor) {
105
+ const job = runtime.agentUpdates.deleteLog(id);
106
+ runtime.appendActivity({
107
+ source: "web",
108
+ status: "info",
109
+ type: "agent_update_log_deleted",
110
+ agentId: job.agentId,
111
+ threadId: null,
112
+ workspace: runtime.config.workspace,
113
+ actor,
114
+ detail: job.logPath,
115
+ });
116
+ runtime.appendAudit({
117
+ action: "command",
118
+ status: "ok",
119
+ contextKey: runtime.contextKey,
120
+ agentId: job.agentId,
121
+ actor,
122
+ description: `delete update log ${id}`,
123
+ detail: job.logPath,
124
+ });
125
+ return job;
126
+ }
127
+ export function relayRuntimeSendAgentUpdateInput(runtime, id, input, actor) {
128
+ const job = runtime.agentUpdates.sendInput(id, input);
129
+ runtime.appendActivity({
130
+ source: "web",
131
+ status: "info",
132
+ type: "agent_update_input_sent",
133
+ agentId: job.agentId,
134
+ threadId: null,
135
+ workspace: runtime.config.workspace,
136
+ actor: actor ?? runtime.agentUpdateActors.get(id),
137
+ detail: `Input sent to ${job.agentLabel} ${job.operation}.`,
138
+ });
139
+ return job;
140
+ }
141
+ export function relayRuntimeCancelAgentUpdate(runtime, id, actor) {
142
+ const job = runtime.agentUpdates.cancel(id);
143
+ runtime.appendActivity({
144
+ source: "web",
145
+ status: "aborted",
146
+ type: "agent_update_cancel_requested",
147
+ agentId: job.agentId,
148
+ threadId: null,
149
+ workspace: runtime.config.workspace,
150
+ actor: actor ?? runtime.agentUpdateActors.get(id),
151
+ detail: `${job.agentLabel} ${job.operation} cancellation requested.`,
152
+ });
153
+ return job;
154
+ }
155
+ export function relayRuntimeTasks(runtime) {
156
+ return {
157
+ current: runtime.currentProgress ? { ...runtime.currentProgress, tools: [...runtime.currentProgress.tools] } : null,
158
+ external: runtime.externalActivityMonitor.task(),
159
+ queue: runtime.queue(),
160
+ queuePaused: runtime.queuePaused(),
161
+ recent: runtime.activity({ limit: 20 }),
162
+ };
163
+ }
164
+ export async function relayRuntimeJobs(runtime) {
165
+ const jobs = [];
166
+ const current = runtime.currentProgress;
167
+ if (current) {
168
+ jobs.push(taskToUnifiedJob("web:current", "web-turn", "Current WebUI turn", current, {
169
+ canCancel: current.status === "running",
170
+ canRetry: false,
171
+ canReadLog: false,
172
+ }));
173
+ }
174
+ const external = runtime.externalActivityMonitor.task();
175
+ if (external) {
176
+ jobs.push(taskToUnifiedJob(`external:${external.agentId ?? "agent"}:${external.threadId ?? "pending"}`, "external-turn", "External CLI turn", external, {
177
+ canCancel: false,
178
+ canRetry: false,
179
+ canReadLog: false,
180
+ }));
181
+ }
182
+ for (const item of runtime.queueService.rawList()) {
183
+ const createdAt = new Date(item.createdAt).toISOString();
184
+ jobs.push({
185
+ id: `queue:${item.id}`,
186
+ kind: "queued-prompt",
187
+ title: `Queued prompt ${item.id}`,
188
+ status: "queued",
189
+ source: "web",
190
+ threadId: null,
191
+ workspace: runtime.config.workspace,
192
+ owner: item.activityActor,
193
+ startedAt: createdAt,
194
+ updatedAt: createdAt,
195
+ summary: item.description,
196
+ queueId: item.id,
197
+ logTail: item.lastError,
198
+ canCancel: true,
199
+ canRetry: true,
200
+ canReadLog: true,
201
+ });
202
+ }
203
+ for (const job of runtime.agentUpdates.list()) {
204
+ jobs.push({
205
+ id: `agent-update:${job.id}`,
206
+ kind: "agent-update",
207
+ title: `${job.agentLabel} ${job.operation}`,
208
+ status: agentUpdateStatusToUnified(job.status),
209
+ source: "web",
210
+ agentId: job.agentId,
211
+ agentLabel: job.agentLabel,
212
+ threadId: null,
213
+ workspace: runtime.config.workspace,
214
+ owner: runtime.agentUpdateActors.get(job.id),
215
+ startedAt: job.startedAt,
216
+ updatedAt: job.updatedAt,
217
+ finishedAt: job.finishedAt,
218
+ summary: job.error || job.summary,
219
+ logPath: job.logPath,
220
+ logTail: job.outputTail,
221
+ updateJobId: job.id,
222
+ canCancel: job.status === "running",
223
+ canRetry: job.status !== "running",
224
+ canReadLog: true,
225
+ });
226
+ }
227
+ for (const event of runtime.activity({ limit: 100 })) {
228
+ if (event.type === "diagnostics_bundle_exported") {
229
+ jobs.push(activityToUnifiedJob(event, "support-bundle", "Diagnostics support bundle", {
230
+ canCancel: false,
231
+ canRetry: true,
232
+ canReadLog: Boolean(event.detail),
233
+ }));
234
+ }
235
+ else if (event.type === "update_started") {
236
+ jobs.push(activityToUnifiedJob(event, "connector-update", "NordRelay update", {
237
+ canCancel: false,
238
+ canRetry: true,
239
+ canReadLog: Boolean(event.detail),
240
+ }));
241
+ }
242
+ else if (event.category === "prompt" && event.type.startsWith("prompt_")) {
243
+ jobs.push(promptActivityToUnifiedJob(event));
244
+ }
245
+ }
246
+ const liveJobs = dedupeJobs(jobs);
247
+ const storedJobs = runtime.jobStore.upsertMany(liveJobs);
248
+ return {
249
+ jobs: dedupeJobs([...liveJobs, ...storedJobs]).sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt)),
250
+ updatedAt: new Date().toISOString(),
251
+ };
252
+ }
253
+ export async function relayRuntimeJobLog(runtime, id) {
254
+ if (id.startsWith("agent-update:")) {
255
+ const updateId = id.slice("agent-update:".length);
256
+ const log = runtime.agentUpdates.readLog(updateId);
257
+ return { job: (await runtime.jobs()).jobs.find((job) => job.id === id) ?? null, plain: log.plain };
258
+ }
259
+ if (id.startsWith("queue:")) {
260
+ const queueId = id.slice("queue:".length);
261
+ const item = runtime.queueService.rawList().find((candidate) => candidate.id === queueId);
262
+ return {
263
+ job: (await runtime.jobs()).jobs.find((job) => job.id === id) ?? null,
264
+ plain: item ? [
265
+ `Queued prompt: ${item.id}`,
266
+ `Created: ${new Date(item.createdAt).toISOString()}`,
267
+ `Attempts: ${item.attempts ?? 0}`,
268
+ `Description: ${item.description}`,
269
+ item.lastError ? `Last error: ${item.lastError}` : "",
270
+ ].filter(Boolean).join("\n") : "Queued prompt not found.",
271
+ };
272
+ }
273
+ const job = (await runtime.jobs()).jobs.find((candidate) => candidate.id === id) ?? null;
274
+ return { job, plain: job?.logTail || job?.logPath || job?.summary || runtime.jobStore.get(id)?.summary || "No log available for this job." };
275
+ }
276
+ export async function relayRuntimeJobAction(runtime, id, action, actor) {
277
+ if (id === "web:current" && action === "cancel") {
278
+ await runtime.abort(actor);
279
+ return runtime.jobs();
280
+ }
281
+ if (id.startsWith("queue:")) {
282
+ const queueId = id.slice("queue:".length);
283
+ runtime.queueService.apply(action === "cancel" ? "cancel" : "run", queueId);
284
+ runtime.jobStore.patch(id, {
285
+ status: action === "cancel" ? "aborted" : "queued",
286
+ summary: action === "cancel" ? `Cancelled queued prompt ${queueId}.` : `Queued prompt ${queueId} moved to the front.`,
287
+ canCancel: action !== "cancel",
288
+ canRetry: action === "cancel",
289
+ finishedAt: action === "cancel" ? new Date().toISOString() : undefined,
290
+ });
291
+ runtime.broadcast({ type: "queue_update", queue: runtime.queue(), paused: runtime.queuePaused() });
292
+ runtime.appendActivity({
293
+ source: "web",
294
+ status: action === "cancel" ? "aborted" : "queued",
295
+ type: action === "cancel" ? "job_cancelled" : "job_retried",
296
+ threadId: null,
297
+ workspace: runtime.config.workspace,
298
+ actor,
299
+ detail: `queue:${queueId}`,
300
+ });
301
+ if (action === "retry") {
302
+ void runtime.drainQueue();
303
+ }
304
+ return runtime.jobs();
305
+ }
306
+ if (id.startsWith("agent-update:")) {
307
+ const updateId = id.slice("agent-update:".length);
308
+ const current = runtime.agentUpdates.get(updateId);
309
+ if (!current) {
310
+ throw new Error("Unknown agent update job.");
311
+ }
312
+ if (action === "cancel") {
313
+ runtime.cancelAgentUpdate(updateId, actor);
314
+ }
315
+ else {
316
+ runtime.startAgentUpdate(current.agentId, current.operation, actor);
317
+ }
318
+ return runtime.jobs();
319
+ }
320
+ if (id.startsWith("support-bundle:") && action === "retry") {
321
+ await runtime.supportBundle(actor);
322
+ return runtime.jobs();
323
+ }
324
+ if (id.startsWith("connector-update:") && action === "retry") {
325
+ runtime.updateConnector(actor);
326
+ return runtime.jobs();
327
+ }
328
+ throw new Error(`Unsupported job action: ${action} ${id}`);
329
+ }
330
+ export function relayRuntimeRecordAgentUpdateLifecycle(runtime, job) {
331
+ const previous = runtime.agentUpdateStates.get(job.id);
332
+ const actor = runtime.agentUpdateActors.get(job.id);
333
+ if (job.needsInput && !previous?.needsInput) {
334
+ runtime.appendActivity({
335
+ source: "web",
336
+ status: "info",
337
+ type: "agent_update_input_required",
338
+ agentId: job.agentId,
339
+ threadId: null,
340
+ workspace: runtime.config.workspace,
341
+ actor,
342
+ detail: `${job.agentLabel} ${job.operation} may require input.`,
343
+ });
344
+ }
345
+ if (job.status !== "running" && previous?.status === "running") {
346
+ runtime.appendActivity({
347
+ source: "web",
348
+ status: job.status === "completed" ? "completed" : job.status === "cancelled" ? "aborted" : "failed",
349
+ type: job.operation === "install" ? `agent_install_${job.status}` : `agent_update_${job.status}`,
350
+ agentId: job.agentId,
351
+ threadId: null,
352
+ workspace: runtime.config.workspace,
353
+ actor,
354
+ detail: job.error ?? `${job.agentLabel} ${job.operation} ${job.status}.`,
355
+ durationMs: Math.max(0, Date.parse(job.finishedAt ?? job.updatedAt) - Date.parse(job.startedAt)),
356
+ });
357
+ runtime.agentUpdateActors.delete(job.id);
358
+ }
359
+ runtime.agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
360
+ }