@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.
- package/.env.example +9 -0
- package/README.md +81 -1197
- package/dist/{access-control.js → access/access-control.js} +1 -1
- package/dist/{audit-log.js → access/audit-log.js} +2 -2
- package/dist/{session-locks.js → access/session-locks.js} +1 -1
- package/dist/{user-management.js → access/user-management.js} +1 -1
- package/dist/{claude-code-cli.js → agents/claude-code/claude-code-cli.js} +2 -2
- package/dist/{claude-code-session.js → agents/claude-code/claude-code-session.js} +1 -1
- package/dist/{codex-cli.js → agents/codex/codex-cli.js} +14 -5
- package/dist/{codex-session.js → agents/codex/codex-session.js} +2 -4
- package/dist/{hermes-cli.js → agents/hermes/hermes-cli.js} +2 -2
- package/dist/{hermes-launch.js → agents/hermes/hermes-launch.js} +1 -1
- package/dist/{hermes-session.js → agents/hermes/hermes-session.js} +1 -1
- package/dist/{openclaw-cli.js → agents/openclaw/openclaw-cli.js} +2 -2
- package/dist/{openclaw-launch.js → agents/openclaw/openclaw-launch.js} +1 -1
- package/dist/{openclaw-session.js → agents/openclaw/openclaw-session.js} +1 -1
- package/dist/{pi-cli.js → agents/pi/pi-cli.js} +2 -2
- package/dist/{pi-launch.js → agents/pi/pi-launch.js} +1 -1
- package/dist/{pi-session.js → agents/pi/pi-session.js} +1 -1
- package/dist/{adapter-conformance.js → agents/shared/adapter-conformance.js} +2 -2
- package/dist/{agent-activity.js → agents/shared/agent-activity.js} +5 -5
- package/dist/agents/shared/agent-auth-commands.js +30 -0
- package/dist/{agent-factory.js → agents/shared/agent-factory.js} +5 -5
- package/dist/{agent-feature-matrix.js → agents/shared/agent-feature-matrix.js} +2 -2
- package/dist/{agent-updates.js → agents/shared/agent-updates.js} +7 -7
- package/dist/{discord-artifacts.js → channels/discord/discord-artifacts.js} +4 -4
- package/dist/{discord-bot.js → channels/discord/discord-bot.js} +164 -424
- package/dist/{discord-channel-runtime.js → channels/discord/discord-channel-runtime.js} +2 -2
- package/dist/{discord-command-surface.js → channels/discord/discord-command-surface.js} +3 -3
- package/dist/{bot-rendering.js → channels/shared/bot-rendering.js} +6 -6
- package/dist/{channel-actions.js → channels/shared/channel-actions.js} +4 -4
- package/dist/channels/shared/channel-bridge-controller.js +69 -0
- package/dist/channels/shared/channel-cli-artifacts.js +51 -0
- package/dist/{channel-command-service.js → channels/shared/channel-command-service.js} +51 -28
- package/dist/channels/shared/channel-external-mirror-controller.js +193 -0
- package/dist/channels/shared/channel-external-monitor.js +52 -0
- package/dist/{channel-mirror-registry.js → channels/shared/channel-mirror-registry.js} +14 -6
- package/dist/{channel-peer-prompt.js → channels/shared/channel-peer-prompt.js} +3 -3
- package/dist/{channel-turn-service.js → channels/shared/channel-turn-service.js} +2 -2
- package/dist/{context-key.js → channels/shared/context-key.js} +1 -1
- package/dist/{session-format.js → channels/shared/session-format.js} +2 -2
- package/dist/{slack-artifacts.js → channels/slack/slack-artifacts.js} +4 -4
- package/dist/{slack-bot.js → channels/slack/slack-bot.js} +159 -294
- package/dist/{slack-channel-runtime.js → channels/slack/slack-channel-runtime.js} +2 -2
- package/dist/{slack-command-surface.js → channels/slack/slack-command-surface.js} +2 -2
- package/dist/{slack-diagnostics.js → channels/slack/slack-diagnostics.js} +2 -2
- package/dist/{bot-ui.js → channels/telegram/bot-ui.js} +1 -1
- package/dist/{bot.js → channels/telegram/bot.js} +178 -427
- package/dist/{telegram-access-commands.js → channels/telegram/telegram-access-commands.js} +3 -3
- package/dist/{telegram-access-middleware.js → channels/telegram/telegram-access-middleware.js} +4 -4
- package/dist/{telegram-agent-commands.js → channels/telegram/telegram-agent-commands.js} +9 -9
- package/dist/{telegram-artifact-commands.js → channels/telegram/telegram-artifact-commands.js} +4 -4
- package/dist/{telegram-channel-runtime.js → channels/telegram/telegram-channel-runtime.js} +2 -2
- package/dist/{telegram-command-menu.js → channels/telegram/telegram-command-menu.js} +1 -1
- package/dist/{telegram-diagnostics-command.js → channels/telegram/telegram-diagnostics-command.js} +7 -7
- package/dist/{telegram-general-commands.js → channels/telegram/telegram-general-commands.js} +4 -4
- package/dist/{telegram-operational-commands.js → channels/telegram/telegram-operational-commands.js} +5 -5
- package/dist/{telegram-output.js → channels/telegram/telegram-output.js} +2 -2
- package/dist/{telegram-preference-commands.js → channels/telegram/telegram-preference-commands.js} +3 -3
- package/dist/{telegram-queue-commands.js → channels/telegram/telegram-queue-commands.js} +6 -6
- package/dist/{telegram-support-command.js → channels/telegram/telegram-support-command.js} +4 -4
- package/dist/{telegram-update-commands.js → channels/telegram/telegram-update-commands.js} +5 -5
- package/dist/{config-metadata.js → core/config-metadata.js} +8 -0
- package/dist/{config.js → core/config.js} +11 -3
- package/dist/index.js +27 -23
- package/dist/{peer-client.js → peers/peer-client.js} +57 -1
- package/dist/peers/peer-discovery-jobs.js +206 -0
- package/dist/peers/peer-discovery.js +223 -0
- package/dist/peers/peer-health-monitor.js +49 -0
- package/dist/{peer-identity.js → peers/peer-identity.js} +50 -1
- package/dist/{peer-runtime-service.js → peers/peer-runtime-service.js} +29 -7
- package/dist/{peer-server.js → peers/peer-server.js} +23 -6
- package/dist/{peer-store.js → peers/peer-store.js} +84 -11
- package/dist/{peer-types.js → peers/peer-types.js} +9 -0
- package/dist/peers/peer-web-proxy-contract.js +127 -0
- package/dist/{metrics.js → runtime/metrics.js} +5 -3
- package/dist/{relay-artifact-service.js → runtime/relay-artifact-service.js} +1 -1
- package/dist/runtime/relay-auth-service.js +63 -0
- package/dist/runtime/relay-dashboard-service.js +139 -0
- package/dist/{relay-external-activity-monitor.js → runtime/relay-external-activity-monitor.js} +140 -53
- package/dist/runtime/relay-runtime-active-sessions.js +387 -0
- package/dist/runtime/relay-runtime-dashboard.js +201 -0
- package/dist/runtime/relay-runtime-prompt-queue-artifacts.js +307 -0
- package/dist/runtime/relay-runtime-sessions.js +623 -0
- package/dist/runtime/relay-runtime-types.js +1 -0
- package/dist/runtime/relay-runtime-updates-jobs.js +360 -0
- package/dist/runtime/relay-runtime.js +451 -0
- package/dist/runtime/runtime-cache.js +117 -0
- package/dist/{session-registry.js → state/session-registry.js} +3 -3
- package/dist/{operations.js → support/operations.js} +7 -7
- package/dist/{support-bundle.js → support/support-bundle.js} +1 -1
- package/dist/{web-api-contract.js → web/web-api-contract.js} +17 -3
- package/dist/web/web-api-types.js +1 -0
- package/dist/{web-dashboard-access-routes.js → web/web-dashboard-access-routes.js} +2 -2
- package/dist/{web-dashboard-assets.js → web/web-dashboard-assets.js} +24 -2
- package/dist/{web-dashboard-http.js → web/web-dashboard-http.js} +41 -5
- package/dist/{web-dashboard-pages.js → web/web-dashboard-pages.js} +37 -10
- package/dist/{web-dashboard-peer-routes.js → web/web-dashboard-peer-routes.js} +102 -7
- package/dist/web/web-dashboard-security.js +14 -0
- package/dist/{web-dashboard-session-routes.js → web/web-dashboard-session-routes.js} +12 -1
- package/dist/{web-dashboard.js → web/web-dashboard.js} +132 -48
- package/dist/web/web-performance.js +60 -0
- package/dist/web/web-rate-limit.js +19 -0
- package/dist/{web-state.js → web/web-state.js} +74 -5
- package/dist/webui-assets/dashboard.css +171 -10
- package/dist/webui-assets/dashboard.js +515 -48
- package/dist/webui-assets/favicon.ico +0 -0
- package/dist/webui-assets/favicon.png +0 -0
- package/dist/webui-assets/logo.png +0 -0
- package/package.json +4 -3
- package/plugins/nordrelay/scripts/nordrelay.mjs +17 -5
- package/{launchd/start.sh → scripts/launchd-start.sh} +1 -1
- package/dist/relay-runtime.js +0 -1916
- package/dist/runtime-cache.js +0 -57
- /package/dist/{user-management-crypto.js → access/user-management-crypto.js} +0 -0
- /package/dist/{user-management-normalize.js → access/user-management-normalize.js} +0 -0
- /package/dist/{user-management-types.js → access/user-management-types.js} +0 -0
- /package/dist/{claude-code-auth.js → agents/claude-code/claude-code-auth.js} +0 -0
- /package/dist/{claude-code-launch.js → agents/claude-code/claude-code-launch.js} +0 -0
- /package/dist/{claude-code-state.js → agents/claude-code/claude-code-state.js} +0 -0
- /package/dist/{codex-auth.js → agents/codex/codex-auth.js} +0 -0
- /package/dist/{codex-config.js → agents/codex/codex-config.js} +0 -0
- /package/dist/{codex-launch.js → agents/codex/codex-launch.js} +0 -0
- /package/dist/{codex-state.js → agents/codex/codex-state.js} +0 -0
- /package/dist/{hermes-api.js → agents/hermes/hermes-api.js} +0 -0
- /package/dist/{hermes-auth.js → agents/hermes/hermes-auth.js} +0 -0
- /package/dist/{hermes-state.js → agents/hermes/hermes-state.js} +0 -0
- /package/dist/{openclaw-auth.js → agents/openclaw/openclaw-auth.js} +0 -0
- /package/dist/{openclaw-gateway.js → agents/openclaw/openclaw-gateway.js} +0 -0
- /package/dist/{openclaw-state.js → agents/openclaw/openclaw-state.js} +0 -0
- /package/dist/{pi-auth.js → agents/pi/pi-auth.js} +0 -0
- /package/dist/{pi-rpc.js → agents/pi/pi-rpc.js} +0 -0
- /package/dist/{pi-state.js → agents/pi/pi-state.js} +0 -0
- /package/dist/{agent-adapter.js → agents/shared/agent-adapter.js} +0 -0
- /package/dist/{agent.js → agents/shared/agent.js} +0 -0
- /package/dist/{artifacts.js → artifacts/artifacts.js} +0 -0
- /package/dist/{attachments.js → artifacts/attachments.js} +0 -0
- /package/dist/{voice.js → artifacts/voice.js} +0 -0
- /package/dist/{discord-rate-limit.js → channels/discord/discord-rate-limit.js} +0 -0
- /package/dist/{channel-adapter.js → channels/shared/channel-adapter.js} +0 -0
- /package/dist/{relay-runtime-types.js → channels/shared/channel-bridge-state.js} +0 -0
- /package/dist/{channel-command-catalog.js → channels/shared/channel-command-catalog.js} +0 -0
- /package/dist/{channel-command-core.js → channels/shared/channel-command-core.js} +0 -0
- /package/dist/{channel-prompt-engine.js → channels/shared/channel-prompt-engine.js} +0 -0
- /package/dist/{channel-runtime.js → channels/shared/channel-runtime.js} +0 -0
- /package/dist/{channel-turn-lifecycle.js → channels/shared/channel-turn-lifecycle.js} +0 -0
- /package/dist/{slack-rate-limit.js → channels/slack/slack-rate-limit.js} +0 -0
- /package/dist/{telegram-command-types.js → channels/telegram/telegram-command-types.js} +0 -0
- /package/dist/{telegram-rate-limit.js → channels/telegram/telegram-rate-limit.js} +0 -0
- /package/dist/{activity-events.js → core/activity-events.js} +0 -0
- /package/dist/{error-messages.js → core/error-messages.js} +0 -0
- /package/dist/{format.js → core/format.js} +0 -0
- /package/dist/{logger.js → core/logger.js} +0 -0
- /package/dist/{redaction.js → core/redaction.js} +0 -0
- /package/dist/{settings-service.js → core/settings-service.js} +0 -0
- /package/dist/{settings-wizard-test.js → core/settings-wizard-test.js} +0 -0
- /package/dist/{workspace-policy.js → core/workspace-policy.js} +0 -0
- /package/dist/{peer-auth.js → peers/peer-auth.js} +0 -0
- /package/dist/{peer-context.js → peers/peer-context.js} +0 -0
- /package/dist/{peer-readiness.js → peers/peer-readiness.js} +0 -0
- /package/dist/{relay-queue-service.js → runtime/relay-queue-service.js} +0 -0
- /package/dist/{web-api-types.js → runtime/relay-runtime-delegate.js} +0 -0
- /package/dist/{relay-runtime-helpers.js → runtime/relay-runtime-helpers.js} +0 -0
- /package/dist/{remote-prompt.js → runtime/remote-prompt.js} +0 -0
- /package/dist/{bot-preferences.js → state/bot-preferences.js} +0 -0
- /package/dist/{job-store.js → state/job-store.js} +0 -0
- /package/dist/{persistence.js → state/persistence.js} +0 -0
- /package/dist/{prompt-store.js → state/prompt-store.js} +0 -0
- /package/dist/{state-backend.js → state/state-backend.js} +0 -0
- /package/dist/{zip-writer.js → support/zip-writer.js} +0 -0
- /package/dist/{web-dashboard-artifact-routes.js → web/web-dashboard-artifact-routes.js} +0 -0
- /package/dist/{web-dashboard-runtime-routes.js → web/web-dashboard-runtime-routes.js} +0 -0
- /package/dist/{web-dashboard-ui.js → web/web-dashboard-ui.js} +0 -0
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Client, Events, GatewayIntentBits, Partials, REST, Routes, } from "discord.js";
|
|
3
|
-
import { ADMIN_GROUP_ID } from "
|
|
4
|
-
import { agentLabel, agentReasoningLabel, agentReasoningOptions } from "
|
|
5
|
-
import { getAgentActivityLog, getExternalSnapshotForSession } from "
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
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, formatLocalDateTime, parseActivityOptions, renderPromptFailure, trimLine } from "../shared/bot-rendering.js";
|
|
15
|
+
import { 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 { ChannelCommandService } from "../shared/channel-command-service.js";
|
|
19
|
+
import { discordHelpCommandList } from "../shared/channel-command-catalog.js";
|
|
20
|
+
import { createChannelPromptEngine } from "../shared/channel-prompt-engine.js";
|
|
21
|
+
import { runChannelPeerPrompt } from "../shared/channel-peer-prompt.js";
|
|
22
|
+
import { deliverChannelAction } from "../shared/channel-runtime.js";
|
|
23
|
+
import { deliverChannelCliArtifacts } from "../shared/channel-cli-artifacts.js";
|
|
24
|
+
import { createChannelExternalMirrorController } from "../shared/channel-external-mirror-controller.js";
|
|
25
|
+
import { monitorChannelExternalContexts } from "../shared/channel-external-monitor.js";
|
|
26
|
+
import { discordContextKey, isDiscordContextKey, parseDiscordContextKey } from "../shared/context-key.js";
|
|
24
27
|
import { DiscordBotChannelRuntime, actionFromDiscordCustomId, discordActionRows, splitDiscordMessage, trimDiscordMessage } from "./discord-channel-runtime.js";
|
|
25
28
|
import { createDiscordArtifactCommandHandler, sendRecentDiscordArtifacts } from "./discord-artifacts.js";
|
|
26
29
|
import { argumentFromDiscordInteraction, discordCommands, isUnauthenticatedDiscordCommandAllowed, parseDiscordMessageCommand, permissionForDiscordAction, requiredPermissionForDiscordCommand } from "./discord-command-surface.js";
|
|
27
30
|
import { discordRateLimiter, getDiscordRateLimitMetrics } from "./discord-rate-limit.js";
|
|
28
|
-
import { friendlyErrorText } from "
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import { UserStore } from "./user-management.js";
|
|
43
|
-
import { WebActivityStore } from "./web-state.js";
|
|
31
|
+
import { friendlyErrorText } from "../../core/error-messages.js";
|
|
32
|
+
import { spawnConnectorRestart, spawnSelfUpdate } from "../../support/operations.js";
|
|
33
|
+
import { RemoteRelayClient } from "../../peers/peer-client.js";
|
|
34
|
+
import { PromptStore, toPromptEnvelope } from "../../state/prompt-store.js";
|
|
35
|
+
import { RelayArtifactService } from "../../runtime/relay-artifact-service.js";
|
|
36
|
+
import { RelayAuthService } from "../../runtime/relay-auth-service.js";
|
|
37
|
+
import { configureRedaction, redactText } from "../../core/redaction.js";
|
|
38
|
+
import { renderSessionInfoPlain } from "../shared/session-format.js";
|
|
39
|
+
import { canWriteWithLock, SessionLockStore } from "../../access/session-locks.js";
|
|
40
|
+
import { SessionRegistry } from "../../state/session-registry.js";
|
|
41
|
+
import { transcribeAudio } from "../../artifacts/voice.js";
|
|
42
|
+
import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "../../core/workspace-policy.js";
|
|
43
|
+
import { UserStore } from "../../access/user-management.js";
|
|
44
|
+
import { WebActivityStore } from "../../web/web-state.js";
|
|
44
45
|
export { isUnauthenticatedDiscordCommandAllowed, permissionForDiscordAction, requiredPermissionForDiscordCommand } from "./discord-command-surface.js";
|
|
45
46
|
const EDIT_DEBOUNCE_MS = 1500;
|
|
46
47
|
const TYPING_INTERVAL_MS = 4500;
|
|
@@ -75,24 +76,23 @@ export function createDiscordBridge(config, registry) {
|
|
|
75
76
|
const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
76
77
|
const userStore = new UserStore();
|
|
77
78
|
const artifactService = new RelayArtifactService(config);
|
|
79
|
+
const authService = new RelayAuthService(config);
|
|
78
80
|
const agentUpdates = new AgentUpdateManager();
|
|
79
81
|
const commandService = new ChannelCommandService(config);
|
|
80
|
-
const busyStates =
|
|
82
|
+
const busyStates = createChannelBusyStore();
|
|
81
83
|
const turnProgress = new Map();
|
|
82
84
|
const draining = new Set();
|
|
83
85
|
const picks = new Map();
|
|
84
86
|
const responseOwners = new Map();
|
|
85
87
|
const externalMirrors = new Map();
|
|
86
|
-
const queueStatusMessages =
|
|
88
|
+
const queueStatusMessages = createChannelQueueStatusController({
|
|
89
|
+
send: async (_contextKey, context, text) => (await runtime.sendMessage(context, { text, fallbackText: text })).messageId,
|
|
90
|
+
edit: async (_contextKey, context, messageId, text) => {
|
|
91
|
+
await runtime.editMessage(context, messageId, { text, fallbackText: text });
|
|
92
|
+
},
|
|
93
|
+
});
|
|
87
94
|
let externalMonitor;
|
|
88
|
-
const getBusyState = (contextKey) =>
|
|
89
|
-
let state = busyStates.get(contextKey);
|
|
90
|
-
if (!state) {
|
|
91
|
-
state = { processing: false, switching: false };
|
|
92
|
-
busyStates.set(contextKey, state);
|
|
93
|
-
}
|
|
94
|
-
return state;
|
|
95
|
-
};
|
|
95
|
+
const getBusyState = (contextKey) => busyStates.get(contextKey);
|
|
96
96
|
const actorFor = (request) => ({
|
|
97
97
|
channel: "discord",
|
|
98
98
|
id: request.authUser?.user.id ?? `discord:${request.user.id}`,
|
|
@@ -100,27 +100,19 @@ export function createDiscordBridge(config, registry) {
|
|
|
100
100
|
username: request.authUser?.user.email ?? request.user.username,
|
|
101
101
|
channelUserId: request.user.id,
|
|
102
102
|
});
|
|
103
|
-
const appendActivity = (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
contextKey: input.contextKey ?? request.contextKey,
|
|
117
|
-
actor: input.actor ?? actorFor(request),
|
|
118
|
-
actorId: request.authUser?.user.id ?? request.user.id,
|
|
119
|
-
actorRole: request.authUser?.groups.map((group) => group.name).join(", ") ?? "unauthenticated",
|
|
120
|
-
...input,
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
const hasPermission = (request, permission) => userStore.hasPermission(request.authUser, permission);
|
|
103
|
+
const appendActivity = createChannelActivityRecorder({
|
|
104
|
+
source: "discord",
|
|
105
|
+
workspace: config.workspace,
|
|
106
|
+
activityStore,
|
|
107
|
+
actorFor,
|
|
108
|
+
});
|
|
109
|
+
const audit = createChannelAuditRecorder({
|
|
110
|
+
channelId: "discord",
|
|
111
|
+
auditLog,
|
|
112
|
+
actorFor,
|
|
113
|
+
actorIdFor: (request) => request.user.id,
|
|
114
|
+
});
|
|
115
|
+
const hasPermission = createChannelPermissionChecker(userStore);
|
|
124
116
|
const reply = async (request, content, options = {}) => {
|
|
125
117
|
const chunks = splitDiscordMessage(content);
|
|
126
118
|
if (request.interaction) {
|
|
@@ -232,7 +224,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
232
224
|
};
|
|
233
225
|
const commandArtifacts = createDiscordArtifactCommandHandler(artifactDeps);
|
|
234
226
|
const getBusyReason = (contextKey) => {
|
|
235
|
-
const state = busyStates.
|
|
227
|
+
const state = busyStates.peek(contextKey);
|
|
236
228
|
const session = registry.get(contextKey);
|
|
237
229
|
if (state?.processing || state?.switching || session?.isProcessing()) {
|
|
238
230
|
return { busy: true, kind: "connector", state: state ?? getBusyState(contextKey) };
|
|
@@ -244,210 +236,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
244
236
|
return { busy: false, kind: "idle" };
|
|
245
237
|
};
|
|
246
238
|
const updateQueueStatusMessage = async (contextKey, context, text) => {
|
|
247
|
-
|
|
248
|
-
if (state.lastText === text && state.messageId) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (!state.messageId) {
|
|
252
|
-
const sent = await runtime.sendMessage(context, { text, fallbackText: text });
|
|
253
|
-
state.messageId = sent.messageId;
|
|
254
|
-
state.lastText = text;
|
|
255
|
-
queueStatusMessages.set(contextKey, state);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
await runtime.editMessage(context, state.messageId, { text, fallbackText: text });
|
|
259
|
-
state.lastText = text;
|
|
260
|
-
queueStatusMessages.set(contextKey, state);
|
|
261
|
-
};
|
|
262
|
-
const sendExternalMirrorTyping = async (context, state) => {
|
|
263
|
-
const now = Date.now();
|
|
264
|
-
if (state.lastTypingAt && now - state.lastTypingAt < TYPING_INTERVAL_MS) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
state.lastTypingAt = now;
|
|
268
|
-
await runtime.sendTyping(context).catch(() => { });
|
|
269
|
-
};
|
|
270
|
-
const sendExternalWorkingNotice = async (context, state, snapshot) => {
|
|
271
|
-
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
272
|
-
if (state.workingNoticeTurnKey === turnKey) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const prompt = trimLine(snapshot.latestUserMessage ?? "", 250);
|
|
276
|
-
const text = prompt
|
|
277
|
-
? `**Working on** ${prompt}`
|
|
278
|
-
: `**Working on** external ${snapshot.agentLabel} task...`;
|
|
279
|
-
await runtime.sendMessage(context, {
|
|
280
|
-
text,
|
|
281
|
-
fallbackText: prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`,
|
|
282
|
-
});
|
|
283
|
-
state.workingNoticeTurnKey = turnKey;
|
|
284
|
-
};
|
|
285
|
-
const mirrorExternalSnapshot = async (contextKey, context, session, snapshot) => {
|
|
286
|
-
const previous = externalMirrors.get(contextKey);
|
|
287
|
-
let state = previous;
|
|
288
|
-
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.sourcePath) {
|
|
289
|
-
state = {
|
|
290
|
-
threadId: snapshot.threadId,
|
|
291
|
-
rolloutPath: snapshot.sourcePath,
|
|
292
|
-
lastLine: snapshot.lineCount,
|
|
293
|
-
turnId: snapshot.activity.turnId,
|
|
294
|
-
startedAt: snapshot.activity.startedAt,
|
|
295
|
-
};
|
|
296
|
-
externalMirrors.set(contextKey, state);
|
|
297
|
-
}
|
|
298
|
-
const mirrorMode = preferencesStore.get(contextKey).mirrorMode ?? config.discordMirrorMode;
|
|
299
|
-
if (snapshot.activity.active) {
|
|
300
|
-
state.turnId = snapshot.activity.turnId;
|
|
301
|
-
state.startedAt = snapshot.activity.startedAt;
|
|
302
|
-
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
303
|
-
if (state.activityStartedTurnKey !== turnKey) {
|
|
304
|
-
const info = session.getInfo();
|
|
305
|
-
activityStore.append({
|
|
306
|
-
source: "cli",
|
|
307
|
-
status: "running",
|
|
308
|
-
type: "cli_turn_started",
|
|
309
|
-
contextKey,
|
|
310
|
-
threadId: snapshot.threadId,
|
|
311
|
-
workspace: info.workspace,
|
|
312
|
-
agentId: info.agentId,
|
|
313
|
-
actor: { channel: "cli", label: `${snapshot.agentLabel} CLI` },
|
|
314
|
-
prompt: snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`,
|
|
315
|
-
detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
|
|
316
|
-
});
|
|
317
|
-
state.activityStartedTurnKey = turnKey;
|
|
318
|
-
state.activityFinishedTurnKey = undefined;
|
|
319
|
-
state.activityToolStartLines = [];
|
|
320
|
-
state.activityToolEndLines = [];
|
|
321
|
-
}
|
|
322
|
-
if (mirrorMode !== "off") {
|
|
323
|
-
await sendExternalMirrorTyping(context, state);
|
|
324
|
-
}
|
|
325
|
-
if (mirrorMode === "final") {
|
|
326
|
-
await sendExternalWorkingNotice(context, state, snapshot);
|
|
327
|
-
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (mirrorMode === "off") {
|
|
331
|
-
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
const status = renderExternalMirrorStatus(snapshot, promptStore.list(contextKey).length);
|
|
335
|
-
const statusMessage = { text: status.html, fallbackText: status.plain, parseMode: "html" };
|
|
336
|
-
const now = Date.now();
|
|
337
|
-
const canUpdateStatus = !state.latestStatusAt || now - state.latestStatusAt >= config.discordMirrorMinUpdateMs;
|
|
338
|
-
if (!state.statusMessageId) {
|
|
339
|
-
const sent = await runtime.sendMessage(context, statusMessage);
|
|
340
|
-
state.statusMessageId = sent.messageId;
|
|
341
|
-
state.latestStatusAt = now;
|
|
342
|
-
}
|
|
343
|
-
else if (state.latestStatus !== status.plain && canUpdateStatus) {
|
|
344
|
-
await runtime.editMessage(context, state.statusMessageId, statusMessage);
|
|
345
|
-
state.latestStatusAt = now;
|
|
346
|
-
}
|
|
347
|
-
state.latestStatus = status.plain;
|
|
348
|
-
if (mirrorMode === "full") {
|
|
349
|
-
const newEvents = snapshot.events
|
|
350
|
-
.filter((event) => event.lineNumber > (state.latestMirroredEventLine ?? state.lastLine))
|
|
351
|
-
.filter((event) => event.kind === "tool" || event.kind === "task")
|
|
352
|
-
.slice(-4);
|
|
353
|
-
for (const event of newEvents) {
|
|
354
|
-
const rendered = renderExternalMirrorEvent(event);
|
|
355
|
-
if (!rendered) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
await deliverChannelAction(runtime, context, rendered);
|
|
359
|
-
state.latestMirroredEventLine = event.lineNumber;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const info = session.getInfo();
|
|
363
|
-
const loggedStartLines = new Set(state.activityToolStartLines ?? []);
|
|
364
|
-
const loggedEndLines = new Set(state.activityToolEndLines ?? []);
|
|
365
|
-
for (const event of snapshot.events.filter((event) => event.lineNumber > state.lastLine && event.kind === "tool")) {
|
|
366
|
-
if (event.status === "started" && !loggedStartLines.has(event.lineNumber)) {
|
|
367
|
-
activityStore.append({
|
|
368
|
-
source: "cli",
|
|
369
|
-
status: "running",
|
|
370
|
-
type: "cli_tool_started",
|
|
371
|
-
contextKey,
|
|
372
|
-
threadId: snapshot.threadId,
|
|
373
|
-
workspace: info.workspace,
|
|
374
|
-
agentId: info.agentId,
|
|
375
|
-
actor: { channel: "cli", label: `${snapshot.agentLabel} CLI` },
|
|
376
|
-
prompt: snapshot.latestUserMessage ?? undefined,
|
|
377
|
-
detail: event.toolName ?? "tool",
|
|
378
|
-
});
|
|
379
|
-
loggedStartLines.add(event.lineNumber);
|
|
380
|
-
}
|
|
381
|
-
if ((event.status === "finished" || event.status === "failed") && !loggedEndLines.has(event.lineNumber)) {
|
|
382
|
-
activityStore.append({
|
|
383
|
-
source: "cli",
|
|
384
|
-
status: event.status === "failed" ? "failed" : "completed",
|
|
385
|
-
type: event.status === "failed" ? "cli_tool_failed" : "cli_tool_completed",
|
|
386
|
-
contextKey,
|
|
387
|
-
threadId: snapshot.threadId,
|
|
388
|
-
workspace: info.workspace,
|
|
389
|
-
agentId: info.agentId,
|
|
390
|
-
actor: { channel: "cli", label: `${snapshot.agentLabel} CLI` },
|
|
391
|
-
prompt: snapshot.latestUserMessage ?? undefined,
|
|
392
|
-
detail: event.toolName ?? "tool",
|
|
393
|
-
});
|
|
394
|
-
loggedEndLines.add(event.lineNumber);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
state.activityToolStartLines = [...loggedStartLines].slice(-200);
|
|
398
|
-
state.activityToolEndLines = [...loggedEndLines].slice(-200);
|
|
399
|
-
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (!previous) {
|
|
403
|
-
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
407
|
-
if (terminalEvent) {
|
|
408
|
-
const turnKey = terminalEvent.turnId ?? snapshot.activity.turnId ?? state.startedAt?.toString() ?? "unknown";
|
|
409
|
-
if (state.activityFinishedTurnKey !== turnKey) {
|
|
410
|
-
const info = session.getInfo();
|
|
411
|
-
const startedAt = state.startedAt instanceof Date ? state.startedAt : state.startedAt ? new Date(state.startedAt) : snapshot.activity.startedAt;
|
|
412
|
-
activityStore.append({
|
|
413
|
-
source: "cli",
|
|
414
|
-
status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
|
|
415
|
-
type: "cli_turn_finished",
|
|
416
|
-
contextKey,
|
|
417
|
-
threadId: snapshot.threadId,
|
|
418
|
-
workspace: info.workspace,
|
|
419
|
-
agentId: info.agentId,
|
|
420
|
-
actor: { channel: "cli", label: `${snapshot.agentLabel} CLI` },
|
|
421
|
-
prompt: snapshot.latestUserMessage ?? undefined,
|
|
422
|
-
detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
|
|
423
|
-
durationMs: startedAt && terminalEvent.timestamp ? Math.max(0, terminalEvent.timestamp.getTime() - startedAt.getTime()) : undefined,
|
|
424
|
-
});
|
|
425
|
-
state.activityFinishedTurnKey = turnKey;
|
|
426
|
-
}
|
|
427
|
-
if (mirrorMode !== "off") {
|
|
428
|
-
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
429
|
-
if (state.statusMessageId) {
|
|
430
|
-
await runtime.editMessage(context, state.statusMessageId, { text: doneText, fallbackText: doneText });
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
await runtime.sendMessage(context, { text: doneText, fallbackText: doneText });
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
|
|
437
|
-
if (mirrorMode !== "off" && mirrorMode !== "status" && finalAgent?.text && finalAgent.lineNumber !== state.latestAgentLine) {
|
|
438
|
-
await runtime.sendMessage(context, {
|
|
439
|
-
text: `**${snapshot.agentLabel} CLI final answer:**`,
|
|
440
|
-
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
441
|
-
});
|
|
442
|
-
for (const chunk of splitDiscordMessage(finalAgent.text)) {
|
|
443
|
-
await runtime.sendMessage(context, { text: chunk, fallbackText: chunk });
|
|
444
|
-
}
|
|
445
|
-
state.latestAgentLine = finalAgent.lineNumber;
|
|
446
|
-
}
|
|
447
|
-
await deliverCliGeneratedArtifacts(contextKey, context, session, state.startedAt, terminalEvent.turnId);
|
|
448
|
-
}
|
|
449
|
-
state.workingNoticeTurnKey = undefined;
|
|
450
|
-
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
239
|
+
await queueStatusMessages.update(contextKey, context, text);
|
|
451
240
|
};
|
|
452
241
|
const ensureActiveThread = async (request, session) => {
|
|
453
242
|
if (!session.hasActiveThread()) {
|
|
@@ -455,69 +244,15 @@ export function createDiscordBridge(config, registry) {
|
|
|
455
244
|
updateSession(request, session);
|
|
456
245
|
}
|
|
457
246
|
};
|
|
458
|
-
const checkAgentAuthStatus =
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return checkHermesAuthStatus({ baseUrl: config.hermesApiBaseUrl, apiKey: config.hermesApiKey });
|
|
463
|
-
if (info.agentId === "openclaw")
|
|
464
|
-
return checkOpenClawAuthStatus({ gatewayUrl: config.openClawGatewayUrl, token: config.openClawGatewayToken, password: config.openClawGatewayPassword });
|
|
465
|
-
if (info.agentId === "claude-code")
|
|
466
|
-
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
467
|
-
return checkAuthStatus(config.codexApiKey);
|
|
468
|
-
};
|
|
469
|
-
const checkLoginAuthStatus = async (info) => {
|
|
470
|
-
if (info.agentId === "hermes")
|
|
471
|
-
return checkHermesAuthStatus({ baseUrl: config.hermesApiBaseUrl, apiKey: config.hermesApiKey });
|
|
472
|
-
if (info.agentId === "claude-code")
|
|
473
|
-
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
474
|
-
return checkAuthStatus(config.codexApiKey);
|
|
475
|
-
};
|
|
476
|
-
const startAgentLogin = (info) => {
|
|
477
|
-
if (info.agentId === "hermes")
|
|
478
|
-
return startHermesLogin(config.hermesCliPath);
|
|
479
|
-
if (info.agentId === "claude-code")
|
|
480
|
-
return startClaudeCodeLogin(config.claudeCodeCliPath);
|
|
481
|
-
if (info.agentId === "codex")
|
|
482
|
-
return startCodexLogin();
|
|
483
|
-
return Promise.resolve({
|
|
484
|
-
success: false,
|
|
485
|
-
message: `${info.agentLabel} login is not managed by NordRelay. Run the agent login flow on the host.`,
|
|
486
|
-
});
|
|
487
|
-
};
|
|
488
|
-
const startAgentLogout = (info) => {
|
|
489
|
-
if (info.agentId === "hermes")
|
|
490
|
-
return startHermesLogout(config.hermesCliPath);
|
|
491
|
-
if (info.agentId === "claude-code")
|
|
492
|
-
return startClaudeCodeLogout(config.claudeCodeCliPath);
|
|
493
|
-
if (info.agentId === "codex")
|
|
494
|
-
return startCodexLogout();
|
|
495
|
-
return Promise.resolve({
|
|
496
|
-
success: false,
|
|
497
|
-
message: `${info.agentLabel} logout is not managed by NordRelay. Run the agent logout flow on the host.`,
|
|
498
|
-
});
|
|
499
|
-
};
|
|
247
|
+
const checkAgentAuthStatus = (info) => authService.check(info);
|
|
248
|
+
const checkLoginAuthStatus = (info) => authService.check(info);
|
|
249
|
+
const startAgentLogin = (info) => authService.startLogin(info);
|
|
250
|
+
const startAgentLogout = (info) => authService.startLogout(info);
|
|
500
251
|
const hostLoginCommand = (info) => {
|
|
501
|
-
|
|
502
|
-
return `${config.hermesCliPath ?? "hermes"} login --no-browser`;
|
|
503
|
-
if (info.agentId === "claude-code")
|
|
504
|
-
return `${config.claudeCodeCliPath ?? "claude"} auth login`;
|
|
505
|
-
if (info.agentId === "pi")
|
|
506
|
-
return `${config.piCliPath ?? "pi"} auth login`;
|
|
507
|
-
if (info.agentId === "openclaw")
|
|
508
|
-
return `${config.openClawCliPath ?? "openclaw"} login`;
|
|
509
|
-
return "codex login --device-auth";
|
|
252
|
+
return hostAgentLoginCommand(config, info);
|
|
510
253
|
};
|
|
511
254
|
const hostLogoutCommand = (info) => {
|
|
512
|
-
|
|
513
|
-
return `${config.hermesCliPath ?? "hermes"} logout`;
|
|
514
|
-
if (info.agentId === "claude-code")
|
|
515
|
-
return `${config.claudeCodeCliPath ?? "claude"} auth logout`;
|
|
516
|
-
if (info.agentId === "pi")
|
|
517
|
-
return `${config.piCliPath ?? "pi"} auth logout`;
|
|
518
|
-
if (info.agentId === "openclaw")
|
|
519
|
-
return `${config.openClawCliPath ?? "openclaw"} logout`;
|
|
520
|
-
return "codex logout";
|
|
255
|
+
return hostAgentLogoutCommand(config, info);
|
|
521
256
|
};
|
|
522
257
|
const denyIfLocked = async (request) => {
|
|
523
258
|
const lock = lockStore.get(request.contextKey);
|
|
@@ -745,57 +480,73 @@ export function createDiscordBridge(config, registry) {
|
|
|
745
480
|
}
|
|
746
481
|
};
|
|
747
482
|
const deliverCliGeneratedArtifacts = async (contextKey, context, session, startedAt, turnId) => {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
const state = externalMirrors.get(contextKey);
|
|
752
|
-
if (state?.artifactsDeliveredForTurnId === turnId) {
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
const workspace = session.getInfo().workspace;
|
|
756
|
-
const report = await collectRecentWorkspaceArtifacts(workspace, {
|
|
757
|
-
since: startedAt,
|
|
758
|
-
until: new Date(),
|
|
759
|
-
maxFileSize: config.maxFileSize,
|
|
760
|
-
limit: 5,
|
|
761
|
-
ignoreDirs: config.artifactIgnoreDirs,
|
|
762
|
-
ignoreGlobs: config.artifactIgnoreGlobs,
|
|
763
|
-
});
|
|
764
|
-
if (report.artifacts.length === 0 && report.skippedCount === 0 && !report.omittedCount) {
|
|
765
|
-
if (state)
|
|
766
|
-
state.artifactsDeliveredForTurnId = turnId;
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const persisted = await persistWorkspaceArtifactReport(workspace, turnId, report).catch((error) => {
|
|
770
|
-
console.error("Failed to persist Discord CLI artifact report:", error);
|
|
771
|
-
return null;
|
|
772
|
-
});
|
|
773
|
-
const summary = formatArtifactSummary(report.artifacts, report.skippedCount, report.omittedCount);
|
|
774
|
-
if (summary) {
|
|
775
|
-
await runtime.sendMessage(context, { text: summary, fallbackText: summary });
|
|
776
|
-
}
|
|
777
|
-
if (config.discordAutoSendArtifacts) {
|
|
778
|
-
for (const artifact of (persisted?.artifacts ?? report.artifacts).slice(0, 5)) {
|
|
779
|
-
await runtime.sendFile(context, { localPath: artifact.localPath, name: artifact.name }).catch((error) => {
|
|
780
|
-
console.error(`Failed to send Discord CLI artifact ${artifact.name}:`, error);
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
const info = session.getInfo();
|
|
785
|
-
activityStore.append({
|
|
786
|
-
source: "cli",
|
|
787
|
-
status: "info",
|
|
788
|
-
type: config.discordAutoSendArtifacts ? "artifacts_sent" : "artifacts_detected",
|
|
483
|
+
await deliverChannelCliArtifacts({
|
|
484
|
+
config,
|
|
789
485
|
contextKey,
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
486
|
+
session,
|
|
487
|
+
startedAt,
|
|
488
|
+
turnId,
|
|
489
|
+
state: externalMirrors.get(contextKey),
|
|
490
|
+
autoSend: config.discordAutoSendArtifacts,
|
|
491
|
+
sendSummaryWhenAutoSendDisabled: true,
|
|
492
|
+
logPrefix: "Discord",
|
|
493
|
+
sendSummary: (summary) => runtime.sendMessage(context, { text: summary, fallbackText: summary }).then(() => { }),
|
|
494
|
+
sendArtifact: (artifact) => runtime.sendFile(context, { localPath: artifact.localPath, name: artifact.name }).then(() => { }).catch((error) => {
|
|
495
|
+
console.error(`Failed to send Discord CLI artifact ${artifact.name}:`, error);
|
|
496
|
+
}),
|
|
497
|
+
appendActivity: (input) => {
|
|
498
|
+
activityStore.append(input);
|
|
499
|
+
},
|
|
795
500
|
});
|
|
796
|
-
if (state)
|
|
797
|
-
state.artifactsDeliveredForTurnId = turnId;
|
|
798
501
|
};
|
|
502
|
+
const externalMirrorController = createChannelExternalMirrorController({
|
|
503
|
+
config,
|
|
504
|
+
states: externalMirrors,
|
|
505
|
+
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
506
|
+
minUpdateMs: () => config.discordMirrorMinUpdateMs,
|
|
507
|
+
mirrorMode: (contextKey) => preferencesStore.get(contextKey).mirrorMode ?? config.discordMirrorMode,
|
|
508
|
+
queueLength: (contextKey) => promptStore.list(contextKey).length,
|
|
509
|
+
activityActor: (snapshot) => ({ channel: "cli", label: `${snapshot.agentLabel} CLI` }),
|
|
510
|
+
appendActivity: (input) => {
|
|
511
|
+
activityStore.append(input);
|
|
512
|
+
},
|
|
513
|
+
sendTyping: (_contextKey, context) => runtime.sendTyping(context).catch(() => { }),
|
|
514
|
+
sendWorkingNotice: async (_contextKey, context, state, snapshot, prompt) => {
|
|
515
|
+
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
516
|
+
if (state.workingNoticeTurnKey === turnKey) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const text = prompt ? `**Working on** ${prompt}` : `**Working on** external ${snapshot.agentLabel} task...`;
|
|
520
|
+
await runtime.sendMessage(context, {
|
|
521
|
+
text,
|
|
522
|
+
fallbackText: prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`,
|
|
523
|
+
});
|
|
524
|
+
state.workingNoticeTurnKey = turnKey;
|
|
525
|
+
},
|
|
526
|
+
sendStatus: async (_contextKey, context, _state, rendered) => {
|
|
527
|
+
const sent = await runtime.sendMessage(context, { text: rendered.html, fallbackText: rendered.plain, parseMode: "html" });
|
|
528
|
+
return sent.messageId;
|
|
529
|
+
},
|
|
530
|
+
editStatus: (_contextKey, context, _state, messageId, rendered) => runtime.editMessage(context, messageId, { text: rendered.html, fallbackText: rendered.plain, parseMode: "html" }),
|
|
531
|
+
sendEvent: (_contextKey, context, _state, rendered) => deliverChannelAction(runtime, context, rendered).then(() => { }),
|
|
532
|
+
sendDone: (_contextKey, context, state, text) => {
|
|
533
|
+
if (state.statusMessageId) {
|
|
534
|
+
return runtime.editMessage(context, state.statusMessageId, { text, fallbackText: text });
|
|
535
|
+
}
|
|
536
|
+
return runtime.sendMessage(context, { text, fallbackText: text }).then(() => { });
|
|
537
|
+
},
|
|
538
|
+
sendFinalAnswer: async (_contextKey, context, _state, snapshot, text) => {
|
|
539
|
+
await runtime.sendMessage(context, {
|
|
540
|
+
text: `**${snapshot.agentLabel} CLI final answer:**`,
|
|
541
|
+
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
542
|
+
});
|
|
543
|
+
for (const chunk of splitDiscordMessage(text)) {
|
|
544
|
+
await runtime.sendMessage(context, { text: chunk, fallbackText: chunk });
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
deliverArtifacts: (contextKey, context, session, state, turnId) => deliverCliGeneratedArtifacts(contextKey, context, session, state.startedAt, turnId),
|
|
548
|
+
});
|
|
549
|
+
const mirrorExternalSnapshot = externalMirrorController.mirror;
|
|
799
550
|
const commandDispatcher = createSharedChannelCommandDispatcher({
|
|
800
551
|
transport: "discord",
|
|
801
552
|
bindings: [
|
|
@@ -1644,47 +1395,36 @@ export function createDiscordBridge(config, registry) {
|
|
|
1644
1395
|
return id;
|
|
1645
1396
|
};
|
|
1646
1397
|
const monitorExternalContexts = async () => {
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
if (mirrorSnapshot && !session.isProcessing()) {
|
|
1678
|
-
await mirrorExternalSnapshot(contextKey, context, session, mirrorSnapshot);
|
|
1679
|
-
}
|
|
1680
|
-
if (mirrorSnapshot?.activity.active) {
|
|
1681
|
-
if (promptStore.list(contextKey).length > 0) {
|
|
1682
|
-
await updateQueueStatusMessage(contextKey, context, `Waiting for ${mirrorSnapshot.agentLabel} CLI task... ${promptStore.list(contextKey).length} queued${promptStore.isPaused(contextKey) ? " (paused)" : ""}.`).catch(() => { });
|
|
1683
|
-
}
|
|
1684
|
-
continue;
|
|
1685
|
-
}
|
|
1686
|
-
if (promptStore.list(contextKey).length > 0 && !promptStore.isPaused(contextKey) && !session.isProcessing()) {
|
|
1687
|
-
await updateQueueStatusMessage(contextKey, context, `CLI task finished, running queued prompt 1/${promptStore.list(contextKey).length}.`).catch(() => { });
|
|
1398
|
+
await monitorChannelExternalContexts({
|
|
1399
|
+
config,
|
|
1400
|
+
registry,
|
|
1401
|
+
promptStore,
|
|
1402
|
+
isContextKey: isDiscordContextKey,
|
|
1403
|
+
canSendSystemMessages: (contextKey) => canSendSystemMessagesToDiscordContext(userStore, contextKey),
|
|
1404
|
+
isAllowed: (contextKey) => {
|
|
1405
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1406
|
+
if (!parsed)
|
|
1407
|
+
return false;
|
|
1408
|
+
const guildId = parsed.guildId?.startsWith("dm-") ? undefined : parsed.guildId;
|
|
1409
|
+
return isDiscordGuildAllowed(guildId) && isDiscordChannelAllowedByEnv(parsed.channelId);
|
|
1410
|
+
},
|
|
1411
|
+
contextForKey: (contextKey) => {
|
|
1412
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1413
|
+
if (!parsed)
|
|
1414
|
+
return null;
|
|
1415
|
+
return {
|
|
1416
|
+
channelId: "discord",
|
|
1417
|
+
chatId: parsed.threadId ?? parsed.channelId,
|
|
1418
|
+
...(parsed.threadId ? { topicId: parsed.threadId } : {}),
|
|
1419
|
+
};
|
|
1420
|
+
},
|
|
1421
|
+
previousLastLine: (contextKey) => externalMirrors.get(contextKey)?.lastLine,
|
|
1422
|
+
mirrorSnapshot: mirrorExternalSnapshot,
|
|
1423
|
+
updateQueueStatus: updateQueueStatusMessage,
|
|
1424
|
+
drainQueue: async (contextKey, context) => {
|
|
1425
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1426
|
+
if (!parsed)
|
|
1427
|
+
return;
|
|
1688
1428
|
const systemRequest = {
|
|
1689
1429
|
contextKey,
|
|
1690
1430
|
context,
|
|
@@ -1695,8 +1435,8 @@ export function createDiscordBridge(config, registry) {
|
|
|
1695
1435
|
source: "message",
|
|
1696
1436
|
};
|
|
1697
1437
|
await drainQueue(systemRequest);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1438
|
+
},
|
|
1439
|
+
});
|
|
1700
1440
|
};
|
|
1701
1441
|
const registerSlashCommands = async () => {
|
|
1702
1442
|
if (!config.discordClientId || !config.discordAutoRegisterCommands || config.discordCommandMode === "message" || !config.discordBotToken) {
|