@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
@@ -1,47 +1,49 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { App } from "@slack/bolt";
3
- import { ADMIN_GROUP_ID } from "./access-control.js";
4
- import { agentLabel, agentReasoningLabel, agentReasoningOptions } from "./agent.js";
5
- import { getAgentActivityLog, getExternalSnapshotForSession } from "./agent-activity.js";
6
- import { listAgentAdapterDescriptors } from "./agent-adapter.js";
7
- import { AgentUpdateManager } from "./agent-updates.js";
8
- import { enabledAgents } from "./agent-factory.js";
9
- import { collectRecentWorkspaceArtifacts, ensureOutDir, formatArtifactSummary, persistWorkspaceArtifactReport } from "./artifacts.js";
10
- import { buildFileInstructions, outboxPath, stageFile } from "./attachments.js";
11
- import { AuditLogStore } from "./audit-log.js";
12
- import { BotPreferencesStore } from "./bot-preferences.js";
13
- import { capabilitiesOf, filterActivityEvents, parseActivityOptions, renderExternalMirrorEvent, renderExternalMirrorStatus, renderPromptFailure, trimLine } from "./bot-rendering.js";
14
- import { parseAgentUpdateId, renderAgentUpdateJobAction, renderAgentUpdateJobsAction, renderAgentUpdateLogAction, renderAgentUpdatePickerAction, renderQueueListAction } from "./channel-actions.js";
15
- import { createSharedChannelCommandDispatcher } from "./channel-command-core.js";
16
- import { slackHelpCommandList } from "./channel-command-catalog.js";
17
- import { ChannelCommandService } from "./channel-command-service.js";
18
- import { createChannelPromptEngine } from "./channel-prompt-engine.js";
19
- import { runChannelPeerPrompt } from "./channel-peer-prompt.js";
20
- import { deliverChannelAction } from "./channel-runtime.js";
21
- import { checkAuthStatus, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
22
- import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout } from "./claude-code-auth.js";
23
- import { isSlackContextKey, parseSlackContextKey, slackContextKey } from "./context-key.js";
24
- import { friendlyErrorText } from "./error-messages.js";
25
- import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
26
- import { spawnConnectorRestart, spawnSelfUpdate } from "./operations.js";
27
- import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
28
- import { RemoteRelayClient } from "./peer-client.js";
29
- import { checkPiAuthStatus } from "./pi-auth.js";
30
- import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
31
- import { RelayArtifactService } from "./relay-artifact-service.js";
32
- import { configureRedaction, redactText } from "./redaction.js";
33
- import { renderSessionInfoPlain } from "./session-format.js";
34
- import { canWriteWithLock, SessionLockStore } from "./session-locks.js";
35
- import { SessionRegistry } from "./session-registry.js";
3
+ import { ADMIN_GROUP_ID } from "../../access/access-control.js";
4
+ import { agentLabel, agentReasoningLabel, agentReasoningOptions } from "../../agents/shared/agent.js";
5
+ import { getAgentActivityLog, getExternalSnapshotForSession } from "../../agents/shared/agent-activity.js";
6
+ import { hostAgentLoginCommand, hostAgentLogoutCommand } from "../../agents/shared/agent-auth-commands.js";
7
+ import { listAgentAdapterDescriptors } from "../../agents/shared/agent-adapter.js";
8
+ import { AgentUpdateManager } from "../../agents/shared/agent-updates.js";
9
+ import { enabledAgents } from "../../agents/shared/agent-factory.js";
10
+ import { ensureOutDir } from "../../artifacts/artifacts.js";
11
+ import { buildFileInstructions, outboxPath, stageFile } from "../../artifacts/attachments.js";
12
+ import { AuditLogStore } from "../../access/audit-log.js";
13
+ import { BotPreferencesStore } from "../../state/bot-preferences.js";
14
+ import { capabilitiesOf, filterActivityEvents, parseActivityOptions, renderPromptFailure, trimLine } from "../shared/bot-rendering.js";
15
+ import { parseAgentUpdateId, renderAgentUpdateJobAction, renderAgentUpdateJobsAction, renderAgentUpdateLogAction, renderAgentUpdatePickerAction, renderQueueListAction } from "../shared/channel-actions.js";
16
+ import { createChannelActivityRecorder, createChannelAuditRecorder, createChannelBusyStore, createChannelPermissionChecker, createChannelQueueStatusController, } from "../shared/channel-bridge-controller.js";
17
+ import { createSharedChannelCommandDispatcher } from "../shared/channel-command-core.js";
18
+ import { slackHelpCommandList } from "../shared/channel-command-catalog.js";
19
+ import { ChannelCommandService } from "../shared/channel-command-service.js";
20
+ import { createChannelPromptEngine } from "../shared/channel-prompt-engine.js";
21
+ import { queueChannelPromptIfBusy } from "../shared/channel-prompt-queue.js";
22
+ import { runChannelPeerPrompt } from "../shared/channel-peer-prompt.js";
23
+ import { deliverChannelAction } from "../shared/channel-runtime.js";
24
+ import { deliverChannelCliArtifacts } from "../shared/channel-cli-artifacts.js";
25
+ import { createChannelExternalMirrorController } from "../shared/channel-external-mirror-controller.js";
26
+ import { monitorChannelExternalContexts } from "../shared/channel-external-monitor.js";
27
+ import { isSlackContextKey, parseSlackContextKey, slackContextKey } from "../shared/context-key.js";
28
+ import { friendlyErrorText } from "../../core/error-messages.js";
29
+ import { spawnConnectorRestart, spawnSelfUpdate } from "../../support/operations.js";
30
+ import { RemoteRelayClient } from "../../peers/peer-client.js";
31
+ import { PromptStore, toPromptEnvelope } from "../../state/prompt-store.js";
32
+ import { RelayArtifactService } from "../../runtime/relay-artifact-service.js";
33
+ import { RelayAuthService } from "../../runtime/relay-auth-service.js";
34
+ import { configureRedaction, redactText } from "../../core/redaction.js";
35
+ import { renderSessionInfoPlain } from "../shared/session-format.js";
36
+ import { canWriteWithLock, SessionLockStore } from "../../access/session-locks.js";
37
+ import { SessionRegistry } from "../../state/session-registry.js";
36
38
  import { createSlackArtifactCommandHandler, sendRecentSlackArtifacts } from "./slack-artifacts.js";
