@nordbyte/nordrelay 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/.env.example +9 -0
  2. package/README.md +81 -1197
  3. package/dist/{access-control.js → access/access-control.js} +1 -1
  4. package/dist/{audit-log.js → access/audit-log.js} +2 -2
  5. package/dist/{session-locks.js → access/session-locks.js} +1 -1
  6. package/dist/{user-management.js → access/user-management.js} +1 -1
  7. package/dist/{claude-code-cli.js → agents/claude-code/claude-code-cli.js} +2 -2
  8. package/dist/{claude-code-session.js → agents/claude-code/claude-code-session.js} +1 -1
  9. package/dist/{codex-cli.js → agents/codex/codex-cli.js} +14 -5
  10. package/dist/{codex-session.js → agents/codex/codex-session.js} +2 -4
  11. package/dist/{hermes-cli.js → agents/hermes/hermes-cli.js} +2 -2
  12. package/dist/{hermes-launch.js → agents/hermes/hermes-launch.js} +1 -1
  13. package/dist/{hermes-session.js → agents/hermes/hermes-session.js} +1 -1
  14. package/dist/{openclaw-cli.js → agents/openclaw/openclaw-cli.js} +2 -2
  15. package/dist/{openclaw-launch.js → agents/openclaw/openclaw-launch.js} +1 -1
  16. package/dist/{openclaw-session.js → agents/openclaw/openclaw-session.js} +1 -1
  17. package/dist/{pi-cli.js → agents/pi/pi-cli.js} +2 -2
  18. package/dist/{pi-launch.js → agents/pi/pi-launch.js} +1 -1
  19. package/dist/{pi-session.js → agents/pi/pi-session.js} +1 -1
  20. package/dist/{adapter-conformance.js → agents/shared/adapter-conformance.js} +2 -2
  21. package/dist/{agent-activity.js → agents/shared/agent-activity.js} +5 -5
  22. package/dist/agents/shared/agent-auth-commands.js +30 -0
  23. package/dist/{agent-factory.js → agents/shared/agent-factory.js} +5 -5
  24. package/dist/{agent-feature-matrix.js → agents/shared/agent-feature-matrix.js} +2 -2
  25. package/dist/{agent-updates.js → agents/shared/agent-updates.js} +7 -7
  26. package/dist/{discord-artifacts.js → channels/discord/discord-artifacts.js} +4 -4
  27. package/dist/{discord-bot.js → channels/discord/discord-bot.js} +164 -424
  28. package/dist/{discord-channel-runtime.js → channels/discord/discord-channel-runtime.js} +2 -2
  29. package/dist/{discord-command-surface.js → channels/discord/discord-command-surface.js} +3 -3
  30. package/dist/{bot-rendering.js → channels/shared/bot-rendering.js} +6 -6
  31. package/dist/{channel-actions.js → channels/shared/channel-actions.js} +4 -4
  32. package/dist/channels/shared/channel-bridge-controller.js +69 -0
  33. package/dist/channels/shared/channel-cli-artifacts.js +51 -0
  34. package/dist/{channel-command-service.js → channels/shared/channel-command-service.js} +51 -28
  35. package/dist/channels/shared/channel-external-mirror-controller.js +193 -0
  36. package/dist/channels/shared/channel-external-monitor.js +52 -0
  37. package/dist/{channel-mirror-registry.js → channels/shared/channel-mirror-registry.js} +14 -6
  38. package/dist/{channel-peer-prompt.js → channels/shared/channel-peer-prompt.js} +3 -3
  39. package/dist/{channel-turn-service.js → channels/shared/channel-turn-service.js} +2 -2
  40. package/dist/{context-key.js → channels/shared/context-key.js} +1 -1
  41. package/dist/{session-format.js → channels/shared/session-format.js} +2 -2
  42. package/dist/{slack-artifacts.js → channels/slack/slack-artifacts.js} +4 -4
  43. package/dist/{slack-bot.js → channels/slack/slack-bot.js} +159 -294
  44. package/dist/{slack-channel-runtime.js → channels/slack/slack-channel-runtime.js} +2 -2
  45. package/dist/{slack-command-surface.js → channels/slack/slack-command-surface.js} +2 -2
  46. package/dist/{slack-diagnostics.js → channels/slack/slack-diagnostics.js} +2 -2
  47. package/dist/{bot-ui.js → channels/telegram/bot-ui.js} +1 -1
  48. package/dist/{bot.js → channels/telegram/bot.js} +178 -427
  49. package/dist/{telegram-access-commands.js → channels/telegram/telegram-access-commands.js} +3 -3
  50. package/dist/{telegram-access-middleware.js → channels/telegram/telegram-access-middleware.js} +4 -4
  51. package/dist/{telegram-agent-commands.js → channels/telegram/telegram-agent-commands.js} +9 -9
  52. package/dist/{telegram-artifact-commands.js → channels/telegram/telegram-artifact-commands.js} +4 -4
  53. package/dist/{telegram-channel-runtime.js → channels/telegram/telegram-channel-runtime.js} +2 -2
  54. package/dist/{telegram-command-menu.js → channels/telegram/telegram-command-menu.js} +1 -1
  55. package/dist/{telegram-diagnostics-command.js → channels/telegram/telegram-diagnostics-command.js} +7 -7
  56. package/dist/{telegram-general-commands.js → channels/telegram/telegram-general-commands.js} +4 -4
  57. package/dist/{telegram-operational-commands.js → channels/telegram/telegram-operational-commands.js} +5 -5
  58. package/dist/{telegram-output.js → channels/telegram/telegram-output.js} +2 -2
  59. package/dist/{telegram-preference-commands.js → channels/telegram/telegram-preference-commands.js} +3 -3
  60. package/dist/{telegram-queue-commands.js → channels/telegram/telegram-queue-commands.js} +6 -6
  61. package/dist/{telegram-support-command.js → channels/telegram/telegram-support-command.js} +4 -4
  62. package/dist/{telegram-update-commands.js → channels/telegram/telegram-update-commands.js} +5 -5
  63. package/dist/{config-metadata.js → core/config-metadata.js} +8 -0
  64. package/dist/{config.js → core/config.js} +11 -3
  65. package/dist/index.js +27 -23
  66. package/dist/{peer-client.js → peers/peer-client.js} +57 -1
  67. package/dist/peers/peer-discovery-jobs.js +206 -0
  68. package/dist/peers/peer-discovery.js +223 -0
  69. package/dist/peers/peer-health-monitor.js +49 -0
  70. package/dist/{peer-identity.js → peers/peer-identity.js} +50 -1
  71. package/dist/{peer-runtime-service.js → peers/peer-runtime-service.js} +29 -7
  72. package/dist/{peer-server.js → peers/peer-server.js} +23 -6
  73. package/dist/{peer-store.js → peers/peer-store.js} +84 -11
  74. package/dist/{peer-types.js → peers/peer-types.js} +9 -0
  75. package/dist/peers/peer-web-proxy-contract.js +127 -0
  76. package/dist/{metrics.js → runtime/metrics.js} +5 -3
  77. package/dist/{relay-artifact-service.js → runtime/relay-artifact-service.js} +1 -1
  78. package/dist/runtime/relay-auth-service.js +63 -0
  79. package/dist/runtime/relay-dashboard-service.js +139 -0
  80. package/dist/{relay-external-activity-monitor.js → runtime/relay-external-activity-monitor.js} +140 -53
  81. package/dist/runtime/relay-runtime-active-sessions.js +387 -0
  82. package/dist/runtime/relay-runtime-dashboard.js +201 -0
  83. package/dist/runtime/relay-runtime-prompt-queue-artifacts.js +307 -0
  84. package/dist/runtime/relay-runtime-sessions.js +623 -0
  85. package/dist/runtime/relay-runtime-types.js +1 -0
  86. package/dist/runtime/relay-runtime-updates-jobs.js +360 -0
  87. package/dist/runtime/relay-runtime.js +451 -0
  88. package/dist/runtime/runtime-cache.js +117 -0
  89. package/dist/{session-registry.js → state/session-registry.js} +3 -3
  90. package/dist/{operations.js → support/operations.js} +7 -7
  91. package/dist/{support-bundle.js → support/support-bundle.js} +1 -1
  92. package/dist/{web-api-contract.js → web/web-api-contract.js} +17 -3
  93. package/dist/web/web-api-types.js +1 -0
  94. package/dist/{web-dashboard-access-routes.js → web/web-dashboard-access-routes.js} +2 -2
  95. package/dist/{web-dashboard-assets.js → web/web-dashboard-assets.js} +24 -2
  96. package/dist/{web-dashboard-http.js → web/web-dashboard-http.js} +41 -5
  97. package/dist/{web-dashboard-pages.js → web/web-dashboard-pages.js} +37 -10
  98. package/dist/{web-dashboard-peer-routes.js → web/web-dashboard-peer-routes.js} +102 -7
  99. package/dist/web/web-dashboard-security.js +14 -0
  100. package/dist/{web-dashboard-session-routes.js → web/web-dashboard-session-routes.js} +12 -1
  101. package/dist/{web-dashboard.js → web/web-dashboard.js} +132 -48
  102. package/dist/web/web-performance.js +60 -0
  103. package/dist/web/web-rate-limit.js +19 -0
  104. package/dist/{web-state.js → web/web-state.js} +74 -5
  105. package/dist/webui-assets/dashboard.css +171 -10
  106. package/dist/webui-assets/dashboard.js +515 -48
  107. package/dist/webui-assets/favicon.ico +0 -0
  108. package/dist/webui-assets/favicon.png +0 -0
  109. package/dist/webui-assets/logo.png +0 -0
  110. package/package.json +4 -3
  111. package/plugins/nordrelay/scripts/nordrelay.mjs +17 -5
  112. package/{launchd/start.sh → scripts/launchd-start.sh} +1 -1
  113. package/dist/relay-runtime.js +0 -1916
  114. package/dist/runtime-cache.js +0 -57
  115. /package/dist/{user-management-crypto.js → access/user-management-crypto.js} +0 -0
  116. /package/dist/{user-management-normalize.js → access/user-management-normalize.js} +0 -0
  117. /package/dist/{user-management-types.js → access/user-management-types.js} +0 -0
  118. /package/dist/{claude-code-auth.js → agents/claude-code/claude-code-auth.js} +0 -0
  119. /package/dist/{claude-code-launch.js → agents/claude-code/claude-code-launch.js} +0 -0
  120. /package/dist/{claude-code-state.js → agents/claude-code/claude-code-state.js} +0 -0
  121. /package/dist/{codex-auth.js → agents/codex/codex-auth.js} +0 -0
  122. /package/dist/{codex-config.js → agents/codex/codex-config.js} +0 -0
  123. /package/dist/{codex-launch.js → agents/codex/codex-launch.js} +0 -0
  124. /package/dist/{codex-state.js → agents/codex/codex-state.js} +0 -0
  125. /package/dist/{hermes-api.js → agents/hermes/hermes-api.js} +0 -0
  126. /package/dist/{hermes-auth.js → agents/hermes/hermes-auth.js} +0 -0
  127. /package/dist/{hermes-state.js → agents/hermes/hermes-state.js} +0 -0
  128. /package/dist/{openclaw-auth.js → agents/openclaw/openclaw-auth.js} +0 -0
  129. /package/dist/{openclaw-gateway.js → agents/openclaw/openclaw-gateway.js} +0 -0
  130. /package/dist/{openclaw-state.js → agents/openclaw/openclaw-state.js} +0 -0
  131. /package/dist/{pi-auth.js → agents/pi/pi-auth.js} +0 -0
  132. /package/dist/{pi-rpc.js → agents/pi/pi-rpc.js} +0 -0
  133. /package/dist/{pi-state.js → agents/pi/pi-state.js} +0 -0
  134. /package/dist/{agent-adapter.js → agents/shared/agent-adapter.js} +0 -0
  135. /package/dist/{agent.js → agents/shared/agent.js} +0 -0
  136. /package/dist/{artifacts.js → artifacts/artifacts.js} +0 -0
  137. /package/dist/{attachments.js → artifacts/attachments.js} +0 -0
  138. /package/dist/{voice.js → artifacts/voice.js} +0 -0
  139. /package/dist/{discord-rate-limit.js → channels/discord/discord-rate-limit.js} +0 -0
  140. /package/dist/{channel-adapter.js → channels/shared/channel-adapter.js} +0 -0
  141. /package/dist/{relay-runtime-types.js → channels/shared/channel-bridge-state.js} +0 -0
  142. /package/dist/{channel-command-catalog.js → channels/shared/channel-command-catalog.js} +0 -0
  143. /package/dist/{channel-command-core.js → channels/shared/channel-command-core.js} +0 -0
  144. /package/dist/{channel-prompt-engine.js → channels/shared/channel-prompt-engine.js} +0 -0
  145. /package/dist/{channel-runtime.js → channels/shared/channel-runtime.js} +0 -0
  146. /package/dist/{channel-turn-lifecycle.js → channels/shared/channel-turn-lifecycle.js} +0 -0
  147. /package/dist/{slack-rate-limit.js → channels/slack/slack-rate-limit.js} +0 -0
  148. /package/dist/{telegram-command-types.js → channels/telegram/telegram-command-types.js} +0 -0
  149. /package/dist/{telegram-rate-limit.js → channels/telegram/telegram-rate-limit.js} +0 -0
  150. /package/dist/{activity-events.js → core/activity-events.js} +0 -0
  151. /package/dist/{error-messages.js → core/error-messages.js} +0 -0
  152. /package/dist/{format.js → core/format.js} +0 -0
  153. /package/dist/{logger.js → core/logger.js} +0 -0
  154. /package/dist/{redaction.js → core/redaction.js} +0 -0
  155. /package/dist/{settings-service.js → core/settings-service.js} +0 -0
  156. /package/dist/{settings-wizard-test.js → core/settings-wizard-test.js} +0 -0
  157. /package/dist/{workspace-policy.js → core/workspace-policy.js} +0 -0
  158. /package/dist/{peer-auth.js → peers/peer-auth.js} +0 -0
  159. /package/dist/{peer-context.js → peers/peer-context.js} +0 -0
  160. /package/dist/{peer-readiness.js → peers/peer-readiness.js} +0 -0
  161. /package/dist/{relay-queue-service.js → runtime/relay-queue-service.js} +0 -0
  162. /package/dist/{web-api-types.js → runtime/relay-runtime-delegate.js} +0 -0
  163. /package/dist/{relay-runtime-helpers.js → runtime/relay-runtime-helpers.js} +0 -0
  164. /package/dist/{remote-prompt.js → runtime/remote-prompt.js} +0 -0
  165. /package/dist/{bot-preferences.js → state/bot-preferences.js} +0 -0
  166. /package/dist/{job-store.js → state/job-store.js} +0 -0
  167. /package/dist/{persistence.js → state/persistence.js} +0 -0
  168. /package/dist/{prompt-store.js → state/prompt-store.js} +0 -0
  169. /package/dist/{state-backend.js → state/state-backend.js} +0 -0
  170. /package/dist/{zip-writer.js → support/zip-writer.js} +0 -0
  171. /package/dist/{web-dashboard-artifact-routes.js → web/web-dashboard-artifact-routes.js} +0 -0
  172. /package/dist/{web-dashboard-runtime-routes.js → web/web-dashboard-runtime-routes.js} +0 -0
  173. /package/dist/{web-dashboard-ui.js → web/web-dashboard-ui.js} +0 -0
