@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
@@ -13,9 +13,11 @@ export const DEFAULT_PEER_SCOPES = [
13
13
  "logs.read",
14
14
  ];
15
15
  export function publicPeer(record) {
16
+ const trust = peerTrust(record);
16
17
  return {
17
18
  id: record.id,
18
19
  name: record.name,
20
+ group: record.group,
19
21
  url: record.url,
20
22
  nodeId: record.nodeId,
21
23
  fingerprint: record.fingerprint,
@@ -34,12 +36,38 @@ export function publicPeer(record) {
34
36
  remoteVersion: record.remoteVersion,
35
37
  remoteStatus: record.remoteStatus,
36
38
  lastError: record.lastError,
39
+ healthHistory: record.healthHistory?.map((sample) => ({ ...sample })),
40
+ trustStatus: trust.status,
41
+ trustWarnings: trust.warnings,
42
+ effectiveAccess: {
43
+ scopes: [...record.scopes],
44
+ allowedAgents: [...record.allowedAgents],
45
+ allowedWorkspaceRoots: [...record.allowedWorkspaceRoots],
46
+ workspaceAliases: { ...record.workspaceAliases },
47
+ },
37
48
  };
38
49
  }
50
+ function peerTrust(record) {
51
+ const warnings = [];
52
+ if (!record.enabled) {
53
+ warnings.push("Peer is disabled.");
54
+ return { status: "disabled", warnings };
55
+ }
56
+ if (record.lastError) {
57
+ warnings.push(record.lastError);
58
+ return { status: "error", warnings };
59
+ }
60
+ if (record.url?.startsWith("https://") && !record.tlsFingerprint) {
61
+ warnings.push("TLS fingerprint is not pinned yet. Probe or re-pin this peer before using it over untrusted networks.");
62
+ return { status: "tls-unpinned", warnings };
63
+ }
64
+ return { status: "trusted", warnings };
65
+ }
39
66
  export function publicInvitation(record) {
40
67
  return {
41
68
  id: record.id,
42
69
  name: record.name,
70
+ group: record.group,
43
71
  expiresAt: record.expiresAt,
44
72
  createdAt: record.createdAt,
45
73
  scopes: [...record.scopes],
@@ -0,0 +1,129 @@
1
+ import { WEB_API_ROUTE_DEFINITIONS } from "../web/web-api-contract.js";
2
+ const LOCAL_ONLY_ROUTE_PATHS = new Set([
3
+ "/api/auth/me",
4
+ "/api/dashboard/logout",
5
+ "/api/permissions",
6
+ "/api/settings",
7
+ "/api/settings/wizard/test",
8
+ "/api/peers",
9
+ "/api/peers/invite",
10
+ "/api/peers/pair",
11
+ "/api/peers/probe",
12
+ "/api/peers/discover",
13
+ "/api/peers/discovery-jobs",
14
+ "/api/peers/discovery-jobs/:id",
15
+ "/api/peers/discovery-jobs/:id/cancel",
16
+ "/api/peers/discovery-jobs/:id/log",
17
+ "/api/peers/identity/backup",
18
+ "/api/peers/identity/restore",
19
+ "/api/peers/invitations/:id",
20
+ "/api/peers/:id",
21
+ "/api/peers/:id/repin",
22
+ "/api/peers/:id/rotate",
23
+ "/api/peers/:id/health",
24
+ "/api/peers/:id/proxy",
25
+ "/api/peers/:id/events",
26
+ "/api/peers/global-sessions",
27
+ "/api/users",
28
+ "/api/users/:id",
29
+ "/api/users/:id/password",
30
+ "/api/users/:id/sessions",
31
+ "/api/users/:id/sessions/:sessionId",
32
+ "/api/users/:id/telegram",
33
+ "/api/users/:id/telegram/:identityId",
34
+ "/api/users/:id/discord",
35
+ "/api/users/:id/discord/:identityId",
36
+ "/api/users/:id/slack",
37
+ "/api/users/:id/slack/:identityId",
38
+ "/api/groups",
39
+ "/api/groups/:id",
40
+ "/api/telegram-chats",
41
+ "/api/telegram-chats/:id",
42
+ "/api/discord-channels",
43
+ "/api/discord-channels/:id",
44
+ "/api/slack-channels",
45
+ "/api/slack-channels/:id",
46
+ "/api/audit",
47
+ ]);
48
+ const IMPLEMENTED_ROUTE_PATHS = new Set([
49
+ "/api/bootstrap",
50
+ "/api/health",
51
+ "/api/snapshot",
52
+ "/api/tasks",
53
+ "/api/progress",
54
+ "/api/metrics",
55
+ "/api/jobs",
56
+ "/api/trace",
57
+ "/api/jobs/:id/log",
58
+ "/api/jobs/:id/action",
59
+ "/api/active-sessions",
60
+ "/api/version",
61
+ "/api/update",
62
+ "/api/agent-updates",
63
+ "/api/agent-update",
64
+ "/api/agent-update/:id/log",
65
+ "/api/agent-update/:id/input",
66
+ "/api/agent-update/:id/cancel",
67
+ "/api/adapters/health",
68
+ "/api/adapters/conformance",
69
+ "/api/locks",
70
+ "/api/auth/status",
71
+ "/api/auth/login",
72
+ "/api/auth/logout",
73
+ "/api/control-options",
74
+ "/api/sessions",
75
+ "/api/sessions/new",
76
+ "/api/sessions/switch",
77
+ "/api/sessions/attach",
78
+ "/api/sessions/detail",
79
+ "/api/agent",
80
+ "/api/models",
81
+ "/api/session/model",
82
+ "/api/session/reasoning",
83
+ "/api/session/fast",
84
+ "/api/session/launch",
85
+ "/api/prompt",
86
+ "/api/prompt/upload",
87
+ "/api/abort",
88
+ "/api/stop",
89
+ "/api/handback",
90
+ "/api/retry",
91
+ "/api/sync",
92
+ "/api/queue",
93
+ "/api/chat/history",
94
+ "/api/chat/mirror",
95
+ "/api/activity",
96
+ "/api/artifacts",
97
+ "/api/artifacts/bulk",
98
+ "/api/artifacts/zip",
99
+ "/api/artifacts/file",
100
+ "/api/artifacts/preview",
101
+ "/api/logs",
102
+ "/api/logs/clear",
103
+ "/api/diagnostics",
104
+ "/api/diagnostics/bundle",
105
+ "/api/runtime/restart",
106
+ ]);
107
+ export function peerProxyCoverage() {
108
+ const implemented = [];
109
+ const localOnly = [];
110
+ const missing = [];
111
+ for (const route of WEB_API_ROUTE_DEFINITIONS) {
112
+ for (const method of route.methods) {
113
+ const key = { method, path: route.path };
114
+ if (IMPLEMENTED_ROUTE_PATHS.has(route.path)) {
115
+ implemented.push(key);
116
+ }
117
+ else if (LOCAL_ONLY_ROUTE_PATHS.has(route.path)) {
118
+ localOnly.push(key);
119
+ }
120
+ else {
121
+ missing.push(key);
122
+ }
123
+ }
124
+ }
125
+ return { implemented, localOnly, missing };
126
+ }
127
+ export function isPeerProxyLocalOnlyPath(path) {
128
+ return LOCAL_ONLY_ROUTE_PATHS.has(path);
129
+ }
@@ -1,7 +1,8 @@
1
1
  import { monitorEventLoopDelay } from "node:perf_hooks";
2
- import { getDiscordRateLimitMetrics } from "./discord-rate-limit.js";
3
- import { getSlackRateLimitMetrics } from "./slack-rate-limit.js";
4
- import { getTelegramRateLimitMetrics } from "./telegram-rate-limit.js";
2
+ import { getDiscordRateLimitMetrics } from "../channels/discord/discord-rate-limit.js";
3
+ import { getSlackRateLimitMetrics } from "../channels/slack/slack-rate-limit.js";
4
+ import { getTelegramRateLimitMetrics } from "../channels/telegram/telegram-rate-limit.js";
5
+ import { getWebApiPerformanceMetrics } from "../web/web-performance.js";
5
6
  const startedAt = Date.now();
6
7
  const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
7
8
  eventLoopDelay.enable();
@@ -38,6 +39,7 @@ export function buildRuntimeMetrics(input) {
38
39
  discord: getDiscordRateLimitMetrics(),
39
40
  slack: getSlackRateLimitMetrics(),
40
41
  },
42
+ web: getWebApiPerformanceMetrics(),
41
43
  };
42
44
  }
43
45
  function processMetrics() {
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { collectRecentWorkspaceArtifacts, createArtifactZipBundle, getArtifactTurnReport, listRecentArtifactReports, persistWorkspaceArtifactReport, removeArtifactTurn, totalArtifactSize, } from "./artifacts.js";
3
+ import { collectRecentWorkspaceArtifacts, createArtifactZipBundle, getArtifactTurnReport, listRecentArtifactReports, persistWorkspaceArtifactReport, removeArtifactTurn, totalArtifactSize, } from "../artifacts/artifacts.js";
4
4
  const MAX_TEXT_PREVIEW_BYTES = 256 * 1024;
5
5
  export class RelayArtifactService {
6
6
  config;
@@ -0,0 +1,63 @@
1
+ import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout } from "../agents/claude-code/claude-code-auth.js";
2
+ import { checkAuthStatus, startLogin as startCodexLogin, startLogout as startCodexLogout } from "../agents/codex/codex-auth.js";
3
+ import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "../agents/hermes/hermes-auth.js";
4
+ import { checkOpenClawAuthStatus } from "../agents/openclaw/openclaw-auth.js";
5
+ import { checkPiAuthStatus } from "../agents/pi/pi-auth.js";
6
+ export class RelayAuthService {
7
+ config;
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ async check(info) {
12
+ if (info.agentId === "pi") {
13
+ return checkPiAuthStatus(info.model);
14
+ }
15
+ if (info.agentId === "hermes") {
16
+ return checkHermesAuthStatus({
17
+ baseUrl: this.config.hermesApiBaseUrl,
18
+ apiKey: this.config.hermesApiKey,
19
+ });
20
+ }
21
+ if (info.agentId === "openclaw") {
22
+ return checkOpenClawAuthStatus({
23
+ gatewayUrl: this.config.openClawGatewayUrl,
24
+ token: this.config.openClawGatewayToken,
25
+ password: this.config.openClawGatewayPassword,
26
+ });
27
+ }
28
+ if (info.agentId === "claude-code") {
29
+ return checkClaudeCodeAuthStatus(this.config.claudeCodeCliPath);
30
+ }
31
+ return checkAuthStatus(this.config.codexApiKey);
32
+ }
33
+ async startLogin(info) {
34
+ if (info.agentId === "hermes") {
35
+ return startHermesLogin(this.config.hermesCliPath);
36
+ }
37
+ if (info.agentId === "claude-code") {
38
+ return startClaudeCodeLogin(this.config.claudeCodeCliPath);
39
+ }
40
+ if (info.agentId === "codex") {
41
+ return startCodexLogin();
42
+ }
43
+ return {
44
+ success: false,
45
+ message: `${info.agentLabel} login is not managed by NordRelay. Run the agent login flow on the host.`,
46
+ };
47
+ }
48
+ async startLogout(info) {
49
+ if (info.agentId === "hermes") {
50
+ return startHermesLogout(this.config.hermesCliPath);
51
+ }
52
+ if (info.agentId === "claude-code") {
53
+ return startClaudeCodeLogout(this.config.claudeCodeCliPath);
54
+ }
55
+ if (info.agentId === "codex") {
56
+ return startCodexLogout();
57
+ }
58
+ return {
59
+ success: false,
60
+ message: `${info.agentLabel} logout is not managed by NordRelay. Run the agent logout flow on the host.`,
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,139 @@
1
+ import { enabledAgents } from "../agents/shared/agent-factory.js";
2
+ import { listAgentAdapterDescriptors } from "../agents/shared/agent-adapter.js";
3
+ import { friendlyErrorText } from "../core/error-messages.js";
4
+ import { getAgentDiagnostics } from "../agents/shared/agent-activity.js";
5
+ import { getConnectorHealth, getVersionChecks, readConnectorState } from "../support/operations.js";
6
+ import { collectSlackDiagnostics } from "../channels/slack/slack-diagnostics.js";
7
+ import { getSlackRateLimitMetrics } from "../channels/slack/slack-rate-limit.js";
8
+ import { cliHealthForAgent, versionCheckForAgent } from "./relay-runtime-helpers.js";
9
+ export class RelayDashboardService {
10
+ options;
11
+ keys = ["version", "adapterHealth", "diagnostics"];
12
+ warmTimer;
13
+ constructor(options) {
14
+ this.options = options;
15
+ options.cache.register("version", () => this.produceVersion());
16
+ options.cache.register("adapterHealth", () => this.produceAdapterHealth());
17
+ options.cache.register("diagnostics", () => this.produceDiagnostics());
18
+ }
19
+ startBackgroundRefresh() {
20
+ this.options.cache.warm(this.keys);
21
+ const ttlMs = this.options.config.dashboardCacheTtlMs;
22
+ if (ttlMs <= 0 || this.warmTimer) {
23
+ return;
24
+ }
25
+ const intervalMs = Math.max(5_000, ttlMs);
26
+ this.warmTimer = setInterval(() => this.options.cache.warm(this.keys), intervalMs);
27
+ this.warmTimer.unref?.();
28
+ }
29
+ stopBackgroundRefresh() {
30
+ if (!this.warmTimer) {
31
+ return;
32
+ }
33
+ clearInterval(this.warmTimer);
34
+ this.warmTimer = undefined;
35
+ }
36
+ async version() {
37
+ return this.cached("version");
38
+ }
39
+ async diagnostics() {
40
+ return this.cached("diagnostics");
41
+ }
42
+ async adapterHealth() {
43
+ return this.cached("adapterHealth");
44
+ }
45
+ invalidate(key) {
46
+ this.options.cache.invalidate(key);
47
+ if (key) {
48
+ this.options.cache.warm([key]);
49
+ return;
50
+ }
51
+ this.options.cache.warm(this.keys);
52
+ }
53
+ async cached(key) {
54
+ return (await this.options.cache.get(key, this.options.config.dashboardCacheTtlMs)).value;
55
+ }
56
+ async produceVersion() {
57
+ const cliOptions = this.options.cliPathOptions();
58
+ const [health, state, versionChecks] = await Promise.all([
59
+ getConnectorHealth(cliOptions),
60
+ readConnectorState(),
61
+ getVersionChecks(cliOptions),
62
+ ]);
63
+ return {
64
+ health,
65
+ state,
66
+ versionChecks,
67
+ };
68
+ }
69
+ async produceDiagnostics() {
70
+ const cliOptions = this.options.cliPathOptions();
71
+ const [health, versionChecks, snapshot, session] = await Promise.all([
72
+ getConnectorHealth(cliOptions),
73
+ getVersionChecks(cliOptions),
74
+ this.options.snapshot(),
75
+ this.options.getSession(),
76
+ ]);
77
+ return {
78
+ health,
79
+ versionChecks,
80
+ snapshot,
81
+ runtime: {
82
+ stateBackend: this.options.config.stateBackend,
83
+ sourceWorkspace: this.options.config.workspace,
84
+ queuePaused: this.options.queuePaused(),
85
+ externalMirror: this.options.externalMirror(),
86
+ agentDiagnostics: getAgentDiagnostics(session, this.options.config),
87
+ slackDiagnostics: await collectSlackDiagnostics({
88
+ config: this.options.config,
89
+ timeoutMs: 2_500,
90
+ rateLimit: getSlackRateLimitMetrics(),
91
+ }),
92
+ },
93
+ };
94
+ }
95
+ async produceAdapterHealth() {
96
+ const cliOptions = this.options.cliPathOptions();
97
+ const [health, versions] = await Promise.all([
98
+ getConnectorHealth(cliOptions),
99
+ getVersionChecks(cliOptions),
100
+ ]);
101
+ return Promise.all(listAgentAdapterDescriptors().map(async (descriptor) => {
102
+ const enabled = enabledAgents(this.options.config).includes(descriptor.id);
103
+ const auth = descriptor.capabilities.auth && enabled
104
+ ? await this.options.authStatus(descriptor.id).catch((error) => ({
105
+ agentId: descriptor.id,
106
+ agentLabel: descriptor.label,
107
+ supported: descriptor.capabilities.auth,
108
+ authenticated: false,
109
+ detail: friendlyErrorText(error),
110
+ loginSupported: descriptor.capabilities.login,
111
+ logoutSupported: descriptor.capabilities.logout,
112
+ }))
113
+ : null;
114
+ const cli = cliHealthForAgent(descriptor.id, health);
115
+ const version = versionCheckForAgent(descriptor.id, versions);
116
+ return {
117
+ id: descriptor.id,
118
+ label: descriptor.label,
119
+ enabled,
120
+ status: descriptor.status === "available" ? (enabled ? "enabled" : "disabled") : "planned",
121
+ auth: {
122
+ supported: descriptor.capabilities.auth,
123
+ authenticated: auth ? auth.authenticated : null,
124
+ method: auth?.method,
125
+ detail: auth?.detail,
126
+ },
127
+ cli,
128
+ version: {
129
+ installed: version.installedLabel,
130
+ latest: version.latestVersion,
131
+ status: version.status,
132
+ detail: version.detail,
133
+ },
134
+ capabilities: descriptor.capabilities,
135
+ notes: descriptor.notes,
136
+ };
137
+ }));
138
+ }
139
+ }