37
39
  import { SlackBotChannelRuntime, actionFromSlackActionId, splitSlackMessage, trimSlackMessage } from "./slack-channel-runtime.js";
38
40
  import { isUnauthenticatedSlackCommandAllowed, parseSlackMessageCommand, parseSlackSlashCommand, permissionForSlackAction, requiredPermissionForSlackCommand } from "./slack-command-surface.js";
39
41
  import { collectSlackDiagnostics } from "./slack-diagnostics.js";
40
42
  import { getSlackRateLimitMetrics } from "./slack-rate-limit.js";
41
- import { transcribeAudio } from "./voice.js";
42
- import { UserStore } from "./user-management.js";
43
- import { WebActivityStore } from "./web-state.js";
44
- import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "./workspace-policy.js";
43
+ import { transcribeAudio } from "../../artifacts/voice.js";
44
+ import { UserStore } from "../../access/user-management.js";
45
+ import { WebActivityStore } from "../../web/web-state.js";
46
+ import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "../../core/workspace-policy.js";
45
47
  export { isUnauthenticatedSlackCommandAllowed, permissionForSlackAction, requiredPermissionForSlackCommand } from "./slack-command-surface.js";
46
48
  const EDIT_DEBOUNCE_MS = 1500;
47
49
  const TYPING_INTERVAL_MS = 4500;