@@ -0,0 +1,451 @@
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
+ import { relayRuntimeSubscribe, relayRuntimeSnapshot, relayRuntimeStatus, relayRuntimeBootstrapStatus, relayRuntimeVersion, relayRuntimeDiagnostics, relayRuntimeAdapterHealth, relayRuntimePermissions, relayRuntimeMetrics, relayRuntimeAudit, relayRuntimeSupportBundle, relayRuntimeLogs, relayRuntimeClearLogs, relayRuntimeRestartConnector, relayRuntimeDispose } from "./relay-runtime-dashboard.js";
36
+ import { relayRuntimeUpdateConnector, relayRuntimeAgentUpdateJobs, relayRuntimeStartAgentUpdate, relayRuntimeAgentUpdateLog, relayRuntimeDeleteAgentUpdateLog, relayRuntimeSendAgentUpdateInput, relayRuntimeCancelAgentUpdate, relayRuntimeTasks, relayRuntimeJobs, relayRuntimeJobLog, relayRuntimeJobAction, relayRuntimeRecordAgentUpdateLifecycle } from "./relay-runtime-updates-jobs.js";
37
+ import { relayRuntimeActiveSessions, relayRuntimeGetSession, relayRuntimeListKnownContextMetadata, relayRuntimeDiscoverRunningConnectorSessions, relayRuntimeDiscoverActiveCodexSessions, relayRuntimeExternalActiveSession, relayRuntimeSessionStubForMetadata, relayRuntimeCapabilitiesForAgent, relayRuntimeActiveSessionKey, relayRuntimePreferredActiveSession, relayRuntimeRecordActivity, relayRuntimeAppendActivity, relayRuntimeEnrichActivityInput, relayRuntimeEnrichActivityEvent, relayRuntimeEnrichActivityFields, relayRuntimeAppendAudit, relayRuntimeUpdateCurrentProgress, relayRuntimeAddCurrentTool, relayRuntimeBroadcastQueue, relayRuntimeBroadcastStatus, relayRuntimeBroadcast, relayRuntimeScheduleActiveSessionsBroadcast, relayRuntimePublicInfo } from "./relay-runtime-active-sessions.js";
38
+ import { relayRuntimeLocks, relayRuntimeLockWebSession, relayRuntimeUnlockWebSession, relayRuntimeControlOptions, relayRuntimeAuthStatus, relayRuntimeLogin, relayRuntimeLogout, relayRuntimeChatHistory, relayRuntimeWebMirrorPreference, relayRuntimeSessionDetail, relayRuntimeClearChatHistory, relayRuntimeActivity, relayRuntimeRetry, relayRuntimeSync, relayRuntimeListSessions, relayRuntimeListSessionsPage, relayRuntimeFilteredSessions, relayRuntimeListModels, relayRuntimeSetAgent, relayRuntimeNewSession, relayRuntimeSwitchSession, relayRuntimeAttachSession, relayRuntimeSetModel, relayRuntimeSetReasoningEffort, relayRuntimeSetFastMode, relayRuntimeSetLaunchProfile, relayRuntimeHandback, relayRuntimeAbort, relayRuntimeGetControlSession, relayRuntimeCliPathOptions } from "./relay-runtime-sessions.js";
39
+ import { relayRuntimeSendPrompt, relayRuntimeSendUploadPrompt, relayRuntimeSendEnvelope, relayRuntimeQueue, relayRuntimeQueuePaused, relayRuntimeQueueAction, relayRuntimeArtifacts, relayRuntimeArtifact, relayRuntimeDeleteArtifact, relayRuntimeCreateArtifactZip, relayRuntimeArtifactPreview, relayRuntimeEnsureActiveThread, relayRuntimeEnsureIdle, relayRuntimeRunPrompt, relayRuntimeDrainQueue, relayRuntimeUpdateSession } from "./relay-runtime-prompt-queue-artifacts.js";
40
+ export const WEB_CONTEXT_KEY = "web:dashboard";
41
+ const ACTIVE_CODEX_DISCOVERY_LIMIT = 200;
42
+ const ACTIVE_ACTIVITY_TTL_MS = 6 * 60 * 60 * 1000;
43
+ const MAX_WEB_SESSION_PAGE_SIZE = 50;
44
+ const MAX_CHAT_HISTORY = 250;
45
+ export class RelayRuntime {
46
+ config;
47
+ contextKey;
48
+ registry;
49
+ promptStore;
50
+ chatStore;
51
+ activityStore;
52
+ auditStore;
53
+ preferencesStore;
54
+ lockStore;
55
+ agentUpdates;
56
+ queueService;
57
+ jobStore;
58
+ artifactService;
59
+ mirrorRegistry;
60
+ externalActivityMonitor;
61
+ cache = new RuntimeSnapshotCache();
62
+ dashboardService;
63
+ turnService;
64
+ authService;
65
+ subscribers = new Set();
66
+ agentUpdateActors = new Map();
67
+ agentUpdateStates = new Map();
68
+ externalMonitor;
69
+ activeSessionsBroadcastTimer = null;
70
+ activeSessionsLastBroadcastAt = 0;
71
+ draining = false;
72
+ currentTurnId = null;
73
+ accumulatedText = "";
74
+ currentTurnStartedAt = 0;
75
+ currentProgress = null;
76
+ constructor(config, options = {}) {
77
+ this.config = config;
78
+ this.contextKey = options.contextKey ?? WEB_CONTEXT_KEY;
79
+ this.registry = new SessionRegistry(config, {
80
+ fileName: options.registryFileName ?? "web-contexts.json",
81
+ sqliteKey: options.registrySqliteKey ?? "web-contexts",
82
+ });
83
+ this.promptStore = new PromptStore(config.workspace, config.stateBackend);
84
+ this.chatStore = new WebChatStore(config.workspace, config.stateBackend, MAX_CHAT_HISTORY);
85
+ this.activityStore = new WebActivityStore(config.workspace, config.stateBackend, config.auditMaxEvents);
86
+ this.auditStore = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
87
+ this.preferencesStore = new BotPreferencesStore(config.workspace, config.stateBackend);
88
+ this.lockStore = new SessionLockStore(config.workspace, config.stateBackend);
89
+ this.queueService = new RelayQueueService(this.promptStore, this.contextKey);
90
+ this.jobStore = new UnifiedJobStore(config.workspace, config.stateBackend, config.unifiedJobMaxItems);
91
+ this.artifactService = new RelayArtifactService(config);
92
+ this.authService = new RelayAuthService(config);
93
+ this.mirrorRegistry = new ChannelMirrorRegistry(config, this.promptStore);
94
+ this.agentUpdates = new AgentUpdateManager({
95
+ onUpdate: (job) => {
96
+ this.broadcast({ type: "agent_update", job });
97
+ this.recordAgentUpdateLifecycle(job);
98
+ },
99
+ });
100
+ this.externalActivityMonitor = new RelayExternalActivityMonitor({
101
+ config,
102
+ getSession: () => this.getSession(true),
103
+ publicInfo: (session) => this.publicInfo(session),
104
+ queueLength: () => this.queueService.length(),
105
+ mirrorMode: () => this.preferencesStore.get(this.contextKey).mirrorMode ?? this.config.webMirrorMode,
106
+ mirrorMinUpdateMs: () => this.config.webMirrorMinUpdateMs,
107
+ chatStore: this.chatStore,
108
+ chatHistory: () => this.chatHistory(),
109
+ persistWorkspaceArtifactsForTurn: (workspace, turnId, startedAt) => this.artifactService.persistWorkspaceArtifactsForTurn(workspace, turnId, startedAt),
110
+ drainQueue: () => this.drainQueue(),
111
+ appendActivity: (input) => this.appendActivity(input),
112
+ broadcast: (event) => this.broadcast(event),
113
+ broadcastStatus: (message, level) => this.broadcastStatus(message, level),
114
+ });
115
+ this.dashboardService = new RelayDashboardService({
116
+ config,
117
+ cache: this.cache,
118
+ snapshot: () => this.snapshot(),
119
+ getSession: () => this.getSession(true),
120
+ queuePaused: () => this.queueService.isPaused(),
121
+ externalMirror: () => this.externalActivityMonitor.snapshot(),
122
+ authStatus: (agentId) => this.authStatus(agentId),
123
+ cliPathOptions: () => this.cliPathOptions(),
124
+ });
125
+ this.dashboardService.startBackgroundRefresh();
126
+ if (config.codexExternalBusyCheckMs > 0) {
127
+ this.externalMonitor = setInterval(() => {
128
+ void this.externalActivityMonitor.monitorSafe();
129
+ }, config.codexExternalBusyCheckMs);
130
+ this.externalMonitor.unref?.();
131
+ }
132
+ this.turnService = new ChannelTurnService({
133
+ source: "web",
134
+ contextKey: this.contextKey,
135
+ chatStore: this.chatStore,
136
+ artifactService: this.artifactService,
137
+ checkAuth: (info) => this.authService.check(info),
138
+ ensureActiveThread: (session) => this.ensureActiveThread(session),
139
+ updateSession: (session) => this.updateSession(session),
140
+ appendActivity: (input) => this.appendActivity(input),
141
+ appendAudit: (input) => this.appendAudit(input),
142
+ broadcast: (event) => this.broadcast(event),
143
+ chatHistory: () => this.chatHistory(),
144
+ setLastPrompt: (envelope) => this.queueService.setLastPrompt(envelope),
145
+ getCurrentProgress: () => this.currentProgress,
146
+ setCurrentProgress: (progress) => {
147
+ this.currentProgress = progress;
148
+ },
149
+ setCurrentTurn: (id, startedAt, accumulatedText) => {
150
+ this.currentTurnId = id;
151
+ if (startedAt !== undefined)
152
+ this.currentTurnStartedAt = startedAt;
153
+ if (accumulatedText !== undefined)
154
+ this.accumulatedText = accumulatedText;
155
+ },
156
+ getCurrentTurnStartedAt: () => this.currentTurnStartedAt,
157
+ getAccumulatedText: () => this.accumulatedText,
158
+ setAccumulatedText: (text) => {
159
+ this.accumulatedText = text;
160
+ },
161
+ });
162
+ }
163
+ subscribe(callback) {
164
+ return relayRuntimeSubscribe(this, callback);
165
+ }
166
+ async snapshot() {
167
+ return relayRuntimeSnapshot(this);
168
+ }
169
+ async status() {
170
+ return relayRuntimeStatus(this);
171
+ }
172
+ async bootstrapStatus() {
173
+ return relayRuntimeBootstrapStatus(this);
174
+ }
175
+ async version() {
176
+ return relayRuntimeVersion(this);
177
+ }
178
+ updateConnector(actor) {
179
+ return relayRuntimeUpdateConnector(this, actor);
180
+ }
181
+ agentUpdateJobs() {
182
+ return relayRuntimeAgentUpdateJobs(this);
183
+ }
184
+ startAgentUpdate(agentId, operation = "update", actor) {
185
+ return relayRuntimeStartAgentUpdate(this, agentId, operation, actor);
186
+ }
187
+ agentUpdateLog(id) {
188
+ return relayRuntimeAgentUpdateLog(this, id);
189
+ }
190
+ deleteAgentUpdateLog(id, actor) {
191
+ return relayRuntimeDeleteAgentUpdateLog(this, id, actor);
192
+ }
193
+ sendAgentUpdateInput(id, input, actor) {
194
+ return relayRuntimeSendAgentUpdateInput(this, id, input, actor);
195
+ }
196
+ cancelAgentUpdate(id, actor) {
197
+ return relayRuntimeCancelAgentUpdate(this, id, actor);
198
+ }
199
+ async diagnostics() {
200
+ return relayRuntimeDiagnostics(this);
201
+ }
202
+ async adapterHealth() {
203
+ return relayRuntimeAdapterHealth(this);
204
+ }
205
+ permissions() {
206
+ return relayRuntimePermissions(this);
207
+ }
208
+ tasks() {
209
+ return relayRuntimeTasks(this);
210
+ }
211
+ async jobs() {
212
+ return relayRuntimeJobs(this);
213
+ }
214
+ async jobLog(id) {
215
+ return relayRuntimeJobLog(this, id);
216
+ }
217
+ async jobAction(id, action, actor) {
218
+ return relayRuntimeJobAction(this, id, action, actor);
219
+ }
220
+ async activeSessions() {
221
+ return relayRuntimeActiveSessions(this);
222
+ }
223
+ async metrics() {
224
+ return relayRuntimeMetrics(this);
225
+ }
226
+ audit(options = 50) {
227
+ return relayRuntimeAudit(this, options);
228
+ }
229
+ async supportBundle(actor) {
230
+ return relayRuntimeSupportBundle(this, actor);
231
+ }
232
+ locks() {
233
+ return relayRuntimeLocks(this);
234
+ }
235
+ lockWebSession(ownerName = "Web dashboard", actor) {
236
+ return relayRuntimeLockWebSession(this, ownerName, actor);
237
+ }
238
+ unlockWebSession(actor) {
239
+ return relayRuntimeUnlockWebSession(this, actor);
240
+ }
241
+ async controlOptions(agentId) {
242
+ return relayRuntimeControlOptions(this, agentId);
243
+ }
244
+ async authStatus(agentId) {
245
+ return relayRuntimeAuthStatus(this, agentId);
246
+ }
247
+ async login(agentId, actor) {
248
+ return relayRuntimeLogin(this, agentId, actor);
249
+ }
250
+ async logout(agentId, actor) {
251
+ return relayRuntimeLogout(this, agentId, actor);
252
+ }
253
+ async chatHistory(limit = 200) {
254
+ return relayRuntimeChatHistory(this, limit);
255
+ }
256
+ async webMirrorPreference(argument = "", actor) {
257
+ return relayRuntimeWebMirrorPreference(this, argument, actor);
258
+ }
259
+ async sessionDetail(threadId) {
260
+ return relayRuntimeSessionDetail(this, threadId);
261
+ }
262
+ async clearChatHistory(actor) {
263
+ return relayRuntimeClearChatHistory(this, actor);
264
+ }
265
+ activity(options = {}) {
266
+ return relayRuntimeActivity(this, options);
267
+ }
268
+ async retry(actor) {
269
+ return relayRuntimeRetry(this, actor);
270
+ }
271
+ async sync(actor) {
272
+ return relayRuntimeSync(this, actor);
273
+ }
274
+ async listSessions(limit = 80, query = "", agentId) {
275
+ return relayRuntimeListSessions(this, limit, query, agentId);
276
+ }
277
+ async listSessionsPage(page = 1, pageSize = MAX_WEB_SESSION_PAGE_SIZE, query = "", agentId) {
278
+ return relayRuntimeListSessionsPage(this, page, pageSize, query, agentId);
279
+ }
280
+ filteredSessions(session, query, limit) {
281
+ return relayRuntimeFilteredSessions(this, session, query, limit);
282
+ }
283
+ async listModels() {
284
+ return relayRuntimeListModels(this);
285
+ }
286
+ async setAgent(agentId, actor) {
287
+ return relayRuntimeSetAgent(this, agentId, actor);
288
+ }
289
+ async newSession(options = {}, actor) {
290
+ return relayRuntimeNewSession(this, options, actor);
291
+ }
292
+ async switchSession(threadId, actor) {
293
+ return relayRuntimeSwitchSession(this, threadId, actor);
294
+ }
295
+ async attachSession(threadId, actor) {
296
+ return relayRuntimeAttachSession(this, threadId, actor);
297
+ }
298
+ async setModel(model, actor) {
299
+ return relayRuntimeSetModel(this, model, actor);
300
+ }
301
+ async setReasoningEffort(effort, actor) {
302
+ return relayRuntimeSetReasoningEffort(this, effort, actor);
303
+ }
304
+ async setFastMode(enabled, actor) {
305
+ return relayRuntimeSetFastMode(this, enabled, actor);
306
+ }
307
+ async setLaunchProfile(profileId, actor) {
308
+ return relayRuntimeSetLaunchProfile(this, profileId, actor);
309
+ }
310
+ async handback(actor) {
311
+ return relayRuntimeHandback(this, actor);
312
+ }
313
+ async abort(actor) {
314
+ return relayRuntimeAbort(this, actor);
315
+ }
316
+ async sendPrompt(text, actor) {
317
+ return relayRuntimeSendPrompt(this, text, actor);
318
+ }
319
+ async sendUploadPrompt(options, actor) {
320
+ return relayRuntimeSendUploadPrompt(this, options, actor);
321
+ }
322
+ async sendEnvelope(envelope, actor) {
323
+ return relayRuntimeSendEnvelope(this, envelope, actor);
324
+ }
325
+ queue() {
326
+ return relayRuntimeQueue(this);
327
+ }
328
+ queuePaused() {
329
+ return relayRuntimeQueuePaused(this);
330
+ }
331
+ queueAction(action, id, actor) {
332
+ return relayRuntimeQueueAction(this, action, id, actor);
333
+ }
334
+ async artifacts() {
335
+ return relayRuntimeArtifacts(this);
336
+ }
337
+ async artifact(turnId) {
338
+ return relayRuntimeArtifact(this, turnId);
339
+ }
340
+ async deleteArtifact(turnId, actor) {
341
+ return relayRuntimeDeleteArtifact(this, turnId, actor);
342
+ }
343
+ async createArtifactZip(turnId, actor) {
344
+ return relayRuntimeCreateArtifactZip(this, turnId, actor);
345
+ }
346
+ async artifactPreview(turnId, relativePath) {
347
+ return relayRuntimeArtifactPreview(this, turnId, relativePath);
348
+ }
349
+ async logs(target = "connector", lines = 100) {
350
+ return relayRuntimeLogs(this, target, lines);
351
+ }
352
+ clearLogs(target = "connector", actor) {
353
+ return relayRuntimeClearLogs(this, target, actor);
354
+ }
355
+ restartConnector(actor) {
356
+ return relayRuntimeRestartConnector(this, actor);
357
+ }
358
+ dispose() {
359
+ return relayRuntimeDispose(this);
360
+ }
361
+ async getSession(deferThreadStart) {
362
+ return relayRuntimeGetSession(this, deferThreadStart);
363
+ }
364
+ listKnownContextMetadata() {
365
+ return relayRuntimeListKnownContextMetadata(this);
366
+ }
367
+ discoverRunningConnectorSessions() {
368
+ return relayRuntimeDiscoverRunningConnectorSessions(this);
369
+ }
370
+ discoverActiveCodexSessions(knownContexts, preferences) {
371
+ return relayRuntimeDiscoverActiveCodexSessions(this, knownContexts, preferences);
372
+ }
373
+ externalActiveSession(meta, knownContexts, preferences) {
374
+ return relayRuntimeExternalActiveSession(this, meta, knownContexts, preferences);
375
+ }
376
+ sessionStubForMetadata(meta, agentId, capabilities) {
377
+ return relayRuntimeSessionStubForMetadata(this, meta, agentId, capabilities);
378
+ }
379
+ capabilitiesForAgent(agentId) {
380
+ return relayRuntimeCapabilitiesForAgent(this, agentId);
381
+ }
382
+ activeSessionKey(session) {
383
+ return relayRuntimeActiveSessionKey(this, session);
384
+ }
385
+ preferredActiveSession(existing, candidate) {
386
+ return relayRuntimePreferredActiveSession(this, existing, candidate);
387
+ }
388
+ async getControlSession(agentId) {
389
+ return relayRuntimeGetControlSession(this, agentId);
390
+ }
391
+ cliPathOptions() {
392
+ return relayRuntimeCliPathOptions(this);
393
+ }
394
+ async ensureActiveThread(session) {
395
+ return relayRuntimeEnsureActiveThread(this, session);
396
+ }
397
+ ensureIdle(session) {
398
+ return relayRuntimeEnsureIdle(this, session);
399
+ }
400
+ async runPrompt(session, envelope) {
401
+ return relayRuntimeRunPrompt(this, session, envelope);
402
+ }
403
+ async drainQueue() {
404
+ return relayRuntimeDrainQueue(this);
405
+ }
406
+ updateSession(session) {
407
+ return relayRuntimeUpdateSession(this, session);
408
+ }
409
+ recordActivity(input) {
410
+ return relayRuntimeRecordActivity(this, input);
411
+ }
412
+ recordAgentUpdateLifecycle(job) {
413
+ return relayRuntimeRecordAgentUpdateLifecycle(this, job);
414
+ }
415
+ appendActivity(input) {
416
+ return relayRuntimeAppendActivity(this, input);
417
+ }
418
+ enrichActivityInput(input) {
419
+ return relayRuntimeEnrichActivityInput(this, input);
420
+ }
421
+ enrichActivityEvent(event, info) {
422
+ return relayRuntimeEnrichActivityEvent(this, event, info);
423
+ }
424
+ enrichActivityFields(event, info) {
425
+ return relayRuntimeEnrichActivityFields(this, event, info);
426
+ }
427
+ appendAudit(input) {
428
+ return relayRuntimeAppendAudit(this, input);
429
+ }
430
+ updateCurrentProgress(patch = {}) {
431
+ return relayRuntimeUpdateCurrentProgress(this, patch);
432
+ }
433
+ addCurrentTool(toolName) {
434
+ return relayRuntimeAddCurrentTool(this, toolName);
435
+ }
436
+ broadcastQueue() {
437
+ return relayRuntimeBroadcastQueue(this);
438
+ }
439
+ broadcastStatus(message, level = "info") {
440
+ return relayRuntimeBroadcastStatus(this, message, level);
441
+ }
442
+ broadcast(event) {
443
+ return relayRuntimeBroadcast(this, event);
444
+ }
445
+ scheduleActiveSessionsBroadcast() {
446
+ return relayRuntimeScheduleActiveSessionsBroadcast(this);
447
+ }
448
+ publicInfo(session) {
449
+ return relayRuntimePublicInfo(this, session);
450
+ }
451
+ }
@@ -0,0 +1,117 @@
1
+ export class RuntimeSnapshotCache {
2
+ entries = new Map();
3
+ register(key, producer) {
4
+ const entry = this.entries.get(key);
5
+ if (entry) {
6
+ entry.producer = producer;
7
+ return;
8
+ }
9
+ this.entries.set(key, { producer, refreshedAt: 0 });
10
+ }
11
+ async get(key, ttlMs, producer) {
12
+ if (producer) {
13
+ this.register(key, producer);
14
+ }
15
+ const now = Date.now();
16
+ const entry = this.entries.get(key);
17
+ const activeProducer = producer ?? entry?.producer;
18
+ if (!activeProducer) {
19
+ throw new Error(`Runtime cache producer is not registered for ${key}.`);
20
+ }
21
+ const hasFreshValue = entry?.value !== undefined && ttlMs > 0 && now - entry.refreshedAt <= ttlMs;
22
+ if (hasFreshValue) {
23
+ return {
24
+ value: entry.value,
25
+ refreshedAt: new Date(entry.refreshedAt).toISOString(),
26
+ stale: false,
27
+ };
28
+ }
29
+ if (entry?.value !== undefined) {
30
+ if (!entry.refresh) {
31
+ entry.refresh = activeProducer()
32
+ .then((value) => {
33
+ entry.value = value;
34
+ entry.refreshedAt = Date.now();
35
+ return value;
36
+ })
37
+ .catch(() => entry.value)
38
+ .finally(() => {
39
+ entry.refresh = undefined;
40
+ });
41
+ }
42
+ return {
43
+ value: entry.value,
44
+ refreshedAt: new Date(entry.refreshedAt).toISOString(),
45
+ stale: true,
46
+ };
47
+ }
48
+ const pending = entry?.refresh ?? activeProducer();
49
+ this.entries.set(key, { ...entry, producer: activeProducer, refresh: pending, refreshedAt: now });
50
+ try {
51
+ const value = await pending;
52
+ const refreshedAt = Date.now();
53
+ this.entries.set(key, { value, refreshedAt, producer: activeProducer });
54
+ return {
55
+ value,
56
+ refreshedAt: new Date(refreshedAt).toISOString(),
57
+ stale: false,
58
+ };
59
+ }
60
+ catch (error) {
61
+ this.entries.delete(key);
62
+ throw error;
63
+ }
64
+ }
65
+ refresh(key) {
66
+ const entry = this.entries.get(key);
67
+ if (!entry?.producer) {
68
+ throw new Error(`Runtime cache producer is not registered for ${key}.`);
69
+ }
70
+ if (!entry.refresh) {
71
+ entry.refresh = entry.producer()
72
+ .then((value) => {
73
+ entry.value = value;
74
+ entry.refreshedAt = Date.now();
75
+ return value;
76
+ })
77
+ .finally(() => {
78
+ entry.refresh = undefined;
79
+ });
80
+ }
81
+ return entry.refresh.then((value) => ({
82
+ value,
83
+ refreshedAt: new Date(entry.refreshedAt).toISOString(),
84
+ stale: false,
85
+ }));
86
+ }
87
+ warm(keys) {
88
+ const targetKeys = keys ?? [...this.entries.keys()];
89
+ for (const key of targetKeys) {
90
+ if (!this.entries.get(key)?.producer)
91
+ continue;
92
+ void this.refresh(key).catch(() => {
93
+ // Best-effort dashboard warm-up. Foreground requests will surface errors.
94
+ });
95
+ }
96
+ }
97
+ invalidate(key) {
98
+ if (key) {
99
+ const entry = this.entries.get(key);
100
+ if (entry?.producer) {
101
+ this.entries.set(key, { producer: entry.producer, refreshedAt: 0 });
102
+ }
103
+ else {
104
+ this.entries.delete(key);
105
+ }
106
+ return;
107
+ }
108
+ for (const [entryKey, entry] of this.entries.entries()) {
109
+ if (entry.producer) {
110
+ this.entries.set(entryKey, { producer: entry.producer, refreshedAt: 0 });
111
+ }
112
+ else {
113
+ this.entries.delete(entryKey);
114
+ }
115
+ }
116
+ }
117
+ }
@@ -1,6 +1,6 @@
1
- import { createAgentSessionService } from "./agent-factory.js";
2
- import { CODEX_AGENT_CAPABILITIES } from "./agent.js";
3
- import { findLaunchProfile } from "./codex-launch.js";
1
+ import { createAgentSessionService } from "../agents/shared/agent-factory.js";
2
+ import { CODEX_AGENT_CAPABILITIES } from "../agents/shared/agent.js";
3
+ import { findLaunchProfile } from "../agents/codex/codex-launch.js";
4
4
  import { createDocumentStore } from "./state-backend.js";
