@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.
- package/.env.example +9 -0
- package/README.md +84 -1205
- package/dist/{access-control.js → access/access-control.js} +1 -1
- package/dist/{audit-log.js → access/audit-log.js} +32 -15
- 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} +176 -451
- 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/channels/shared/channel-prompt-queue.js +37 -0
- package/dist/{channel-turn-service.js → channels/shared/channel-turn-service.js} +25 -11
- 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} +171 -309
- 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} +195 -430
- 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/core/pagination.js +22 -0
- package/dist/index.js +27 -23
- 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} +3 -2
- package/dist/{peer-store.js → peers/peer-store.js} +96 -9
- package/dist/{peer-types.js → peers/peer-types.js} +28 -0
- package/dist/peers/peer-web-proxy-contract.js +129 -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} +155 -53
- package/dist/{relay-queue-service.js → runtime/relay-queue-service.js} +1 -0
- package/dist/runtime/relay-runtime-active-sessions.js +387 -0
- package/dist/runtime/relay-runtime-dashboard.js +204 -0
- package/dist/{relay-runtime-helpers.js → runtime/relay-runtime-helpers.js} +3 -0
- package/dist/runtime/relay-runtime-prompt-queue-artifacts.js +311 -0
- package/dist/runtime/relay-runtime-sessions.js +631 -0
- package/dist/runtime/relay-runtime-trace.js +92 -0
- package/dist/runtime/relay-runtime-types.js +1 -0
- package/dist/runtime/relay-runtime-updates-jobs.js +366 -0
- package/dist/runtime/relay-runtime.js +461 -0
- package/dist/runtime/runtime-cache.js +117 -0
- package/dist/{prompt-store.js → state/prompt-store.js} +13 -1
- 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} +19 -3
- package/dist/web/web-api-types.js +1 -0
- package/dist/{web-dashboard-access-routes.js → web/web-dashboard-access-routes.js} +17 -14
- package/dist/{web-dashboard-artifact-routes.js → web/web-dashboard-artifact-routes.js} +6 -2
- package/dist/{web-dashboard-assets.js → web/web-dashboard-assets.js} +25 -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} +95 -30
- package/dist/{web-dashboard-peer-routes.js → web/web-dashboard-peer-routes.js} +121 -7
- package/dist/{web-dashboard-runtime-routes.js → web/web-dashboard-runtime-routes.js} +8 -1
- package/dist/web/web-dashboard-security.js +14 -0
- package/dist/{web-dashboard-session-routes.js → web/web-dashboard-session-routes.js} +29 -13
- package/dist/web/web-dashboard-ui.js +56 -0
- package/dist/{web-dashboard.js → web/web-dashboard.js} +132 -48
- package/dist/web/web-performance.js +62 -0
- package/dist/web/web-rate-limit.js +19 -0
- package/dist/{web-state.js → web/web-state.js} +107 -9
- package/dist/webui-assets/dashboard.css +398 -49
- package/dist/webui-assets/dashboard.js +1239 -103
- 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 +6 -3
- package/plugins/nordrelay/scripts/nordrelay.mjs +346 -12
- package/plugins/nordrelay/scripts/service-installer.mjs +183 -0
- package/{launchd/start.sh → scripts/launchd-start.sh} +1 -1
- package/scripts/postinstall.mjs +122 -0
- package/dist/relay-runtime.js +0 -1916
- package/dist/runtime-cache.js +0 -57
- package/dist/web-dashboard-ui.js +0 -20
- /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-client.js → peers/peer-client.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/{web-api-types.js → runtime/relay-runtime-delegate.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/{state-backend.js → state/state-backend.js} +0 -0
- /package/dist/{zip-writer.js → support/zip-writer.js} +0 -0
|
@@ -1,46 +1,48 @@
|
|
|
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 { 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 { discordContextKey, isDiscordContextKey, parseDiscordContextKey } from "../shared/context-key.js";
|
|
24
28
|
import { DiscordBotChannelRuntime, actionFromDiscordCustomId, discordActionRows, splitDiscordMessage, trimDiscordMessage } from "./discord-channel-runtime.js";
|
|
25
29
|
import { createDiscordArtifactCommandHandler, sendRecentDiscordArtifacts } from "./discord-artifacts.js";
|
|
26
30
|
import { argumentFromDiscordInteraction, discordCommands, isUnauthenticatedDiscordCommandAllowed, parseDiscordMessageCommand, permissionForDiscordAction, requiredPermissionForDiscordCommand } from "./discord-command-surface.js";
|
|
27
31
|
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";
|
|
32
|
+
import { friendlyErrorText } from "../../core/error-messages.js";
|
|
33
|
+
import { spawnConnectorRestart, spawnSelfUpdate } from "../../support/operations.js";
|
|
34
|
+
import { RemoteRelayClient } from "../../peers/peer-client.js";
|
|
35
|
+
import { PromptStore, toPromptEnvelope } from "../../state/prompt-store.js";
|
|
36
|
+
import { RelayArtifactService } from "../../runtime/relay-artifact-service.js";
|
|
37
|
+
import { RelayAuthService } from "../../runtime/relay-auth-service.js";
|
|
38
|
+
import { configureRedaction, redactText } from "../../core/redaction.js";
|
|
39
|
+
import { renderSessionInfoPlain } from "../shared/session-format.js";
|
|
40
|
+
import { canWriteWithLock, SessionLockStore } from "../../access/session-locks.js";
|
|
41
|
+
import { SessionRegistry } from "../../state/session-registry.js";
|
|
42
|
+
import { transcribeAudio } from "../../artifacts/voice.js";
|
|
43
|
+
import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "../../core/workspace-policy.js";
|
|
44
|
+
import { UserStore } from "../../access/user-management.js";
|
|
45
|
+
import { WebActivityStore } from "../../web/web-state.js";
|
|
44
46
|
export { isUnauthenticatedDiscordCommandAllowed, permissionForDiscordAction, requiredPermissionForDiscordCommand } from "./discord-command-surface.js";
|
|
45
47
|
const EDIT_DEBOUNCE_MS = 1500;
|
|
46
48
|
const TYPING_INTERVAL_MS = 4500;
|
|
@@ -75,24 +77,23 @@ export function createDiscordBridge(config, registry) {
|
|
|
75
77
|
const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
76
78
|
const userStore = new UserStore();
|
|
77
79
|
const artifactService = new RelayArtifactService(config);
|
|
80
|
+
const authService = new RelayAuthService(config);
|
|
78
81
|
const agentUpdates = new AgentUpdateManager();
|
|
79
82
|
const commandService = new ChannelCommandService(config);
|
|
80
|
-
const busyStates =
|
|
83
|
+
const busyStates = createChannelBusyStore();
|
|
81
84
|
const turnProgress = new Map();
|
|
82
85
|
const draining = new Set();
|
|
83
86
|
const picks = new Map();
|
|
84
87
|
const responseOwners = new Map();
|
|
85
88
|
const externalMirrors = new Map();
|
|
86
|
-
const queueStatusMessages =
|
|
89
|
+
const queueStatusMessages = createChannelQueueStatusController({
|
|
90
|
+
send: async (_contextKey, context, text) => (await runtime.sendMessage(context, { text, fallbackText: text })).messageId,
|
|
91
|
+
edit: async (_contextKey, context, messageId, text) => {
|
|
92
|
+
await runtime.editMessage(context, messageId, { text, fallbackText: text });
|
|
93
|
+
},
|
|
94
|
+
});
|
|
87
95
|
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
|
-
};
|
|
96
|
+
const getBusyState = (contextKey) => busyStates.get(contextKey);
|
|
96
97
|
const actorFor = (request) => ({
|
|
97
98
|
channel: "discord",
|
|
98
99
|
id: request.authUser?.user.id ?? `discord:${request.user.id}`,
|
|
@@ -100,27 +101,19 @@ export function createDiscordBridge(config, registry) {
|
|
|
100
101
|
username: request.authUser?.user.email ?? request.user.username,
|
|
101
102
|
channelUserId: request.user.id,
|
|
102
103
|
});
|
|
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);
|
|
104
|
+
const appendActivity = createChannelActivityRecorder({
|
|
105
|
+
source: "discord",
|
|
106
|
+
workspace: config.workspace,
|
|
107
|
+
activityStore,
|
|
108
|
+
actorFor,
|
|
109
|
+
});
|
|
110
|
+
const audit = createChannelAuditRecorder({
|
|
111
|
+
channelId: "discord",
|
|
112
|
+
auditLog,
|
|
113
|
+
actorFor,
|
|
114
|
+
actorIdFor: (request) => request.user.id,
|
|
115
|
+
});
|
|
116
|
+
const hasPermission = createChannelPermissionChecker(userStore);
|
|
124
117
|
const reply = async (request, content, options = {}) => {
|
|
125
118
|
const chunks = splitDiscordMessage(content);
|
|
126
119
|
if (request.interaction) {
|
|
@@ -232,7 +225,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
232
225
|
};
|
|
233
226
|
const commandArtifacts = createDiscordArtifactCommandHandler(artifactDeps);
|
|
234
227
|
const getBusyReason = (contextKey) => {
|
|
235
|
-
const state = busyStates.
|
|
228
|
+
const state = busyStates.peek(contextKey);
|
|
236
229
|
const session = registry.get(contextKey);
|
|
237
230
|
if (state?.processing || state?.switching || session?.isProcessing()) {
|
|
238
231
|
return { busy: true, kind: "connector", state: state ?? getBusyState(contextKey) };
|
|
@@ -244,210 +237,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
244
237
|
return { busy: false, kind: "idle" };
|
|
245
238
|
};
|
|
246
239
|
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);
|
|
240
|
+
await queueStatusMessages.update(contextKey, context, text);
|
|
451
241
|
};
|
|
452
242
|
const ensureActiveThread = async (request, session) => {
|
|
453
243
|
if (!session.hasActiveThread()) {
|
|
@@ -455,69 +245,15 @@ export function createDiscordBridge(config, registry) {
|
|
|
455
245
|
updateSession(request, session);
|
|
456
246
|
}
|
|
457
247
|
};
|
|
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
|
-
};
|
|
248
|
+
const checkAgentAuthStatus = (info) => authService.check(info);
|
|
249
|
+
const checkLoginAuthStatus = (info) => authService.check(info);
|
|
250
|
+
const startAgentLogin = (info) => authService.startLogin(info);
|
|
251
|
+
const startAgentLogout = (info) => authService.startLogout(info);
|
|
500
252
|
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";
|
|
253
|
+
return hostAgentLoginCommand(config, info);
|
|
510
254
|
};
|
|
511
255
|
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";
|
|
256
|
+
return hostAgentLogoutCommand(config, info);
|
|
521
257
|
};
|
|
522
258
|
const denyIfLocked = async (request) => {
|
|
523
259
|
const lock = lockStore.get(request.contextKey);
|
|
@@ -569,30 +305,17 @@ export function createDiscordBridge(config, registry) {
|
|
|
569
305
|
if (!options.fromQueue && await denyIfLocked(request)) {
|
|
570
306
|
return;
|
|
571
307
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
});
|
|
584
|
-
appendActivity(request, {
|
|
585
|
-
status: "queued",
|
|
586
|
-
type: "prompt_queued",
|
|
587
|
-
prompt: item.description,
|
|
588
|
-
detail: text,
|
|
589
|
-
});
|
|
590
|
-
audit(request, {
|
|
591
|
-
action: "prompt_queued",
|
|
592
|
-
status: "ok",
|
|
593
|
-
promptId: item.id,
|
|
594
|
-
description: item.description,
|
|
595
|
-
});
|
|
308
|
+
if (await queueChannelPromptIfBusy({
|
|
309
|
+
request,
|
|
310
|
+
envelope,
|
|
311
|
+
fromQueue: options.fromQueue,
|
|
312
|
+
promptStore,
|
|
313
|
+
busy: getBusyReason(request.contextKey),
|
|
314
|
+
actionPrefix: "discord",
|
|
315
|
+
reply,
|
|
316
|
+
appendActivity,
|
|
317
|
+
audit,
|
|
318
|
+
})) {
|
|
596
319
|
return;
|
|
597
320
|
}
|
|
598
321
|
const busyState = getBusyState(request.contextKey);
|
|
@@ -745,57 +468,73 @@ export function createDiscordBridge(config, registry) {
|
|
|
745
468
|
}
|
|
746
469
|
};
|
|
747
470
|
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",
|
|
471
|
+
await deliverChannelCliArtifacts({
|
|
472
|
+
config,
|
|
789
473
|
contextKey,
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
474
|
+
session,
|
|
475
|
+
startedAt,
|
|
476
|
+
turnId,
|
|
477
|
+
state: externalMirrors.get(contextKey),
|
|
478
|
+
autoSend: config.discordAutoSendArtifacts,
|
|
479
|
+
sendSummaryWhenAutoSendDisabled: true,
|
|
480
|
+
logPrefix: "Discord",
|
|
481
|
+
sendSummary: (summary) => runtime.sendMessage(context, { text: summary, fallbackText: summary }).then(() => { }),
|
|
482
|
+
sendArtifact: (artifact) => runtime.sendFile(context, { localPath: artifact.localPath, name: artifact.name }).then(() => { }).catch((error) => {
|
|
483
|
+
console.error(`Failed to send Discord CLI artifact ${artifact.name}:`, error);
|
|
484
|
+
}),
|
|
485
|
+
appendActivity: (input) => {
|
|
486
|
+
activityStore.append(input);
|
|
487
|
+
},
|
|
795
488
|
});
|
|
796
|
-
if (state)
|
|
797
|
-
state.artifactsDeliveredForTurnId = turnId;
|
|
798
489
|
};
|
|
490
|
+
const externalMirrorController = createChannelExternalMirrorController({
|
|
491
|
+
config,
|
|
492
|
+
states: externalMirrors,
|
|
493
|
+
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
494
|
+
minUpdateMs: () => config.discordMirrorMinUpdateMs,
|
|
495
|
+
mirrorMode: (contextKey) => preferencesStore.get(contextKey).mirrorMode ?? config.discordMirrorMode,
|
|
496
|
+
queueLength: (contextKey) => promptStore.list(contextKey).length,
|
|
497
|
+
activityActor: (snapshot) => ({ channel: "cli", label: `${snapshot.agentLabel} CLI` }),
|
|
498
|
+
appendActivity: (input) => {
|
|
499
|
+
activityStore.append(input);
|
|
500
|
+
},
|
|
501
|
+
sendTyping: (_contextKey, context) => runtime.sendTyping(context).catch(() => { }),
|
|
502
|
+
sendWorkingNotice: async (_contextKey, context, state, snapshot, prompt) => {
|
|
503
|
+
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
504
|
+
if (state.workingNoticeTurnKey === turnKey) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const text = prompt ? `**Working on** ${prompt}` : `**Working on** external ${snapshot.agentLabel} task...`;
|
|
508
|
+
await runtime.sendMessage(context, {
|
|
509
|
+
text,
|
|
510
|
+
fallbackText: prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`,
|
|
511
|
+
});
|
|
512
|
+
state.workingNoticeTurnKey = turnKey;
|
|
513
|
+
},
|
|
514
|
+
sendStatus: async (_contextKey, context, _state, rendered) => {
|
|
515
|
+
const sent = await runtime.sendMessage(context, { text: rendered.html, fallbackText: rendered.plain, parseMode: "html" });
|
|
516
|
+
return sent.messageId;
|
|
517
|
+
},
|
|
518
|
+
editStatus: (_contextKey, context, _state, messageId, rendered) => runtime.editMessage(context, messageId, { text: rendered.html, fallbackText: rendered.plain, parseMode: "html" }),
|
|
519
|
+
sendEvent: (_contextKey, context, _state, rendered) => deliverChannelAction(runtime, context, rendered).then(() => { }),
|
|
520
|
+
sendDone: (_contextKey, context, state, text) => {
|
|
521
|
+
if (state.statusMessageId) {
|
|
522
|
+
return runtime.editMessage(context, state.statusMessageId, { text, fallbackText: text });
|
|
523
|
+
}
|
|
524
|
+
return runtime.sendMessage(context, { text, fallbackText: text }).then(() => { });
|
|
525
|
+
},
|
|
526
|
+
sendFinalAnswer: async (_contextKey, context, _state, snapshot, text) => {
|
|
527
|
+
await runtime.sendMessage(context, {
|
|
528
|
+
text: `**${snapshot.agentLabel} CLI final answer:**`,
|
|
529
|
+
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
530
|
+
});
|
|
531
|
+
for (const chunk of splitDiscordMessage(text)) {
|
|
532
|
+
await runtime.sendMessage(context, { text: chunk, fallbackText: chunk });
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
deliverArtifacts: (contextKey, context, session, state, turnId) => deliverCliGeneratedArtifacts(contextKey, context, session, state.startedAt, turnId),
|
|
536
|
+
});
|
|
537
|
+
const mirrorExternalSnapshot = externalMirrorController.mirror;
|
|
799
538
|
const commandDispatcher = createSharedChannelCommandDispatcher({
|
|
800
539
|
transport: "discord",
|
|
801
540
|
bindings: [
|
|
@@ -1644,47 +1383,36 @@ export function createDiscordBridge(config, registry) {
|
|
|
1644
1383
|
return id;
|
|
1645
1384
|
};
|
|
1646
1385
|
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(() => { });
|
|
1386
|
+
await monitorChannelExternalContexts({
|
|
1387
|
+
config,
|
|
1388
|
+
registry,
|
|
1389
|
+
promptStore,
|
|
1390
|
+
isContextKey: isDiscordContextKey,
|
|
1391
|
+
canSendSystemMessages: (contextKey) => canSendSystemMessagesToDiscordContext(userStore, contextKey),
|
|
1392
|
+
isAllowed: (contextKey) => {
|
|
1393
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1394
|
+
if (!parsed)
|
|
1395
|
+
return false;
|
|
1396
|
+
const guildId = parsed.guildId?.startsWith("dm-") ? undefined : parsed.guildId;
|
|
1397
|
+
return isDiscordGuildAllowed(guildId) && isDiscordChannelAllowedByEnv(parsed.channelId);
|
|
1398
|
+
},
|
|
1399
|
+
contextForKey: (contextKey) => {
|
|
1400
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1401
|
+
if (!parsed)
|
|
1402
|
+
return null;
|
|
1403
|
+
return {
|
|
1404
|
+
channelId: "discord",
|
|
1405
|
+
chatId: parsed.threadId ?? parsed.channelId,
|
|
1406
|
+
...(parsed.threadId ? { topicId: parsed.threadId } : {}),
|
|
1407
|
+
};
|
|
1408
|
+
},
|
|
1409
|
+
previousLastLine: (contextKey) => externalMirrors.get(contextKey)?.lastLine,
|
|
1410
|
+
mirrorSnapshot: mirrorExternalSnapshot,
|
|
1411
|
+
updateQueueStatus: updateQueueStatusMessage,
|
|
1412
|
+
drainQueue: async (contextKey, context) => {
|
|
1413
|
+
const parsed = parseDiscordContextKey(contextKey);
|
|
1414
|
+
if (!parsed)
|
|
1415
|
+
return;
|
|
1688
1416
|
const systemRequest = {
|
|
1689
1417
|
contextKey,
|
|
1690
1418
|
context,
|
|
@@ -1695,8 +1423,8 @@ export function createDiscordBridge(config, registry) {
|
|
|
1695
1423
|
source: "message",
|
|
1696
1424
|
};
|
|
1697
1425
|
await drainQueue(systemRequest);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1426
|
+
},
|
|
1427
|
+
});
|
|
1700
1428
|
};
|
|
1701
1429
|
const registerSlashCommands = async () => {
|
|
1702
1430
|
if (!config.discordClientId || !config.discordAutoRegisterCommands || config.discordCommandMode === "message" || !config.discordBotToken) {
|
|
@@ -1840,6 +1568,3 @@ function inferMimeType(name) {
|
|
|
1840
1568
|
return "audio/webm";
|
|
1841
1569
|
return "application/octet-stream";
|
|
1842
1570
|
}
|
|
1843
|
-
function isQueuedPrompt(value) {
|
|
1844
|
-
return Boolean(value && typeof value === "object" && "id" in value && "contextKey" in value);
|
|
1845
|
-
}
|