@@ -70,24 +72,23 @@ export function createSlackBridge(config, registry) {
70
72
  const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
71
73
  const userStore = new UserStore();
72
74
  const artifactService = new RelayArtifactService(config);
75
+ const authService = new RelayAuthService(config);
73
76
  const agentUpdates = new AgentUpdateManager();
74
77
  const commandService = new ChannelCommandService(config);
75
- const busyStates = new Map();
78
+ const busyStates = createChannelBusyStore();
76
79
  const turnProgress = new Map();
77
80
  const draining = new Set();
78
81
  const picks = new Map();
79
82
  const externalMirrors = new Map();
80
- const queueStatusMessages = new Map();
83
+ const queueStatusMessages = createChannelQueueStatusController({
84
+ send: async (_contextKey, context, text) => (await runtime.sendMessage(context, { text, fallbackText: text })).messageId,
85
+ edit: async (_contextKey, context, messageId, text) => {
86
+ await runtime.editMessage(context, messageId, { text, fallbackText: text });
87
+ },
88
+ });
81
89
  const remoteClient = new RemoteRelayClient();
82
90
  let externalMonitor;
83
- const getBusyState = (contextKey) => {
84
- let state = busyStates.get(contextKey);
85
- if (!state) {
86
- state = { processing: false, switching: false };
87
- busyStates.set(contextKey, state);
88
- }
89
- return state;
90
- };
91
+ const getBusyState = (contextKey) => busyStates.get(contextKey);
91
92
  const actorFor = (request) => ({
92
93
  channel: "slack",
93
94
  id: request.authUser?.user.id ?? `slack:${request.userId}`,
@@ -95,27 +96,19 @@ export function createSlackBridge(config, registry) {
95
96
  username: request.authUser?.user.email ?? request.username,
96
97
  channelUserId: request.userId,
97
98
  });
98
- const appendActivity = (request, input) => {
99
- activityStore.append({
100
- source: "slack",
101
- contextKey: request.contextKey,
102
- actor: input.actor ?? actorFor(request),
103
- workspace: input.workspace ?? config.workspace,
104
- threadId: input.threadId ?? null,
105
- ...input,
106
- });
107
- };
108
- const audit = (request, input) => {
109
- auditLog.append({
110
- channelId: "slack",
111
- contextKey: input.contextKey ?? request.contextKey,
112
- actor: input.actor ?? actorFor(request),
113
- actorId: request.authUser?.user.id ?? request.userId,
114
- actorRole: request.authUser?.groups.map((group) => group.name).join(", ") ?? "unauthenticated",
115
- ...input,
116
- });
117
- };
118
- const hasPermission = (request, permission) => userStore.hasPermission(request.authUser, permission);
99
+ const appendActivity = createChannelActivityRecorder({
100
+ source: "slack",
101
+ workspace: config.workspace,
102
+ activityStore,
103
+ actorFor,
104
+ });
105
+ const audit = createChannelAuditRecorder({
106
+ channelId: "slack",
107
+ auditLog,
108
+ actorFor,
109
+ actorIdFor: (request) => request.userId,
110
+ });
111
+ const hasPermission = createChannelPermissionChecker(userStore);
119
112
  const reply = async (request, content, options = {}) => {
120
113
  if (options.ephemeral && request.respond) {
121
114
  await request.respond({
@@ -213,7 +206,7 @@ export function createSlackBridge(config, registry) {
213
206
  };
214
207
  const commandArtifacts = createSlackArtifactCommandHandler(artifactDeps);
215
208
  const getBusyReason = (contextKey) => {
216
- const state = busyStates.get(contextKey);
209
+ const state = busyStates.peek(contextKey);
217
210
  const session = registry.get(contextKey);
218
211
  if (state?.processing || state?.switching || session?.isProcessing()) {
219
212
  return { busy: true, kind: "connector", state: state ?? getBusyState(contextKey) };
@@ -225,111 +218,7 @@ export function createSlackBridge(config, registry) {
225
218
  return { busy: false, kind: "idle" };
226
219
  };
227
220
  const updateQueueStatusMessage = async (contextKey, context, text) => {
228
- const state = queueStatusMessages.get(contextKey) ?? {};
229
- if (state.lastText === text && state.messageId) {
230
- return;
231
- }
232
- if (!state.messageId) {
233
- const sent = await runtime.sendMessage(context, { text, fallbackText: text });
234
- state.messageId = sent.messageId;
235
- state.lastText = text;
236
- queueStatusMessages.set(contextKey, state);
237
- return;
238
- }
239
- await runtime.editMessage(context, state.messageId, { text, fallbackText: text });
240
- state.lastText = text;
241
- queueStatusMessages.set(contextKey, state);
242
- };
243
- const sendExternalWorkingNotice = async (context, state, snapshot) => {
244
- const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
245
- if (state.workingNoticeTurnKey === turnKey) {
246
- return;
247
- }
248
- const prompt = trimLine(snapshot.latestUserMessage ?? "", 250);
249
- const text = prompt ? `*Working on* ${prompt}` : `*Working on* external ${snapshot.agentLabel} task...`;
250
- await runtime.sendMessage(context, {
251
- text,
252
- fallbackText: prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`,
253
- });
254
- state.workingNoticeTurnKey = turnKey;
255
- };
256
- const mirrorExternalSnapshot = async (contextKey, context, session, snapshot) => {
257
- let state = externalMirrors.get(contextKey);
258
- if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.sourcePath) {
259
- state = {
260
- threadId: snapshot.threadId,
261
- rolloutPath: snapshot.sourcePath,
262
- lastLine: snapshot.lineCount,
263
- turnId: snapshot.activity.turnId,
264
- startedAt: snapshot.activity.startedAt,
265
- };
266
- externalMirrors.set(contextKey, state);
267
- }
268
- const mirrorMode = preferencesStore.get(contextKey).mirrorMode ?? config.slackMirrorMode;
269
- if (snapshot.activity.active) {
270
- state.turnId = snapshot.activity.turnId;
271
- state.startedAt = snapshot.activity.startedAt;
272
- if (mirrorMode !== "off") {
273
- const now = Date.now();
274
- if (!state.lastTypingAt || now - state.lastTypingAt >= TYPING_INTERVAL_MS) {
275
- state.lastTypingAt = now;
276
- await runtime.sendTyping(context).catch(() => { });
277
- }
278
- }
279
- if (mirrorMode === "final") {
280
- await sendExternalWorkingNotice(context, state, snapshot);
281
- state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
282
- return;
283
- }
284
- if (mirrorMode === "off") {
285
- state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
286
- return;
287
- }
288
- const status = renderExternalMirrorStatus(snapshot, promptStore.list(contextKey).length);
289
- const now = Date.now();
290
- const canUpdateStatus = !state.latestStatusAt || now - state.latestStatusAt >= config.slackMirrorMinUpdateMs;
291
- if (!state.statusMessageId) {
292
- const sent = await runtime.sendMessage(context, { text: status.plain, fallbackText: status.plain });
293
- state.statusMessageId = sent.messageId;
294
- state.latestStatusAt = now;
295
- }
296
- else if (state.latestStatus !== status.plain && canUpdateStatus) {
297
- await runtime.editMessage(context, state.statusMessageId, { text: status.plain, fallbackText: status.plain });
298
- state.latestStatusAt = now;
299
- }
300
- state.latestStatus = status.plain;
301
- if (mirrorMode === "full") {
302
- const newEvents = snapshot.events
303
- .filter((event) => event.lineNumber > (state.latestMirroredEventLine ?? state.lastLine))
304
- .slice(-6);
305
- for (const event of newEvents) {
306
- const rendered = renderExternalMirrorEvent(event);
307
- if (rendered) {
308
- await runtime.sendMessage(context, { text: rendered.plain, fallbackText: rendered.plain });
309
- state.latestMirroredEventLine = event.lineNumber;
310
- }
311
- }
312
- }
313
- state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
314
- return;
315
- }
316
- const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
317
- if (terminalEvent) {
318
- const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
319
- if (mirrorMode !== "off" && mirrorMode !== "status" && finalAgent?.text && finalAgent.lineNumber !== state.latestAgentLine) {
320
- await runtime.sendMessage(context, {
321
- text: `*${snapshot.agentLabel} CLI final answer:*`,
322
- fallbackText: `${snapshot.agentLabel} CLI final answer:`,
323
- });
324
- for (const chunk of splitSlackMessage(finalAgent.text)) {
325
- await runtime.sendMessage(context, { text: chunk, fallbackText: chunk });
326
- }
327
- state.latestAgentLine = finalAgent.lineNumber;
328
- }
329
- await deliverCliGeneratedArtifacts(contextKey, context, session, state.startedAt, terminalEvent.turnId);
330
- }
331
- state.workingNoticeTurnKey = undefined;
332
- state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
221
+ await queueStatusMessages.update(contextKey, context, text);
333
222
  };
334
223
  const ensureActiveThread = async (request, session) => {
335
224
  if (!session.hasActiveThread()) {
@@ -337,63 +226,15 @@ export function createSlackBridge(config, registry) {
337
226
  updateSession(request, session);
338
227
  }
339
228
  };
340
- const checkAgentAuthStatus = async (info) => {
341
- if (info.agentId === "pi")
342
- return checkPiAuthStatus(info.model);
343
- if (info.agentId === "hermes")
344
- return checkHermesAuthStatus({ baseUrl: config.hermesApiBaseUrl, apiKey: config.hermesApiKey });
345
- if (info.agentId === "openclaw")
346
- return checkOpenClawAuthStatus({ gatewayUrl: config.openClawGatewayUrl, token: config.openClawGatewayToken, password: config.openClawGatewayPassword });
347
- if (info.agentId === "claude-code")
348
- return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
349
- return checkAuthStatus(config.codexApiKey);
350
- };
351
- const checkLoginAuthStatus = async (info) => {
352
- if (info.agentId === "hermes")
353
- return checkHermesAuthStatus({ baseUrl: config.hermesApiBaseUrl, apiKey: config.hermesApiKey });
354
- if (info.agentId === "claude-code")
355
- return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
356
- return checkAuthStatus(config.codexApiKey);
357
- };
358
- const startAgentLogin = (info) => {
359
- if (info.agentId === "hermes")
360
- return startHermesLogin(config.hermesCliPath);
361
- if (info.agentId === "claude-code")
362
- return startClaudeCodeLogin(config.claudeCodeCliPath);
363
- if (info.agentId === "codex")
364
- return startCodexLogin();
365
- return Promise.resolve({ success: false, message: `${info.agentLabel} login is not managed by NordRelay. Run the agent login flow on the host.` });
366
- };
367
- const startAgentLogout = (info) => {
368
- if (info.agentId === "hermes")
369
- return startHermesLogout(config.hermesCliPath);
370
- if (info.agentId === "claude-code")
371
- return startClaudeCodeLogout(config.claudeCodeCliPath);
372
- if (info.agentId === "codex")
373
- return startCodexLogout();
374
- return Promise.resolve({ success: false, message: `${info.agentLabel} logout is not managed by NordRelay. Run the agent logout flow on the host.` });
375
- };
229
+ const checkAgentAuthStatus = (info) => authService.check(info);
230
+ const checkLoginAuthStatus = (info) => authService.check(info);
231
+ const startAgentLogin = (info) => authService.startLogin(info);
232
+ const startAgentLogout = (info) => authService.startLogout(info);
376
233
  const hostLoginCommand = (info) => {
377
- if (info.agentId === "hermes")
378
- return `${config.hermesCliPath ?? "hermes"} login --no-browser`;
379
- if (info.agentId === "claude-code")
380
- return `${config.claudeCodeCliPath ?? "claude"} auth login`;
381
- if (info.agentId === "pi")
382
- return `${config.piCliPath ?? "pi"} auth login`;
383
- if (info.agentId === "openclaw")
384
- return `${config.openClawCliPath ?? "openclaw"} login`;
385
- return "codex login --device-auth";
234
+ return hostAgentLoginCommand(config, info);
386
235
  };
387
236
  const hostLogoutCommand = (info) => {
388
- if (info.agentId === "hermes")
389
- return `${config.hermesCliPath ?? "hermes"} logout`;
390
- if (info.agentId === "claude-code")
391
- return `${config.claudeCodeCliPath ?? "claude"} auth logout`;
392
- if (info.agentId === "pi")
393
- return `${config.piCliPath ?? "pi"} auth logout`;
394
- if (info.agentId === "openclaw")
395
- return `${config.openClawCliPath ?? "openclaw"} logout`;
396
- return "codex logout";
237
+ return hostAgentLogoutCommand(config, info);
397
238
  };
398
239
  const denyIfLocked = async (request) => {
399
240
  const lock = lockStore.get(request.contextKey);
@@ -444,18 +285,17 @@ export function createSlackBridge(config, registry) {
444
285
  if (!options.fromQueue && await denyIfLocked(request)) {
445
286
  return;
446
287
  }
447
- const busy = getBusyReason(request.contextKey);
448
- if (busy.busy) {
449
- const item = options.fromQueue && isQueuedPrompt(envelope) ? envelope : promptStore.enqueue(request.contextKey, envelope);
450
- const position = promptStore.list(request.contextKey).findIndex((queued) => queued.id === item.id) + 1;
451
- const text = busy.kind === "external"
452
- ? `Queued prompt ${item.id} at position ${position}. The ${busy.agentLabel} session is still active and is processing a previous task.`
453
- : `Queued prompt ${item.id} at position ${position}.`;
454
- await reply(request, text, {
455
- buttons: [[{ label: "Cancel queued message", action: `slack_queue_cancel:${request.contextKey}:${item.id}` }]],
456
- });
457
- appendActivity(request, { status: "queued", type: "prompt_queued", prompt: item.description, detail: text });
458
- audit(request, { action: "prompt_queued", status: "ok", promptId: item.id, description: item.description });
288
+ if (await queueChannelPromptIfBusy({
289
+ request,
290
+ envelope,
291
+ fromQueue: options.fromQueue,
292
+ promptStore,
293
+ busy: getBusyReason(request.contextKey),
294
+ actionPrefix: "slack",
295
+ reply,
296
+ appendActivity,
297
+ audit,
298
+ })) {
459
299
  return;
460
300
  }
461
301
  const busyState = getBusyState(request.contextKey);
@@ -545,45 +385,76 @@ export function createSlackBridge(config, registry) {
545
385
  }
546
386
  };
547
387
  const deliverCliGeneratedArtifacts = async (contextKey, context, session, startedAt, turnId) => {
548
- if (!startedAt || !turnId)
549
- return;
550
- const state = externalMirrors.get(contextKey);
551
- if (state?.artifactsDeliveredForTurnId === turnId)
552
- return;
553
- const workspace = session.getInfo().workspace;
554
- const report = await collectRecentWorkspaceArtifacts(workspace, {
555
- since: startedAt,
556
- until: new Date(),
557
- maxFileSize: config.maxFileSize,
558
- limit: 5,
559
- ignoreDirs: config.artifactIgnoreDirs,
560
- ignoreGlobs: config.artifactIgnoreGlobs,
561
- });
562
- if (report.artifacts.length === 0 && report.skippedCount === 0 && !report.omittedCount) {
563
- if (state)
564
- state.artifactsDeliveredForTurnId = turnId;
565
- return;
566
- }
567
- const persisted = await persistWorkspaceArtifactReport(workspace, turnId, report).catch((error) => {
568
- console.error("Failed to persist Slack CLI artifact report:", error);
569
- return null;
388
+ await deliverChannelCliArtifacts({
389
+ config,
390
+ contextKey,
391
+ session,
392
+ startedAt,
393
+ turnId,
394
+ state: externalMirrors.get(contextKey),
395
+ autoSend: config.slackAutoSendArtifacts,
396
+ sendSummaryWhenAutoSendDisabled: true,
397
+ logPrefix: "Slack",
398
+ sendSummary: (summary) => runtime.sendMessage(context, { text: summary, fallbackText: summary }).then(() => { }),
399
+ sendArtifact: (artifact) => runtime.sendFile(context, { localPath: artifact.localPath, name: artifact.name }).then(() => { }).catch((error) => {
400
+ console.error(`Failed to send Slack CLI artifact ${artifact.name}:`, error);
401
+ }),
402
+ appendActivity: (input) => {
403
+ activityStore.append(input);
404
+ },
570
405
  });
571
- const summary = formatArtifactSummary(report.artifacts, report.skippedCount, report.omittedCount);
572
- if (summary) {
573
- await runtime.sendMessage(context, { text: summary, fallbackText: summary });
574
- }
575
- if (config.slackAutoSendArtifacts) {
576
- for (const artifact of (persisted?.artifacts ?? report.artifacts).slice(0, 5)) {
577
- await runtime.sendFile(context, { localPath: artifact.localPath, name: artifact.name }).catch((error) => {
578
- console.error(`Failed to send Slack CLI artifact ${artifact.name}:`, error);
579
- });
580
- }
581
- }
582
- const info = session.getInfo();
583
- activityStore.append({ source: "cli", status: "info", type: config.slackAutoSendArtifacts ? "artifacts_sent" : "artifacts_detected", contextKey, threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor: { channel: "cli", label: `${info.agentLabel} CLI` }, detail: summary });
584
- if (state)
585
- state.artifactsDeliveredForTurnId = turnId;
586
406
  };
407
+ const externalMirrorController = createChannelExternalMirrorController({
408
+ config,
409
+ states: externalMirrors,
410
+ typingIntervalMs: TYPING_INTERVAL_MS,
411
+ minUpdateMs: () => config.slackMirrorMinUpdateMs,
412
+ mirrorMode: (contextKey) => preferencesStore.get(contextKey).mirrorMode ?? config.slackMirrorMode,
413
+ queueLength: (contextKey) => promptStore.list(contextKey).length,
414
+ activityActor: (snapshot) => ({ channel: "cli", label: `${snapshot.agentLabel} CLI` }),
415
+ appendActivity: (input) => {
416
+ activityStore.append(input);
417
+ },
418
+ sendTyping: (_contextKey, context) => runtime.sendTyping(context).catch(() => { }),
419
+ sendWorkingNotice: async (_contextKey, context, state, snapshot, prompt) => {
420
+ const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
421
+ if (state.workingNoticeTurnKey === turnKey) {
422
+ return;
423
+ }
424
+ const text = prompt ? `*Working on* ${prompt}` : `*Working on* external ${snapshot.agentLabel} task...`;
425
+ await runtime.sendMessage(context, {
426
+ text,
427
+ fallbackText: prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`,
428
+ });
429
+ state.workingNoticeTurnKey = turnKey;
430
+ },
431
+ sendStatus: async (_contextKey, context, _state, rendered) => {
432
+ const sent = await runtime.sendMessage(context, { text: rendered.plain, fallbackText: rendered.plain });
433
+ return sent.messageId;
434
+ },
435
+ editStatus: (_contextKey, context, _state, messageId, rendered) => runtime.editMessage(context, messageId, { text: rendered.plain, fallbackText: rendered.plain }),
436
+ sendEvent: (_contextKey, context, _state, rendered) => runtime.sendMessage(context, { text: rendered.plain, fallbackText: rendered.plain }).then(() => { }),
437
+ sendDone: (_contextKey, context, state, text) => {
438
+ if (state.statusMessageId) {
439
+ return runtime.editMessage(context, state.statusMessageId, { text, fallbackText: text });
440
+ }
441
+ return runtime.sendMessage(context, { text, fallbackText: text }).then(() => { });
442
+ },
443
+ sendFinalAnswer: async (_contextKey, context, _state, snapshot, text) => {
444
+ await runtime.sendMessage(context, {
445
+ text: `*${snapshot.agentLabel} CLI final answer:*`,
446
+ fallbackText: `${snapshot.agentLabel} CLI final answer:`,
447
+ });
448
+ for (const chunk of splitSlackMessage(text)) {
449
+ await runtime.sendMessage(context, { text: chunk, fallbackText: chunk });
450
+ }
451
+ },
452
+ deliverArtifacts: (contextKey, context, session, state, turnId) => deliverCliGeneratedArtifacts(contextKey, context, session, state.startedAt, turnId),
453
+ fullEventFilter: () => true,
454
+ fullEventLimit: 6,
455
+ requirePreviousForTerminal: false,
456
+ });
457
+ const mirrorExternalSnapshot = externalMirrorController.mirror;
587
458
  const commandDispatcher = createSharedChannelCommandDispatcher({
588
459
  transport: "slack",
589
460
  bindings: [
@@ -1285,36 +1156,30 @@ export function createSlackBridge(config, registry) {
1285
1156
  return id;
1286
1157
  };
1287
1158
  const monitorExternalContexts = async () => {
1288
- const keys = new Set([...registry.listContexts().map((context) => context.contextKey), ...promptStore.listContextKeys()].filter(isSlackContextKey));
1289
- for (const contextKey of keys) {
1290
- const parsed = parseSlackContextKey(contextKey);
1291
- if (!parsed)
1292
- continue;
1293
- if (!canSendSystemMessagesToSlackContext(userStore, contextKey))
1294
- continue;
1295
- if (!isSlackTeamAllowed(parsed.teamId) || !isSlackChannelAllowedByEnv(parsed.channelId))
1296
- continue;
1297
- const session = await registry.getOrCreate(contextKey, { deferThreadStart: true }).catch(() => null);
1298
- if (!session)
1299
- continue;
1300
- const context = { channelId: "slack", chatId: parsed.channelId, topicId: parsed.threadTs };
1301
- const previous = externalMirrors.get(contextKey);
1302
- const snapshot = getExternalSnapshotForSession(session, config, { afterLine: previous?.lastLine ?? Number.MAX_SAFE_INTEGER }) ??
1303
- getExternalSnapshotForSession(session, config, { maxEvents: 1 });
1304
- if (snapshot && !session.isProcessing()) {
1305
- await mirrorExternalSnapshot(contextKey, context, session, snapshot);
1306
- }
1307
- if (snapshot?.activity.active) {
1308
- if (promptStore.list(contextKey).length > 0) {
1309
- await updateQueueStatusMessage(contextKey, context, `Waiting for ${snapshot.agentLabel} CLI task... ${promptStore.list(contextKey).length} queued${promptStore.isPaused(contextKey) ? " (paused)" : ""}.`).catch(() => { });
1310
- }
1311
- continue;
1312
- }
1313
- if (promptStore.list(contextKey).length > 0 && !promptStore.isPaused(contextKey) && !session.isProcessing()) {
1314
- await updateQueueStatusMessage(contextKey, context, `CLI task finished, running queued prompt 1/${promptStore.list(contextKey).length}.`).catch(() => { });
1159
+ await monitorChannelExternalContexts({
1160
+ config,
1161
+ registry,
1162
+ promptStore,
1163
+ isContextKey: isSlackContextKey,
1164
+ canSendSystemMessages: (contextKey) => canSendSystemMessagesToSlackContext(userStore, contextKey),
1165
+ isAllowed: (contextKey) => {
1166
+ const parsed = parseSlackContextKey(contextKey);
1167
+ return Boolean(parsed && isSlackTeamAllowed(parsed.teamId) && isSlackChannelAllowedByEnv(parsed.channelId));
1168
+ },
1169
+ contextForKey: (contextKey) => {
1170
+ const parsed = parseSlackContextKey(contextKey);
1171
+ return parsed ? { channelId: "slack", chatId: parsed.channelId, ...(parsed.threadTs ? { topicId: parsed.threadTs } : {}) } : null;
1172
+ },
1173
+ previousLastLine: (contextKey) => externalMirrors.get(contextKey)?.lastLine,
1174
+ mirrorSnapshot: mirrorExternalSnapshot,
1175
+ updateQueueStatus: updateQueueStatusMessage,
1176
+ drainQueue: async (contextKey, context) => {
1177
+ const parsed = parseSlackContextKey(contextKey);
1178
+ if (!parsed)
1179
+ return;
1315
1180
  await drainQueue({ contextKey, context, userId: "system", channelId: parsed.channelId, teamId: parsed.teamId, isDirectMessage: false, source: "system" });
1316
- }
1317
- }
1181
+ },
1182
+ });
1318
1183
  };
1319
1184
  app.event("message", async ({ event }) => {
1320
1185
  await handleMessage(event);
@@ -1456,6 +1321,3 @@ function inferMimeType(name) {
1456
1321
  return "audio/webm";
1457
1322
  return "application/octet-stream";
1458
1323
  }
1459
- function isQueuedPrompt(value) {
1460
- return Boolean(value && typeof value === "object" && "id" in value && "contextKey" in value);
1461
- }
@@ -1,6 +1,6 @@
1
1
  import { createReadStream } from "node:fs";
2
- import { SlackChannelAdapter, } from "./channel-adapter.js";
3
- import { redactText } from "./redaction.js";
2
+ import { SlackChannelAdapter, } from "../shared/channel-adapter.js";
3
+ import { redactText } from "../../core/redaction.js";
4
4
  import { slackRateLimiter } from "./slack-rate-limit.js";
5
5
  const SLACK_TEXT_LIMIT = 40000;
6
6
  const SLACK_SAFE_TEXT_LIMIT = 3500;
@@ -1,5 +1,5 @@
1
- import { permissionForCommand } from "./access-control.js";
2
- import { normalizeChannelCommandName, parseChannelCommand } from "./channel-runtime.js";
1
+ import { permissionForCommand } from "../../access/access-control.js";
2
+ import { normalizeChannelCommandName, parseChannelCommand } from "../shared/channel-runtime.js";
3
3
  export function parseSlackMessageCommand(text) {
4
4
  return parseChannelCommand(text, { allowBotMention: false });
5
5
  }
@@ -1,6 +1,6 @@
1
1
  import { WebClient } from "@slack/web-api";
2
- import { friendlyErrorText } from "./error-messages.js";
3
- import { UserStore } from "./user-management.js";
2
+ import { friendlyErrorText } from "../../core/error-messages.js";
3
+ import { UserStore } from "../../access/user-management.js";
4
4
  export async function collectSlackDiagnostics(input) {
5
5
  const { config } = input;
6
6
  const checks = [];
@@ -1,4 +1,4 @@
1
- import { escapeHTML } from "./format.js";
1
+ import { escapeHTML } from "../../core/format.js";
2
2
  /**
3
3
  * Grouped command reference for /help.
4
4
  */