5
5
  export class SessionRegistry {
6
6
  config;
@@ -3,12 +3,12 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { readFile, stat } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
- import { describeCodexCli, findExecutableOnPath, resolveCodexCli } from "./codex-cli.js";
7
- import { findLatestDatabase } from "./codex-state.js";
8
- import { describeClaudeCodeCli, resolveClaudeCodeCli } from "./claude-code-cli.js";
9
- import { describeHermesCli, resolveHermesCli } from "./hermes-cli.js";
10
- import { describeOpenClawCli, resolveOpenClawCli } from "./openclaw-cli.js";
11
- import { describePiCli, resolvePiCli } from "./pi-cli.js";
6
+ import { describeCodexCli, findExecutableOnPath, resolveCodexCli } from "../agents/codex/codex-cli.js";
7
+ import { findLatestDatabase } from "../agents/codex/codex-state.js";
8
+ import { describeClaudeCodeCli, resolveClaudeCodeCli } from "../agents/claude-code/claude-code-cli.js";
9
+ import { describeHermesCli, resolveHermesCli } from "../agents/hermes/hermes-cli.js";
10
+ import { describeOpenClawCli, resolveOpenClawCli } from "../agents/openclaw/openclaw-cli.js";
11
+ import { describePiCli, resolvePiCli } from "../agents/pi/pi-cli.js";
12
12
  const APP_NAME = "nordrelay";
13
13
  const PACKAGE_NAME = "@nordbyte/nordrelay";
14
14
  const CODEX_PACKAGE_NAME = "@openai/codex";
@@ -480,7 +480,7 @@ export function resolveNpmSpawnCommand(env = process.env) {
480
480
  shell: false,
481
481
  };
482
482
  }
