@nordbyte/nordrelay 0.8.1 → 0.8.3

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