@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
@@ -1,7 +1,8 @@
1
- import {} from "./agent.js";
2
- import { getExternalSnapshotForSession } from "./agent-activity.js";
3
- import { friendlyErrorText } from "./error-messages.js";
4
- import {} from "./web-state.js";
1
+ import {} from "../agents/shared/agent.js";
2
+ import { getExternalSnapshotForSession } from "../agents/shared/agent-activity.js";
3
+ import { renderExternalMirrorEvent, renderExternalMirrorStatus, trimLine, } from "../channels/shared/bot-rendering.js";
4
+ import { friendlyErrorText } from "../core/error-messages.js";
5
+ import {} from "../web/web-state.js";
5
6
  const CLI_ACTIVITY_ACTOR = {
6
7
  channel: "cli",
7
8
  label: "CLI",
@@ -16,6 +17,9 @@ export class RelayExternalActivityMonitor {
16
17
  snapshot() {
17
18
  return this.mirror ? { ...this.mirror } : null;
18
19
  }
20
+ reset() {
21
+ this.mirror = null;
22
+ }
19
23
  task() {
20
24
  if (!this.mirror) {
21
25
  return null;
@@ -81,7 +85,7 @@ export class RelayExternalActivityMonitor {
81
85
  startedAt: snapshot.activity.startedAt?.toISOString() ?? null,
82
86
  };
83
87
  if (snapshot.activity.active) {
84
- this.startExternalTurn(snapshot, info);
88
+ await this.startExternalTurn(snapshot, info);
85
89
  }
86
90
  return;
87
91
  }
@@ -91,36 +95,51 @@ export class RelayExternalActivityMonitor {
91
95
  mirror.turnId = snapshot.activity.turnId;
92
96
  mirror.startedAt = snapshot.activity.startedAt?.toISOString() ?? null;
93
97
  mirror.latestAgentLine = undefined;
94
- this.startExternalTurn(snapshot, info);
98
+ mirror.latestStatusAt = undefined;
99
+ mirror.latestMirroredEventLine = undefined;
100
+ await this.startExternalTurn(snapshot, info);
101
+ }
102
+ const mirrorMode = this.options.mirrorMode();
103
+ const newEvents = snapshot.events.filter((event) => event.lineNumber > mirror.lastLine);
104
+ this.broadcastExternalEvents(snapshot, newEvents, info, mirrorMode === "full");
105
+ if (mirrorMode === "full") {
106
+ await this.appendExternalEventMessages(snapshot, newEvents, mirror);
95
107
  }
96
- this.broadcastExternalEvents(snapshot, snapshot.events.filter((event) => event.lineNumber > mirror.lastLine), info);
97
108
  mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
98
109
  mirror.latestStatus = externalStatusLine(snapshot, this.options.queueLength());
99
- this.options.broadcastStatus(mirror.latestStatus, "info");
110
+ if (mirrorMode === "status" || mirrorMode === "full") {
111
+ await this.updateExternalStatusMessage(snapshot, mirror);
112
+ }
113
+ if (mirrorMode !== "off") {
114
+ this.options.broadcastStatus(mirror.latestStatus, "info");
115
+ }
100
116
  return;
101
117
  }
102
118
  const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
103
119
  if (terminalEvent && terminalEvent.lineNumber > mirror.lastLine) {
120
+ const mirrorMode = this.options.mirrorMode();
104
121
  const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
105
122
  const finalText = finalAgent?.text ?? snapshot.latestAgentMessage;
106
123
  const finalLine = finalAgent?.lineNumber ?? snapshot.lineCount;
107
- if (finalText && finalLine !== mirror.latestAgentLine) {
108
- this.options.chatStore.append({
124
+ if ((mirrorMode === "final" || mirrorMode === "full") && finalText && finalLine !== mirror.latestAgentLine) {
125
+ this.options.chatStore.appendWithResult({
109
126
  threadId: snapshot.threadId,
110
127
  role: "agent",
111
128
  text: finalText,
112
129
  source: "cli",
113
130
  turnId: terminalEvent.turnId ?? undefined,
131
+ key: externalMessageKey("final", snapshot, terminalEvent.lineNumber),
114
132
  });
115
- this.options.broadcast({ type: "text_delta", id: terminalEvent.turnId ?? "cli", delta: finalText });
116
133
  mirror.latestAgentLine = finalLine;
117
134
  }
118
135
  const externalStartedAt = mirror.startedAt ? new Date(mirror.startedAt) : snapshot.activity.startedAt;
119
- this.options.broadcast({
120
- type: "turn_complete",
121
- id: terminalEvent.turnId ?? "cli",
122
- at: terminalEvent.timestamp?.toISOString() ?? new Date().toISOString(),
123
- });
136
+ if (mirrorMode !== "off") {
137
+ this.options.broadcast({
138
+ type: "turn_complete",
139
+ id: terminalEvent.turnId ?? "cli",
140
+ at: terminalEvent.timestamp?.toISOString() ?? new Date().toISOString(),
141
+ });
142
+ }
124
143
  this.options.appendActivity({
125
144
  source: "cli",
126
145
  status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
@@ -137,29 +156,35 @@ export class RelayExternalActivityMonitor {
137
156
  await this.options.persistWorkspaceArtifactsForTurn(info.workspace, terminalEvent.turnId, externalStartedAt);
138
157
  }
139
158
  mirror.latestStatus = `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`;
140
- this.options.broadcastStatus(`${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`, terminalEvent.status === "failed" ? "error" : terminalEvent.status === "aborted" ? "warn" : "info");
141
- this.options.broadcast({ type: "chat_history", messages: await this.options.chatHistory() });
159
+ if (mirrorMode === "status" || mirrorMode === "full") {
160
+ await this.updateExternalStatusMessage(snapshot, mirror, mirror.latestStatus);
161
+ }
162
+ if (mirrorMode !== "off") {
163
+ this.options.broadcastStatus(`${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`, terminalEvent.status === "failed" ? "error" : terminalEvent.status === "aborted" ? "warn" : "info");
164
+ await this.broadcastChatHistory();
165
+ }
142
166
  await this.options.drainQueue();
143
167
  }
144
168
  mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
145
169
  }
146
- startExternalTurn(snapshot, info) {
170
+ async startExternalTurn(snapshot, info) {
147
171
  const prompt = snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`;
148
- this.options.chatStore.append({
149
- threadId: snapshot.threadId,
150
- role: "user",
151
- text: prompt,
152
- source: "cli",
153
- turnId: snapshot.activity.turnId ?? undefined,
154
- timestamp: snapshot.activity.startedAt?.toISOString(),
155
- });
156
- this.options.broadcast({
157
- type: "turn_start",
158
- id: snapshot.activity.turnId ?? "cli",
159
- prompt,
160
- at: snapshot.activity.startedAt?.toISOString() ?? new Date().toISOString(),
161
- source: "cli",
162
- });
172
+ const mode = this.options.mirrorMode();
173
+ if (mode === "final" || mode === "full") {
174
+ this.options.chatStore.appendWithResult({
175
+ threadId: snapshot.threadId,
176
+ role: "system",
177
+ text: `Working on ${trimLine(prompt, 500)}`,
178
+ source: "cli",
179
+ turnId: snapshot.activity.turnId ?? undefined,
180
+ timestamp: snapshot.activity.startedAt?.toISOString(),
181
+ key: externalMessageKey("working", snapshot),
182
+ });
183
+ await this.broadcastChatHistory();
184
+ }
185
+ if ((mode === "status" || mode === "full") && this.mirror) {
186
+ await this.updateExternalStatusMessage(snapshot, this.mirror);
187
+ }
163
188
  this.options.appendActivity({
164
189
  source: "cli",
165
190
  status: "running",
@@ -172,15 +197,17 @@ export class RelayExternalActivityMonitor {
172
197
  detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
173
198
  });
174
199
  }
175
- broadcastExternalEvents(snapshot, events, info) {
200
+ broadcastExternalEvents(snapshot, events, info, broadcastTools) {
176
201
  for (const event of events) {
177
202
  if (event.kind === "tool" && event.status === "started") {
178
- this.options.broadcast({
179
- type: "tool_start",
180
- id: snapshot.activity.turnId ?? "cli",
181
- toolCallId: `cli-${event.lineNumber}`,
182
- toolName: event.toolName ?? "tool",
183
- });
203
+ if (broadcastTools) {
204
+ this.options.broadcast({
205
+ type: "tool_start",
206
+ id: snapshot.activity.turnId ?? "cli",
207
+ toolCallId: `cli-${event.lineNumber}`,
208
+ toolName: event.toolName ?? "tool",
209
+ });
210
+ }
184
211
  this.options.appendActivity({
185
212
  source: "cli",
186
213
  status: "running",
@@ -193,12 +220,14 @@ export class RelayExternalActivityMonitor {
193
220
  });
194
221
  }
195
222
  if (event.kind === "tool" && event.status === "finished") {
196
- this.options.broadcast({
197
- type: "tool_end",
198
- id: snapshot.activity.turnId ?? "cli",
199
- toolCallId: `cli-${event.lineNumber}`,
200
- isError: false,
201
- });
223
+ if (broadcastTools) {
224
+ this.options.broadcast({
225
+ type: "tool_end",
226
+ id: snapshot.activity.turnId ?? "cli",
227
+ toolCallId: `cli-${event.lineNumber}`,
228
+ isError: false,
229
+ });
230
+ }
202
231
  this.options.appendActivity({
203
232
  source: "cli",
204
233
  status: "completed",
@@ -211,12 +240,14 @@ export class RelayExternalActivityMonitor {
211
240
  });
212
241
  }
213
242
  if (event.kind === "tool" && event.status === "failed") {
214
- this.options.broadcast({
215
- type: "tool_end",
216
- id: snapshot.activity.turnId ?? "cli",
217
- toolCallId: `cli-${event.lineNumber}`,
218
- isError: true,
219
- });
243
+ if (broadcastTools) {
244
+ this.options.broadcast({
245
+ type: "tool_end",
246
+ id: snapshot.activity.turnId ?? "cli",
247
+ toolCallId: `cli-${event.lineNumber}`,
248
+ isError: true,
249
+ });
250
+ }
220
251
  this.options.appendActivity({
221
252
  source: "cli",
222
253
  status: "failed",
@@ -230,6 +261,62 @@ export class RelayExternalActivityMonitor {
230
261
  }
231
262
  }
232
263
  }
264
+ async appendExternalEventMessages(snapshot, events, mirror) {
265
+ let changed = false;
266
+ for (const event of events) {
267
+ if (event.lineNumber <= (mirror.latestMirroredEventLine ?? mirror.lastLine)) {
268
+ continue;
269
+ }
270
+ const rendered = renderExternalMirrorEvent(event);
271
+ if (!rendered) {
272
+ continue;
273
+ }
274
+ const stored = this.options.chatStore.appendWithResult({
275
+ threadId: snapshot.threadId,
276
+ role: event.kind === "tool" ? "tool" : "system",
277
+ text: rendered.plain,
278
+ source: "cli",
279
+ turnId: event.turnId ?? snapshot.activity.turnId ?? undefined,
280
+ timestamp: event.timestamp?.toISOString(),
281
+ key: externalMessageKey("event", snapshot, event.lineNumber),
282
+ });
283
+ changed = changed || stored.inserted;
284
+ mirror.latestMirroredEventLine = event.lineNumber;
285
+ }
286
+ if (changed) {
287
+ await this.broadcastChatHistory();
288
+ }
289
+ }
290
+ async updateExternalStatusMessage(snapshot, mirror, text) {
291
+ const now = Date.now();
292
+ const minInterval = this.options.mirrorMinUpdateMs();
293
+ if (!text && mirror.latestStatusAt && now - mirror.latestStatusAt < minInterval) {
294
+ return;
295
+ }
296
+ this.options.chatStore.upsertByKey({
297
+ threadId: snapshot.threadId,
298
+ role: "system",
299
+ text: text ?? renderExternalMirrorStatus(snapshot, this.options.queueLength()).plain,
300
+ source: "cli",
301
+ turnId: snapshot.activity.turnId ?? undefined,
302
+ key: externalMessageKey("status", snapshot),
303
+ });
304
+ mirror.latestStatusAt = now;
305
+ await this.broadcastChatHistory();
306
+ }
307
+ async broadcastChatHistory() {
308
+ this.options.broadcast({ type: "chat_history", messages: await this.options.chatHistory() });
309
+ }
310
+ }
311
+ function externalMessageKey(kind, snapshot, lineNumber) {
312
+ return [
313
+ "external",
314
+ kind,
315
+ snapshot.agentId,
316
+ snapshot.threadId,
317
+ snapshot.activity.turnId ?? "turn",
318
+ lineNumber ?? "",
319
+ ].join(":");
233
320
  }
234
321
  function externalStatusLine(snapshot, queueLength) {
235
322
  const elapsed = snapshot.activity.startedAt
@@ -0,0 +1,387 @@
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 async function relayRuntimeActiveSessions(runtime) {
41
+ const sessions = new Map();
42
+ const knownContexts = runtime.listKnownContextMetadata();
43
+ const preferences = new BotPreferencesStore(runtime.config.workspace, runtime.config.stateBackend);
44
+ const addActiveSession = (session) => {
45
+ const key = runtime.activeSessionKey(session);
46
+ const existing = sessions.get(key);
47
+ sessions.set(key, runtime.preferredActiveSession(existing, session));
48
+ };
49
+ if (runtime.currentProgress?.status === "running") {
50
+ addActiveSession({
51
+ ...runtime.currentProgress,
52
+ contextKey: runtime.contextKey,
53
+ sourceContextKey: runtime.contextKey,
54
+ source: "web",
55
+ status: "running",
56
+ queueLength: runtime.queueService.length(),
57
+ queuePaused: runtime.queueService.isPaused(),
58
+ });
59
+ }
60
+ for (const active of runtime.discoverRunningConnectorSessions()) {
61
+ addActiveSession(active);
62
+ }
63
+ for (const active of runtime.discoverActiveCodexSessions(knownContexts, preferences)) {
64
+ addActiveSession(active);
65
+ }
66
+ for (const meta of knownContexts) {
67
+ if (meta.contextKey === runtime.contextKey && runtime.currentProgress?.status === "running") {
68
+ continue;
69
+ }
70
+ const active = runtime.externalActiveSession(meta, knownContexts, preferences);
71
+ if (active) {
72
+ addActiveSession(active);
73
+ }
74
+ }
75
+ return {
76
+ sessions: [...sessions.values()].sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt)),
77
+ updatedAt: new Date().toISOString(),
78
+ };
79
+ }
80
+ export async function relayRuntimeGetSession(runtime, deferThreadStart) {
81
+ return runtime.registry.getOrCreate(runtime.contextKey, { deferThreadStart });
82
+ }
83
+ export function relayRuntimeListKnownContextMetadata(runtime) {
84
+ const contexts = new Map();
85
+ const add = (meta) => {
86
+ if (meta?.contextKey) {
87
+ contexts.set(meta.contextKey, meta);
88
+ }
89
+ };
90
+ for (const meta of runtime.registry.listContexts()) {
91
+ add(meta);
92
+ }
93
+ const sharedRegistry = new SessionRegistry(runtime.config);
94
+ try {
95
+ for (const meta of sharedRegistry.listContexts()) {
96
+ add(meta);
97
+ }
98
+ }
99
+ finally {
100
+ sharedRegistry.disposeAll();
101
+ }
102
+ const current = runtime.registry.get(runtime.contextKey)?.getInfo();
103
+ if (current) {
104
+ add({
105
+ contextKey: runtime.contextKey,
106
+ agentId: current.agentId,
107
+ threadId: current.threadId,
108
+ workspace: current.workspace,
109
+ model: current.model,
110
+ reasoningEffort: current.reasoningEffort,
111
+ launchProfileId: current.nextLaunchProfileId ?? current.launchProfileId,
112
+ sessionPath: current.sessionPath,
113
+ updatedAt: Date.now(),
114
+ });
115
+ }
116
+ return [...contexts.values()];
117
+ }
118
+ export function relayRuntimeDiscoverRunningConnectorSessions(runtime) {
119
+ const active = [];
120
+ const terminal = new Set();
121
+ const now = Date.now();
122
+ for (const event of runtime.activityStore.list({ limit: 500 })) {
123
+ if (!event.threadId || !event.agentId || !event.contextKey) {
124
+ continue;
125
+ }
126
+ const key = `${event.source}:${event.contextKey}:${event.agentId}:${event.threadId}`;
127
+ if (isPromptTerminalActivity(event)) {
128
+ terminal.add(key);
129
+ continue;
130
+ }
131
+ if (event.type !== "prompt_started" || event.status !== "running" || event.source === "cli") {
132
+ continue;
133
+ }
134
+ if (terminal.has(key)) {
135
+ continue;
136
+ }
137
+ const startedMs = Date.parse(event.timestamp);
138
+ if (!Number.isFinite(startedMs) || now - startedMs > ACTIVE_ACTIVITY_TTL_MS) {
139
+ continue;
140
+ }
141
+ active.push({
142
+ id: `${event.contextKey}:${event.id}`,
143
+ contextKey: event.contextKey,
144
+ sourceContextKey: event.contextKey,
145
+ source: event.source,
146
+ status: "running",
147
+ agentId: event.agentId,
148
+ agentLabel: event.agentId ? agentLabel(event.agentId) : undefined,
149
+ threadId: event.threadId,
150
+ workspace: event.workspace,
151
+ prompt: event.prompt,
152
+ startedAt: event.timestamp,
153
+ updatedAt: event.timestamp,
154
+ durationMs: Math.max(0, now - startedMs),
155
+ queueLength: runtime.promptStore.list(event.contextKey).length,
156
+ queuePaused: runtime.promptStore.isPaused(event.contextKey),
157
+ detail: event.actor?.label ? `Started by ${event.actor.label}` : undefined,
158
+ });
159
+ }
160
+ return active;
161
+ }
162
+ export function relayRuntimeDiscoverActiveCodexSessions(runtime, knownContexts, preferences) {
163
+ if (!runtime.config.codexEnabled || !enabledAgents(runtime.config).includes("codex")) {
164
+ return [];
165
+ }
166
+ const capabilities = runtime.capabilitiesForAgent("codex");
167
+ if (!capabilities.externalActivity) {
168
+ return [];
169
+ }
170
+ const active = [];
171
+ const nowMs = Date.now();
172
+ const staleAfterMs = runtime.config.codexExternalBusyStaleMs;
173
+ for (const thread of listCodexThreads(ACTIVE_CODEX_DISCOVERY_LIMIT)) {
174
+ if (staleAfterMs > 0 && nowMs - thread.updatedAt.getTime() > staleAfterMs) {
175
+ continue;
176
+ }
177
+ const meta = {
178
+ contextKey: `cli:codex:${thread.id}`,
179
+ agentId: "codex",
180
+ threadId: thread.id,
181
+ workspace: thread.cwd,
182
+ model: thread.model ?? undefined,
183
+ reasoningEffort: thread.reasoningEffort ?? undefined,
184
+ updatedAt: thread.updatedAt.getTime(),
185
+ };
186
+ const session = runtime.externalActiveSession(meta, knownContexts, preferences);
187
+ if (session) {
188
+ active.push(session);
189
+ }
190
+ }
191
+ return active;
192
+ }
193
+ export function relayRuntimeExternalActiveSession(runtime, meta, knownContexts, preferences) {
194
+ if (!meta.threadId) {
195
+ return null;
196
+ }
197
+ const agentId = isAgentId(meta.agentId) ? meta.agentId : runtime.config.defaultAgent;
198
+ if (!enabledAgents(runtime.config).includes(agentId)) {
199
+ return null;
200
+ }
201
+ const capabilities = runtime.capabilitiesForAgent(agentId);
202
+ if (!capabilities.externalActivity) {
203
+ return null;
204
+ }
205
+ if (agentId === "codex" &&
206
+ meta.updatedAt &&
207
+ runtime.config.codexExternalBusyStaleMs > 0 &&
208
+ Date.now() - meta.updatedAt > runtime.config.codexExternalBusyStaleMs) {
209
+ return null;
210
+ }
211
+ const snapshot = getExternalSnapshotForSession(runtime.sessionStubForMetadata(meta, agentId, capabilities), runtime.config, {
212
+ maxEvents: 8,
213
+ });
214
+ if (!snapshot?.activity.active) {
215
+ return null;
216
+ }
217
+ const startedAt = snapshot.activity.startedAt?.toISOString() ?? new Date().toISOString();
218
+ const updatedAt = snapshot.activity.updatedAt?.toISOString() ?? new Date().toISOString();
219
+ const startedMs = Date.parse(startedAt);
220
+ const sourceContextKey = `cli:${snapshot.agentId}:${snapshot.threadId}`;
221
+ const mirrorChannels = runtime.mirrorRegistry.activeMirrorsForThread(snapshot.agentId, snapshot.threadId, knownContexts, preferences);
222
+ const queueLength = runtime.mirrorRegistry.queueLengthForExternalSource(sourceContextKey, mirrorChannels);
223
+ const mirrorDetail = mirrorChannels.length > 0
224
+ ? `Mirroring: ${mirrorChannels.map((mirror) => `${mirror.source} ${mirror.mode}`).join(", ")}`
225
+ : "Mirroring: none";
226
+ return {
227
+ id: `${sourceContextKey}:${snapshot.activity.turnId ?? snapshot.threadId}`,
228
+ contextKey: sourceContextKey,
229
+ sourceContextKey,
230
+ source: "cli",
231
+ status: "external",
232
+ agentId: snapshot.agentId,
233
+ agentLabel: snapshot.agentLabel,
234
+ threadId: snapshot.threadId,
235
+ workspace: meta.workspace,
236
+ prompt: snapshot.latestUserMessage ?? undefined,
237
+ currentTool: snapshot.latestToolName ?? undefined,
238
+ lastTool: snapshot.latestToolName ?? undefined,
239
+ startedAt,
240
+ updatedAt,
241
+ durationMs: Number.isFinite(startedMs) ? Math.max(0, Date.now() - startedMs) : 0,
242
+ queueLength,
243
+ queuePaused: runtime.mirrorRegistry.queuePausedForExternalSource(sourceContextKey, mirrorChannels),
244
+ mirrorChannels,
245
+ detail: `${mirrorDetail} | ${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
246
+ };
247
+ }
248
+ export function relayRuntimeSessionStubForMetadata(runtime, meta, agentId, capabilities) {
249
+ const info = {
250
+ agentId,
251
+ agentLabel: agentLabel(agentId),
252
+ threadId: meta.threadId,
253
+ workspace: meta.workspace,
254
+ model: meta.model,
255
+ reasoningEffort: meta.reasoningEffort,
256
+ launchProfileId: meta.launchProfileId ?? runtime.config.defaultLaunchProfileId,
257
+ launchProfileLabel: meta.launchProfileId ?? runtime.config.defaultLaunchProfileId,
258
+ launchProfileBehavior: "-",
259
+ sandboxMode: "-",
260
+ approvalPolicy: "-",
261
+ fastMode: false,
262
+ unsafeLaunch: false,
263
+ sessionPath: meta.sessionPath,
264
+ capabilities,
265
+ };
266
+ return {
267
+ getInfo: () => info,
268
+ getActiveThreadId: () => meta.threadId,
269
+ };
270
+ }
271
+ export function relayRuntimeCapabilitiesForAgent(runtime, agentId) {
272
+ return listAgentAdapterDescriptors().find((descriptor) => descriptor.id === agentId)?.capabilities ?? CODEX_AGENT_CAPABILITIES;
273
+ }
274
+ export function relayRuntimeActiveSessionKey(runtime, session) {
275
+ return session.threadId ? `${session.agentId ?? "unknown"}:${session.threadId}` : session.id;
276
+ }
277
+ export function relayRuntimePreferredActiveSession(runtime, existing, candidate) {
278
+ if (!existing) {
279
+ return candidate;
280
+ }
281
+ const existingPriority = activeSessionPriority(existing);
282
+ const candidatePriority = activeSessionPriority(candidate);
283
+ if (candidatePriority !== existingPriority) {
284
+ return candidatePriority > existingPriority ? candidate : existing;
285
+ }
286
+ return Date.parse(candidate.updatedAt) >= Date.parse(existing.updatedAt) ? candidate : existing;
287
+ }
288
+ export function relayRuntimeRecordActivity(runtime, input) {
289
+ return runtime.appendActivity(input);
290
+ }
291
+ export function relayRuntimeAppendActivity(runtime, input) {
292
+ const event = runtime.activityStore.append(runtime.enrichActivityInput(input));
293
+ runtime.broadcast({ type: "activity_update", events: runtime.activity({ limit: 50 }) });
294
+ return event;
295
+ }
296
+ export function relayRuntimeEnrichActivityInput(runtime, input) {
297
+ return runtime.enrichActivityFields(input);
298
+ }
299
+ export function relayRuntimeEnrichActivityEvent(runtime, event, info) {
300
+ return runtime.enrichActivityFields(event, info);
301
+ }
302
+ export function relayRuntimeEnrichActivityFields(runtime, event, info) {
303
+ if (!info) {
304
+ return !event.threadId && !event.workspace ? { ...event, workspace: runtime.config.workspace } : event;
305
+ }
306
+ if (event.threadId && info.threadId && event.threadId === info.threadId) {
307
+ return { ...event, workspace: event.workspace ?? info.workspace, agentId: event.agentId ?? info.agentId };
308
+ }
309
+ if (!event.threadId && !event.workspace) {
310
+ return { ...event, workspace: runtime.config.workspace };
311
+ }
312
+ return event;
313
+ }
314
+ export function relayRuntimeAppendAudit(runtime, input) {
315
+ return runtime.auditStore.append({ ...input, channelId: "web" });
316
+ }
317
+ export function relayRuntimeUpdateCurrentProgress(runtime, patch = {}) {
318
+ if (!runtime.currentProgress) {
319
+ return;
320
+ }
321
+ if ("currentTool" in patch) {
322
+ runtime.currentProgress.currentTool = patch.currentTool;
323
+ const { currentTool: _currentTool, ...rest } = patch;
324
+ Object.assign(runtime.currentProgress, rest);
325
+ }
326
+ else {
327
+ Object.assign(runtime.currentProgress, patch);
328
+ }
329
+ runtime.currentProgress.durationMs = Date.now() - runtime.currentTurnStartedAt;
330
+ runtime.currentProgress.updatedAt = new Date().toISOString();
331
+ }
332
+ export function relayRuntimeAddCurrentTool(runtime, toolName) {
333
+ if (!runtime.currentProgress) {
334
+ return;
335
+ }
336
+ const existing = runtime.currentProgress.tools.find((tool) => tool.name === toolName);
337
+ if (existing) {
338
+ existing.count += 1;
339
+ }
340
+ else {
341
+ runtime.currentProgress.tools.push({ name: toolName, count: 1 });
342
+ }
343
+ runtime.updateCurrentProgress({ currentTool: toolName, lastTool: toolName });
344
+ }
345
+ export function relayRuntimeBroadcastQueue(runtime) {
346
+ runtime.broadcast({ type: "queue_update", queue: runtime.queue(), paused: runtime.queuePaused() });
347
+ }
348
+ export function relayRuntimeBroadcastStatus(runtime, message, level = "info") {
349
+ runtime.broadcast({ type: "status", message, level, at: new Date().toISOString() });
350
+ }
351
+ export function relayRuntimeBroadcast(runtime, event) {
352
+ for (const subscriber of runtime.subscribers) {
353
+ try {
354
+ subscriber(event);
355
+ }
356
+ catch {
357
+ runtime.subscribers.delete(subscriber);
358
+ }
359
+ }
360
+ if (shouldRefreshActiveSessions(event)) {
361
+ runtime.scheduleActiveSessionsBroadcast();
362
+ }
363
+ }
364
+ export function relayRuntimeScheduleActiveSessionsBroadcast(runtime) {
365
+ if (runtime.activeSessionsBroadcastTimer) {
366
+ return;
367
+ }
368
+ const delayMs = Math.max(0, 1_000 - (Date.now() - runtime.activeSessionsLastBroadcastAt));
369
+ runtime.activeSessionsBroadcastTimer = setTimeout(() => {
370
+ runtime.activeSessionsBroadcastTimer = null;
371
+ runtime.activeSessionsLastBroadcastAt = Date.now();
372
+ void runtime.activeSessions()
373
+ .then((active) => runtime.broadcast({ type: "active_sessions_update", active }))
374
+ .catch(() => { });
375
+ }, delayMs);
376
+ runtime.activeSessionsBroadcastTimer.unref?.();
377
+ }
378
+ export function relayRuntimePublicInfo(runtime, session) {
379
+ const info = session.getInfo();
380
+ const agentId = info.agentId ?? "codex";
381
+ return {
382
+ ...info,
383
+ agentId,
384
+ agentLabel: info.agentLabel ?? agentLabel(agentId),
385
+ capabilities: info.capabilities ?? CODEX_AGENT_CAPABILITIES,
386
+ };
387
+ }