483
- const pathMatch = findExecutableOnPath("npm", env.PATH);
483
+ const pathMatch = findExecutableOnPath("npm", env.PATH, { pathext: env.PATHEXT });
484
484
  if (pathMatch) {
485
485
  return {
486
486
  command: pathMatch,
@@ -3,7 +3,7 @@ import { mkdir, stat, writeFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { getAgentUpdateLogPath, getConnectorHealth, getConnectorHome, getConnectorLogPath, getConnectorStatePath, getSourceRoot, getUpdateLogPath, getVersionChecks, readFormattedLogTail, resolveNpmSpawnCommand, } from "./operations.js";
6
- import { redactText } from "./redaction.js";
6
+ import { redactText } from "../core/redaction.js";
7
7
  import { createZipBuffer } from "./zip-writer.js";
8
8
  export async function createSupportBundle(options) {
9
9
  const createdAt = new Date();
@@ -25,8 +25,16 @@ export const WEB_API_ROUTE_DEFINITIONS = [
25
25
  exact("/api/peers/invite", ["POST"], "peers.write"),
26
26
  exact("/api/peers/pair", ["POST"], "peers.write"),
27
27
  exact("/api/peers/probe", ["POST"], "peers.connect"),
28
+ exact("/api/peers/discover", ["GET"], "peers.connect"),
29
+ exact("/api/peers/discovery-jobs", ["GET", "POST"], readWrite("peers.connect", "peers.connect")),
30
+ dynamic("/api/peers/discovery-jobs/:id", "^/api/peers/discovery-jobs/[^/]+$", ["GET"], "peers.connect", `/api/peers/discovery-jobs/${stringToken}`),
31
+ dynamic("/api/peers/discovery-jobs/:id/cancel", "^/api/peers/discovery-jobs/[^/]+/cancel$", ["POST"], "peers.connect", `/api/peers/discovery-jobs/${stringToken}/cancel`),
32
+ dynamic("/api/peers/discovery-jobs/:id/log", "^/api/peers/discovery-jobs/[^/]+/log$", ["GET"], "peers.connect", `/api/peers/discovery-jobs/${stringToken}/log`),
33
+ exact("/api/peers/identity/backup", ["GET"], "peers.write"),
34
+ exact("/api/peers/identity/restore", ["POST"], "peers.write"),
28
35
  exact("/api/peers/global-sessions", ["GET"], "sessions.read"),
29
36
  dynamic("/api/peers/invitations/:id", "^/api/peers/invitations/[^/]+$", ["DELETE"], "peers.write", `/api/peers/invitations/${stringToken}`),
37
+ dynamic("/api/peers/:id/repin", "^/api/peers/[^/]+/repin$", ["POST"], "peers.write", `/api/peers/${stringToken}/repin`),
30
38
  dynamic("/api/peers/:id/health", "^/api/peers/[^/]+/health$", ["GET"], "peers.connect", `/api/peers/${stringToken}/health`),
31
39
  dynamic("/api/peers/:id", "^/api/peers/[^/]+$", ["PATCH", "DELETE"], "peers.write", `/api/peers/${stringToken}`),
32
40
  dynamic("/api/peers/:id/proxy", "^/api/peers/[^/]+/proxy$", ["POST"], "peers.connect", `/api/peers/${stringToken}/proxy`),
@@ -79,6 +87,7 @@ export const WEB_API_ROUTE_DEFINITIONS = [
79
87
  exact("/api/sync", ["POST"], "sessions.write"),
80
88
  exact("/api/queue", ["GET", "POST"], readWrite("queue.read", "queue.write")),
81
89
  exact("/api/chat/history", ["GET", "DELETE"], readWrite("sessions.read", "sessions.write")),
90
+ exact("/api/chat/mirror", ["GET", "POST"], readWrite("sessions.read", "settings.write")),
82
91
  exact("/api/activity", ["GET"], "sessions.read"),
83
92
  exact("/api/artifacts", ["GET", "DELETE"], readWrite("files.read", "files.write")),
84
93
  exact("/api/artifacts/bulk", ["POST"], "files.write"),
@@ -96,11 +105,16 @@ export const WEB_API_STATIC_PATHS = WEB_API_ROUTE_DEFINITIONS
96
105
  .map((route) => route.path);
97
106
  export const WEB_API_DYNAMIC_TYPE_PATHS = WEB_API_ROUTE_DEFINITIONS
98
107
  .flatMap((route) => route.dynamicType ? [route.dynamicType] : []);
108
+ export function routeForWebRequest(method, pathname) {
109
+ const verb = normalizeMethod(method);
110
+ const route = WEB_API_ROUTE_DEFINITIONS.find((candidate) => (candidate.pattern ? new RegExp(candidate.pattern).test(pathname) : candidate.path === pathname));
111
+ const methods = route?.methods ?? [];
112
+ return route && methods.includes(verb) ? route : null;
113
+ }
99
114
  export function permissionForWebRequestFromContract(method, pathname) {
100
115
  const verb = normalizeMethod(method);
101
- const rule = WEB_API_ROUTE_DEFINITIONS.find((candidate) => (candidate.pattern ? new RegExp(candidate.pattern).test(pathname) : candidate.path === pathname));
102
- const methods = rule?.methods ?? [];
103
- if (!rule || !methods.includes(verb)) {
116
+ const rule = routeForWebRequest(verb, pathname);
117
+ if (!rule) {
104
118
  return null;
105
119
  }
106
120
  return resolvePermission(rule.permissions, verb);
@@ -0,0 +1 @@
1
+ export {};