@nordbyte/nordrelay 0.6.0 → 0.8.0
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 +52 -0
- package/README.md +171 -50
- package/dist/access-control.js +6 -1
- package/dist/activity-events.js +2 -2
- package/dist/adapter-conformance.js +61 -0
- package/dist/bot-preferences.js +1 -0
- package/dist/bot.js +95 -37
- package/dist/channel-adapter.js +44 -11
- package/dist/channel-command-catalog.js +94 -0
- package/dist/channel-command-core.js +60 -0
- package/dist/channel-command-service.js +230 -1
- package/dist/channel-mirror-registry.js +84 -0
- package/dist/channel-peer-prompt.js +95 -0
- package/dist/channel-prompt-engine.js +177 -0
- package/dist/channel-runtime.js +12 -5
- package/dist/channel-turn-lifecycle.js +73 -0
- package/dist/codex-state.js +114 -78
- package/dist/config-metadata.js +82 -8
- package/dist/config.js +79 -7
- package/dist/context-key.js +42 -0
- package/dist/discord-bot.js +173 -342
- package/dist/discord-command-surface.js +11 -73
- package/dist/index.js +29 -0
- package/dist/metrics.js +48 -0
- package/dist/peer-auth.js +85 -0
- package/dist/peer-client.js +288 -0
- package/dist/peer-context.js +21 -0
- package/dist/peer-identity.js +127 -0
- package/dist/peer-readiness.js +77 -0
- package/dist/peer-runtime-service.js +658 -0
- package/dist/peer-server.js +220 -0
- package/dist/peer-store.js +307 -0
- package/dist/peer-types.js +52 -0
- package/dist/relay-runtime-helpers.js +210 -0
- package/dist/relay-runtime.js +79 -274
- package/dist/remote-prompt.js +98 -0
- package/dist/settings-wizard-test.js +216 -0
- package/dist/slack-artifacts.js +165 -0
- package/dist/slack-bot.js +1461 -0
- package/dist/slack-channel-runtime.js +147 -0
- package/dist/slack-command-surface.js +46 -0
- package/dist/slack-diagnostics.js +116 -0
- package/dist/slack-rate-limit.js +139 -0
- package/dist/telegram-command-menu.js +3 -53
- package/dist/telegram-general-commands.js +14 -0
- package/dist/telegram-preference-commands.js +23 -127
- package/dist/user-management-crypto.js +38 -0
- package/dist/user-management-normalize.js +188 -0
- package/dist/user-management-types.js +1 -0
- package/dist/user-management.js +193 -196
- package/dist/web-api-contract.js +16 -0
- package/dist/web-dashboard-access-routes.js +62 -0
- package/dist/web-dashboard-assets.js +1 -0
- package/dist/web-dashboard-pages.js +26 -4
- package/dist/web-dashboard-peer-routes.js +225 -0
- package/dist/web-dashboard-ui.js +1 -0
- package/dist/web-dashboard.js +46 -0
- package/dist/web-state.js +2 -2
- package/dist/webui-assets/dashboard.css +193 -0
- package/dist/webui-assets/dashboard.js +870 -57
- package/package.json +5 -2
- package/plugins/nordrelay/scripts/nordrelay.mjs +468 -11
package/dist/discord-bot.js
CHANGED
|
@@ -9,10 +9,14 @@ import { enabledAgents } from "./agent-factory.js";
|
|
|
9
9
|
import { collectRecentWorkspaceArtifacts, ensureOutDir, formatArtifactSummary, persistWorkspaceArtifactReport } from "./artifacts.js";
|
|
10
10
|
import { buildFileInstructions, outboxPath, stageFile } from "./attachments.js";
|
|
11
11
|
import { AuditLogStore } from "./audit-log.js";
|
|
12
|
-
import { BotPreferencesStore
|
|
12
|
+
import { BotPreferencesStore } from "./bot-preferences.js";
|
|
13
13
|
import { capabilitiesOf, filterActivityEvents, formatLocalDateTime, parseActivityOptions, renderExternalMirrorEvent, renderExternalMirrorStatus, renderPromptFailure, trimLine } from "./bot-rendering.js";
|
|
14
14
|
import { renderAgentUpdateJobAction, renderAgentUpdateJobsAction, renderAgentUpdateLogAction, renderAgentUpdatePickerAction, renderQueueListAction } from "./channel-actions.js";
|
|
15
|
+
import { createSharedChannelCommandDispatcher } from "./channel-command-core.js";
|
|
15
16
|
import { ChannelCommandService } from "./channel-command-service.js";
|
|
17
|
+
import { discordHelpCommandList } from "./channel-command-catalog.js";
|
|
18
|
+
import { createChannelPromptEngine } from "./channel-prompt-engine.js";
|
|
19
|
+
import { runChannelPeerPrompt } from "./channel-peer-prompt.js";
|
|
16
20
|
import { deliverChannelAction } from "./channel-runtime.js";
|
|
17
21
|
import { checkAuthStatus, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
|
|
18
22
|
import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout } from "./claude-code-auth.js";
|
|
@@ -25,6 +29,7 @@ import { friendlyErrorText } from "./error-messages.js";
|
|
|
25
29
|
import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
|
|
26
30
|
import { spawnConnectorRestart, spawnSelfUpdate } from "./operations.js";
|
|
27
31
|
import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
32
|
+
import { RemoteRelayClient } from "./peer-client.js";
|
|
28
33
|
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
29
34
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
30
35
|
import { RelayArtifactService } from "./relay-artifact-service.js";
|
|
@@ -46,7 +51,8 @@ export function createDiscordBridge(config, registry) {
|
|
|
46
51
|
return null;
|
|
47
52
|
}
|
|
48
53
|
if (!config.discordBotToken) {
|
|
49
|
-
|
|
54
|
+
console.warn("Discord adapter disabled: DISCORD_ENABLED=true requires DISCORD_BOT_TOKEN.");
|
|
55
|
+
return null;
|
|
50
56
|
}
|
|
51
57
|
configureRedaction(config.telegramRedactPatterns);
|
|
52
58
|
const intents = [
|
|
@@ -522,10 +528,44 @@ export function createDiscordBridge(config, registry) {
|
|
|
522
528
|
await reply(request, `Session is locked by ${lock?.ownerLabel || lock?.ownerUserId || "another user"}.`);
|
|
523
529
|
return true;
|
|
524
530
|
};
|
|
531
|
+
const remoteClient = new RemoteRelayClient();
|
|
532
|
+
const handleRemotePrompt = async (request, envelope) => {
|
|
533
|
+
const targetPeerId = preferencesStore.get(request.contextKey).targetPeerId ?? undefined;
|
|
534
|
+
return runChannelPeerPrompt({
|
|
535
|
+
targetPeerId,
|
|
536
|
+
contextKey: request.contextKey,
|
|
537
|
+
prompt: envelope,
|
|
538
|
+
remoteClient,
|
|
539
|
+
editMinIntervalMs: EDIT_DEBOUNCE_MS,
|
|
540
|
+
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
541
|
+
sendTyping: () => runtime.sendTyping(request.context),
|
|
542
|
+
sendResponse: async (text) => {
|
|
543
|
+
const rendered = trimDiscordMessage(text);
|
|
544
|
+
const sent = await runtime.sendMessage(request.context, { text: rendered, fallbackText: rendered });
|
|
545
|
+
return sent.messageId;
|
|
546
|
+
},
|
|
547
|
+
editResponse: async (messageId, text) => {
|
|
548
|
+
const rendered = trimDiscordMessage(text);
|
|
549
|
+
await runtime.editMessage(request.context, messageId, { text: rendered, fallbackText: rendered });
|
|
550
|
+
},
|
|
551
|
+
sendTurnStart: (remotePrompt) => reply(request, `Remote peer working on:\n${remotePrompt}`),
|
|
552
|
+
sendToolStart: (toolName) => reply(request, `Remote tool: ${toolName}`),
|
|
553
|
+
sendQueued: async (queueId) => {
|
|
554
|
+
await reply(request, `Remote prompt queued${queueId ? `: ${queueId}` : ""}.`, queueId ? {
|
|
555
|
+
buttons: [[{ label: "Cancel queued message", action: `discord_peer_queue_cancel:${targetPeerId}:${queueId}` }]],
|
|
556
|
+
} : undefined);
|
|
557
|
+
},
|
|
558
|
+
sendCompleted: () => reply(request, "Remote turn completed."),
|
|
559
|
+
sendFailure: (message) => reply(request, `Remote peer failed: ${message}`),
|
|
560
|
+
});
|
|
561
|
+
};
|
|
525
562
|
const handlePrompt = async (request, input, artifactOutDir, options = {}) => {
|
|
526
563
|
const session = await getSession(request);
|
|
527
564
|
const envelope = toPromptEnvelope(input, artifactOutDir);
|
|
528
565
|
envelope.activityActor = actorFor(request);
|
|
566
|
+
if (!options.fromQueue && await handleRemotePrompt(request, envelope)) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
529
569
|
if (!options.fromQueue && await denyIfLocked(request)) {
|
|
530
570
|
return;
|
|
531
571
|
}
|
|
@@ -557,157 +597,41 @@ export function createDiscordBridge(config, registry) {
|
|
|
557
597
|
}
|
|
558
598
|
const busyState = getBusyState(request.contextKey);
|
|
559
599
|
busyState.processing = true;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
let accumulatedText = "";
|
|
565
|
-
let responseMessageId;
|
|
566
|
-
let planMessageId;
|
|
567
|
-
let flushTimer;
|
|
568
|
-
let lastEditAt = 0;
|
|
569
|
-
let running = true;
|
|
570
|
-
let finalized = false;
|
|
571
|
-
const toolCounts = new Map();
|
|
572
|
-
const toolVerbosity = config.toolVerbosity;
|
|
573
|
-
const startedAt = Date.now();
|
|
574
|
-
const turnId = randomUUID().slice(0, 12);
|
|
575
|
-
const progress = {
|
|
576
|
-
status: "running",
|
|
600
|
+
const engine = createChannelPromptEngine({
|
|
601
|
+
runtime,
|
|
602
|
+
context: request.context,
|
|
603
|
+
contextKey: request.contextKey,
|
|
577
604
|
promptDescription: envelope.description,
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
605
|
+
abortAction: `discord_abort:${request.contextKey}`,
|
|
606
|
+
trimMessage: trimDiscordMessage,
|
|
607
|
+
splitMessage: splitDiscordMessage,
|
|
608
|
+
editDebounceMs: EDIT_DEBOUNCE_MS,
|
|
609
|
+
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
610
|
+
toolVerbosity: config.toolVerbosity,
|
|
611
|
+
logPrefix: "Discord",
|
|
612
|
+
onResponseMessage: (messageId) => responseOwners.set(messageId, request.contextKey),
|
|
613
|
+
onToolStart: (toolName) => appendActivity(request, {
|
|
614
|
+
status: "running",
|
|
615
|
+
type: "tool_started",
|
|
616
|
+
prompt: envelope.description,
|
|
617
|
+
detail: toolName,
|
|
618
|
+
threadId: session.getInfo().threadId,
|
|
619
|
+
workspace: session.getInfo().workspace,
|
|
620
|
+
agentId: session.getInfo().agentId,
|
|
621
|
+
}),
|
|
622
|
+
onToolEnd: (isError) => appendActivity(request, {
|
|
623
|
+
status: isError ? "failed" : "completed",
|
|
624
|
+
type: isError ? "tool_failed" : "tool_completed",
|
|
625
|
+
prompt: envelope.description,
|
|
626
|
+
detail: "tool",
|
|
627
|
+
threadId: session.getInfo().threadId,
|
|
628
|
+
workspace: session.getInfo().workspace,
|
|
629
|
+
agentId: session.getInfo().agentId,
|
|
630
|
+
}),
|
|
631
|
+
});
|
|
632
|
+
const progress = engine.progress;
|
|
583
633
|
turnProgress.set(request.contextKey, progress);
|
|
584
|
-
|
|
585
|
-
if (flushTimer || !running) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
const delay = Math.max(0, EDIT_DEBOUNCE_MS - (Date.now() - lastEditAt));
|
|
589
|
-
flushTimer = setTimeout(() => {
|
|
590
|
-
flushTimer = undefined;
|
|
591
|
-
void flushResponse().catch((error) => console.error("Failed to edit Discord response:", error));
|
|
592
|
-
}, delay);
|
|
593
|
-
};
|
|
594
|
-
const ensureResponse = async () => {
|
|
595
|
-
if (responseMessageId)
|
|
596
|
-
return;
|
|
597
|
-
const preview = trimDiscordMessage(accumulatedText || "Working...");
|
|
598
|
-
const sent = await runtime.sendMessage(request.context, {
|
|
599
|
-
text: preview,
|
|
600
|
-
fallbackText: preview,
|
|
601
|
-
buttons: [[{ label: "Abort", action: `discord_abort:${request.contextKey}` }]],
|
|
602
|
-
});
|
|
603
|
-
responseMessageId = sent.messageId;
|
|
604
|
-
responseOwners.set(responseMessageId, request.contextKey);
|
|
605
|
-
lastEditAt = Date.now();
|
|
606
|
-
};
|
|
607
|
-
const flushResponse = async (force = false) => {
|
|
608
|
-
if (!accumulatedText.trim())
|
|
609
|
-
return;
|
|
610
|
-
await ensureResponse();
|
|
611
|
-
if (!responseMessageId)
|
|
612
|
-
return;
|
|
613
|
-
const now = Date.now();
|
|
614
|
-
if (!force && now - lastEditAt < EDIT_DEBOUNCE_MS)
|
|
615
|
-
return;
|
|
616
|
-
await runtime.editMessage(request.context, responseMessageId, {
|
|
617
|
-
text: trimDiscordMessage(accumulatedText),
|
|
618
|
-
fallbackText: trimDiscordMessage(accumulatedText),
|
|
619
|
-
buttons: [[{ label: "Abort", action: `discord_abort:${request.contextKey}` }]],
|
|
620
|
-
});
|
|
621
|
-
lastEditAt = Date.now();
|
|
622
|
-
};
|
|
623
|
-
const finalize = async () => {
|
|
624
|
-
if (finalized) {
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
finalized = true;
|
|
628
|
-
running = false;
|
|
629
|
-
clearInterval(typing);
|
|
630
|
-
if (flushTimer) {
|
|
631
|
-
clearTimeout(flushTimer);
|
|
632
|
-
flushTimer = undefined;
|
|
633
|
-
}
|
|
634
|
-
const finalText = accumulatedText.trim() || "Done.";
|
|
635
|
-
const chunks = splitDiscordMessage(finalText);
|
|
636
|
-
if (responseMessageId) {
|
|
637
|
-
const [first, ...rest] = chunks;
|
|
638
|
-
await runtime.editMessage(request.context, responseMessageId, { text: first ?? "Done.", fallbackText: first ?? "Done." });
|
|
639
|
-
for (const chunk of rest) {
|
|
640
|
-
await runtime.sendMessage(request.context, { text: chunk, fallbackText: chunk });
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
for (const chunk of chunks) {
|
|
645
|
-
await runtime.sendMessage(request.context, { text: chunk, fallbackText: chunk });
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
const callbacks = {
|
|
650
|
-
onTextDelta: (delta) => {
|
|
651
|
-
accumulatedText += delta;
|
|
652
|
-
progress.textCharacters = accumulatedText.length;
|
|
653
|
-
progress.updatedAt = Date.now();
|
|
654
|
-
void ensureResponse().then(() => scheduleFlush()).catch((error) => console.error("Failed to send Discord response:", error));
|
|
655
|
-
},
|
|
656
|
-
onToolStart: (toolName) => {
|
|
657
|
-
toolCounts.set(toolName, (toolCounts.get(toolName) ?? 0) + 1);
|
|
658
|
-
progress.currentTool = toolName;
|
|
659
|
-
progress.lastTool = toolName;
|
|
660
|
-
progress.updatedAt = Date.now();
|
|
661
|
-
appendActivity(request, {
|
|
662
|
-
status: "running",
|
|
663
|
-
type: "tool_started",
|
|
664
|
-
prompt: envelope.description,
|
|
665
|
-
detail: toolName,
|
|
666
|
-
threadId: session.getInfo().threadId,
|
|
667
|
-
workspace: session.getInfo().workspace,
|
|
668
|
-
agentId: session.getInfo().agentId,
|
|
669
|
-
});
|
|
670
|
-
if (toolVerbosity === "all") {
|
|
671
|
-
void runtime.sendMessage(request.context, { text: `Tool started: ${toolName}`, fallbackText: `Tool started: ${toolName}` }).catch(() => { });
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
|
-
onToolUpdate: () => { },
|
|
675
|
-
onToolEnd: (_toolCallId, isError) => {
|
|
676
|
-
progress.currentTool = undefined;
|
|
677
|
-
progress.updatedAt = Date.now();
|
|
678
|
-
appendActivity(request, {
|
|
679
|
-
status: isError ? "failed" : "completed",
|
|
680
|
-
type: isError ? "tool_failed" : "tool_completed",
|
|
681
|
-
prompt: envelope.description,
|
|
682
|
-
detail: "tool",
|
|
683
|
-
threadId: session.getInfo().threadId,
|
|
684
|
-
workspace: session.getInfo().workspace,
|
|
685
|
-
agentId: session.getInfo().agentId,
|
|
686
|
-
});
|
|
687
|
-
},
|
|
688
|
-
onTodoUpdate: (items) => {
|
|
689
|
-
progress.updatedAt = Date.now();
|
|
690
|
-
const text = [
|
|
691
|
-
"Plan:",
|
|
692
|
-
...items.map((item) => `${item.completed ? "[x]" : "[ ]"} ${item.text}`),
|
|
693
|
-
].join("\n");
|
|
694
|
-
if (!planMessageId) {
|
|
695
|
-
void runtime.sendMessage(request.context, { text, fallbackText: text }).then((result) => {
|
|
696
|
-
planMessageId = result.messageId;
|
|
697
|
-
}).catch(() => { });
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
void runtime.editMessage(request.context, planMessageId, { text, fallbackText: text }).catch(() => { });
|
|
701
|
-
}
|
|
702
|
-
},
|
|
703
|
-
onTurnComplete: () => { },
|
|
704
|
-
onAgentEnd: () => {
|
|
705
|
-
progress.status = "completed";
|
|
706
|
-
progress.completedAt = Date.now();
|
|
707
|
-
progress.updatedAt = progress.completedAt;
|
|
708
|
-
void finalize().catch((error) => console.error("Failed to finalize Discord response:", error));
|
|
709
|
-
},
|
|
710
|
-
};
|
|
634
|
+
engine.start();
|
|
711
635
|
try {
|
|
712
636
|
const info = session.getInfo();
|
|
713
637
|
if ((info.capabilities ?? capabilitiesOf(info)).auth) {
|
|
@@ -739,15 +663,15 @@ export function createDiscordBridge(config, registry) {
|
|
|
739
663
|
workspace: currentInfo.workspace,
|
|
740
664
|
description: envelope.description,
|
|
741
665
|
});
|
|
742
|
-
await session.prompt(envelope.input, callbacks);
|
|
666
|
+
await session.prompt(envelope.input, engine.callbacks);
|
|
743
667
|
updateSession(request, session);
|
|
744
668
|
progress.status = "completed";
|
|
745
669
|
progress.completedAt = Date.now();
|
|
746
670
|
progress.updatedAt = progress.completedAt;
|
|
747
|
-
await finalize();
|
|
748
|
-
await artifactService.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, turnId, new Date(startedAt));
|
|
671
|
+
await engine.finalize();
|
|
672
|
+
await artifactService.persistWorkspaceArtifactsForTurn(session.getInfo().workspace, engine.turnId, new Date(engine.startedAt));
|
|
749
673
|
if (config.discordAutoSendArtifacts) {
|
|
750
|
-
await sendRecentDiscordArtifacts(artifactDeps, request, session, new Date(startedAt), turnId);
|
|
674
|
+
await sendRecentDiscordArtifacts(artifactDeps, request, session, new Date(engine.startedAt), engine.turnId);
|
|
751
675
|
}
|
|
752
676
|
appendActivity(request, {
|
|
753
677
|
status: "completed",
|
|
@@ -756,7 +680,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
756
680
|
threadId: session.getInfo().threadId,
|
|
757
681
|
workspace: session.getInfo().workspace,
|
|
758
682
|
agentId: session.getInfo().agentId,
|
|
759
|
-
durationMs: Date.now() - startedAt,
|
|
683
|
+
durationMs: Date.now() - engine.startedAt,
|
|
760
684
|
});
|
|
761
685
|
audit(request, {
|
|
762
686
|
action: "prompt_completed",
|
|
@@ -772,13 +696,8 @@ export function createDiscordBridge(config, registry) {
|
|
|
772
696
|
progress.completedAt = Date.now();
|
|
773
697
|
progress.updatedAt = progress.completedAt;
|
|
774
698
|
progress.error = friendlyErrorText(error);
|
|
775
|
-
const errorText = renderPromptFailure(accumulatedText, error);
|
|
776
|
-
|
|
777
|
-
await runtime.editMessage(request.context, responseMessageId, { text: trimDiscordMessage(errorText), fallbackText: trimDiscordMessage(errorText) }).catch(() => { });
|
|
778
|
-
}
|
|
779
|
-
else {
|
|
780
|
-
await reply(request, errorText).catch(() => { });
|
|
781
|
-
}
|
|
699
|
+
const errorText = renderPromptFailure(engine.accumulatedText(), error);
|
|
700
|
+
await engine.fail(errorText);
|
|
782
701
|
appendActivity(request, {
|
|
783
702
|
status: "failed",
|
|
784
703
|
type: "prompt_failed",
|
|
@@ -787,7 +706,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
787
706
|
threadId: session.getInfo().threadId,
|
|
788
707
|
workspace: session.getInfo().workspace,
|
|
789
708
|
agentId: session.getInfo().agentId,
|
|
790
|
-
durationMs: Date.now() - startedAt,
|
|
709
|
+
durationMs: Date.now() - engine.startedAt,
|
|
791
710
|
});
|
|
792
711
|
audit(request, {
|
|
793
712
|
action: "prompt_failed",
|
|
@@ -800,8 +719,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
800
719
|
});
|
|
801
720
|
}
|
|
802
721
|
finally {
|
|
803
|
-
|
|
804
|
-
clearInterval(typing);
|
|
722
|
+
engine.stop();
|
|
805
723
|
busyState.processing = false;
|
|
806
724
|
await drainQueue(request).catch((error) => console.error("Failed to drain Discord queue:", error));
|
|
807
725
|
}
|
|
@@ -878,6 +796,58 @@ export function createDiscordBridge(config, registry) {
|
|
|
878
796
|
if (state)
|
|
879
797
|
state.artifactsDeliveredForTurnId = turnId;
|
|
880
798
|
};
|
|
799
|
+
const commandDispatcher = createSharedChannelCommandDispatcher({
|
|
800
|
+
transport: "discord",
|
|
801
|
+
bindings: [
|
|
802
|
+
{ names: ["start", "help"], handler: (request) => commandHelp(request) },
|
|
803
|
+
{ names: ["channels"], handler: (request) => deliverChannelAction(runtime, request.context, commandService.renderChannels()).then(() => { }) },
|
|
804
|
+
{ names: ["peers"], handler: (request) => deliverChannelAction(runtime, request.context, commandService.renderPeers()).then(() => { }) },
|
|
805
|
+
{ names: ["target"], handler: (request, argument) => deliverChannelAction(runtime, request.context, commandService.renderTargetPreference({ source: "discord", contextKey: request.contextKey, argument, preferencesStore })).then(() => { }) },
|
|
806
|
+
{ names: ["agents"], handler: (request) => deliverChannelAction(runtime, request.context, commandService.renderAgents()).then(() => { }) },
|
|
807
|
+
{ names: ["agent"], handler: (request, argument) => commandAgent(request, argument) },
|
|
808
|
+
{ names: ["auth"], handler: (request) => commandAuth(request) },
|
|
809
|
+
{ names: ["login"], handler: (request) => commandLogin(request) },
|
|
810
|
+
{ names: ["logout"], handler: (request) => commandLogout(request) },
|
|
811
|
+
{ names: ["session"], handler: (request) => commandSession(request) },
|
|
812
|
+
{ names: ["sessions"], handler: (request, argument) => commandSessions(request, argument) },
|
|
813
|
+
{ names: ["new"], handler: (request, argument) => commandNew(request, argument) },
|
|
814
|
+
{ names: ["switch", "attach"], handler: (request, argument) => commandSwitch(request, argument) },
|
|
815
|
+
{ names: ["model"], handler: (request, argument) => commandModel(request, argument) },
|
|
816
|
+
{ names: ["reasoning", "effort"], handler: (request, argument) => commandReasoning(request, argument) },
|
|
817
|
+
{ names: ["fast"], handler: (request, argument) => commandFast(request, argument) },
|
|
818
|
+
{ names: ["launch", "launch_profiles", "launch-profiles"], handler: (request, argument) => commandLaunch(request, argument) },
|
|
819
|
+
{ names: ["queue"], handler: (request, argument) => commandQueue(request, argument) },
|
|
820
|
+
{ names: ["clearqueue"], handler: (request) => { promptStore.clear(request.contextKey); return reply(request, "Queue cleared."); } },
|
|
821
|
+
{ names: ["cancel"], handler: (request, argument) => commandQueue(request, `cancel ${argument}`) },
|
|
822
|
+
{ names: ["abort", "stop"], handler: (request) => commandAbort(request) },
|
|
823
|
+
{ names: ["retry"], handler: (request) => commandRetry(request) },
|
|
824
|
+
{ names: ["sync"], handler: (request) => commandSync(request) },
|
|
825
|
+
{ names: ["tasks", "progress"], handler: (request) => commandProgress(request) },
|
|
826
|
+
{ names: ["activity"], handler: (request, argument) => commandActivity(request, argument) },
|
|
827
|
+
{ names: ["audit"], handler: (request, argument) => commandAudit(request, argument) },
|
|
828
|
+
{ names: ["artifacts"], handler: (request, argument) => commandArtifacts(request, argument) },
|
|
829
|
+
{ names: ["logs"], handler: (request, argument) => commandLogs(request, argument) },
|
|
830
|
+
{ names: ["version", "health", "status"], handler: (request) => commandVersion(request) },
|
|
831
|
+
{ names: ["diagnostics", "support"], handler: (request) => commandDiagnostics(request) },
|
|
832
|
+
{ names: ["restart"], handler: (request) => commandRestart(request) },
|
|
833
|
+
{ names: ["update"], handler: (request, argument) => commandUpdate(request, argument) },
|
|
834
|
+
{ names: ["lock"], handler: (request) => commandLock(request) },
|
|
835
|
+
{ names: ["unlock"], handler: (request) => { lockStore.clear(request.contextKey); return reply(request, "Session unlocked."); } },
|
|
836
|
+
{ names: ["locks"], handler: (request) => reply(request, lockStore.list().map((lock) => `${lock.contextKey}: ${lock.ownerLabel || lock.ownerUserId}`).join("\n") || "No active locks.") },
|
|
837
|
+
{ names: ["mirror"], handler: (request, argument) => commandMirror(request, argument) },
|
|
838
|
+
{ names: ["notify"], handler: (request, argument) => commandNotify(request, argument) },
|
|
839
|
+
{ names: ["voice"], handler: (request, argument) => commandVoice(request, argument) },
|
|
840
|
+
{ names: ["workspaces"], handler: (request) => commandWorkspaces(request) },
|
|
841
|
+
{ names: ["pin"], handler: (request, argument) => commandPin(request, argument) },
|
|
842
|
+
{ names: ["unpin"], handler: (request, argument) => commandUnpin(request, argument) },
|
|
843
|
+
{ names: ["pinned"], handler: (request) => commandPinned(request) },
|
|
844
|
+
{ names: ["handback"], handler: (request) => commandHandback(request) },
|
|
845
|
+
{ names: ["register_channel"], handler: (request) => commandRegisterChannel(request) },
|
|
846
|
+
{ names: ["link"], handler: (request, argument) => commandLink(request, argument) },
|
|
847
|
+
{ names: ["whoami"], handler: (request) => reply(request, request.authUser ? `${request.authUser.user.displayName} <${request.authUser.user.email}>\nGroups: ${request.authUser.groups.map((group) => group.name).join(", ")}` : "Not linked.") },
|
|
848
|
+
{ names: ["prompt"], handler: (request, argument) => handlePrompt(request, argument) },
|
|
849
|
+
],
|
|
850
|
+
});
|
|
881
851
|
const handleCommand = async (request, command, argument) => {
|
|
882
852
|
const normalized = command.toLowerCase();
|
|
883
853
|
const permission = requiredPermissionForDiscordCommand(normalized, argument);
|
|
@@ -885,158 +855,9 @@ export function createDiscordBridge(config, registry) {
|
|
|
885
855
|
return;
|
|
886
856
|
}
|
|
887
857
|
audit(request, { action: "command", status: "ok", description: `/${normalized} ${argument}`.trim() });
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
await commandHelp(request);
|
|
892
|
-
return;
|
|
893
|
-
case "channels":
|
|
894
|
-
await deliverChannelAction(runtime, request.context, commandService.renderChannels());
|
|
895
|
-
return;
|
|
896
|
-
case "agents":
|
|
897
|
-
await deliverChannelAction(runtime, request.context, commandService.renderAgents());
|
|
898
|
-
return;
|
|
899
|
-
case "agent":
|
|
900
|
-
await commandAgent(request, argument);
|
|
901
|
-
return;
|
|
902
|
-
case "auth":
|
|
903
|
-
await commandAuth(request);
|
|
904
|
-
return;
|
|
905
|
-
case "login":
|
|
906
|
-
await commandLogin(request);
|
|
907
|
-
return;
|
|
908
|
-
case "logout":
|
|
909
|
-
await commandLogout(request);
|
|
910
|
-
return;
|
|
911
|
-
case "session":
|
|
912
|
-
await commandSession(request);
|
|
913
|
-
return;
|
|
914
|
-
case "sessions":
|
|
915
|
-
await commandSessions(request, argument);
|
|
916
|
-
return;
|
|
917
|
-
case "new":
|
|
918
|
-
await commandNew(request, argument);
|
|
919
|
-
return;
|
|
920
|
-
case "switch":
|
|
921
|
-
case "attach":
|
|
922
|
-
await commandSwitch(request, argument);
|
|
923
|
-
return;
|
|
924
|
-
case "model":
|
|
925
|
-
await commandModel(request, argument);
|
|
926
|
-
return;
|
|
927
|
-
case "reasoning":
|
|
928
|
-
case "effort":
|
|
929
|
-
await commandReasoning(request, argument);
|
|
930
|
-
return;
|
|
931
|
-
case "fast":
|
|
932
|
-
await commandFast(request, argument);
|
|
933
|
-
return;
|
|
934
|
-
case "launch":
|
|
935
|
-
case "launch_profiles":
|
|
936
|
-
case "launch-profiles":
|
|
937
|
-
await commandLaunch(request, argument);
|
|
938
|
-
return;
|
|
939
|
-
case "queue":
|
|
940
|
-
await commandQueue(request, argument);
|
|
941
|
-
return;
|
|
942
|
-
case "clearqueue":
|
|
943
|
-
promptStore.clear(request.contextKey);
|
|
944
|
-
await reply(request, "Queue cleared.");
|
|
945
|
-
return;
|
|
946
|
-
case "cancel":
|
|
947
|
-
await commandQueue(request, `cancel ${argument}`);
|
|
948
|
-
return;
|
|
949
|
-
case "abort":
|
|
950
|
-
case "stop":
|
|
951
|
-
await commandAbort(request);
|
|
952
|
-
return;
|
|
953
|
-
case "retry":
|
|
954
|
-
await commandRetry(request);
|
|
955
|
-
return;
|
|
956
|
-
case "sync":
|
|
957
|
-
await commandSync(request);
|
|
958
|
-
return;
|
|
959
|
-
case "tasks":
|
|
960
|
-
case "progress":
|
|
961
|
-
await commandProgress(request);
|
|
962
|
-
return;
|
|
963
|
-
case "activity":
|
|
964
|
-
await commandActivity(request, argument);
|
|
965
|
-
return;
|
|
966
|
-
case "audit":
|
|
967
|
-
await commandAudit(request, argument);
|
|
968
|
-
return;
|
|
969
|
-
case "artifacts":
|
|
970
|
-
await commandArtifacts(request, argument);
|
|
971
|
-
return;
|
|
972
|
-
case "logs":
|
|
973
|
-
await commandLogs(request, argument);
|
|
974
|
-
return;
|
|
975
|
-
case "version":
|
|
976
|
-
case "health":
|
|
977
|
-
case "status":
|
|
978
|
-
await commandVersion(request);
|
|
979
|
-
return;
|
|
980
|
-
case "diagnostics":
|
|
981
|
-
await commandDiagnostics(request);
|
|
982
|
-
return;
|
|
983
|
-
case "support":
|
|
984
|
-
await commandDiagnostics(request);
|
|
985
|
-
return;
|
|
986
|
-
case "restart":
|
|
987
|
-
await commandRestart(request);
|
|
988
|
-
return;
|
|
989
|
-
case "update":
|
|
990
|
-
await commandUpdate(request, argument);
|
|
991
|
-
return;
|
|
992
|
-
case "lock":
|
|
993
|
-
await commandLock(request);
|
|
994
|
-
return;
|
|
995
|
-
case "unlock":
|
|
996
|
-
lockStore.clear(request.contextKey);
|
|
997
|
-
await reply(request, "Session unlocked.");
|
|
998
|
-
return;
|
|
999
|
-
case "locks":
|
|
1000
|
-
await reply(request, lockStore.list().map((lock) => `${lock.contextKey}: ${lock.ownerLabel || lock.ownerUserId}`).join("\n") || "No active locks.");
|
|
1001
|
-
return;
|
|
1002
|
-
case "mirror":
|
|
1003
|
-
await commandMirror(request, argument);
|
|
1004
|
-
return;
|
|
1005
|
-
case "notify":
|
|
1006
|
-
await commandNotify(request, argument);
|
|
1007
|
-
return;
|
|
1008
|
-
case "voice":
|
|
1009
|
-
await commandVoice(request, argument);
|
|
1010
|
-
return;
|
|
1011
|
-
case "workspaces":
|
|
1012
|
-
await commandWorkspaces(request);
|
|
1013
|
-
return;
|
|
1014
|
-
case "pin":
|
|
1015
|
-
await commandPin(request, argument);
|
|
1016
|
-
return;
|
|
1017
|
-
case "unpin":
|
|
1018
|
-
await commandUnpin(request, argument);
|
|
1019
|
-
return;
|
|
1020
|
-
case "pinned":
|
|
1021
|
-
await commandPinned(request);
|
|
1022
|
-
return;
|
|
1023
|
-
case "handback":
|
|
1024
|
-
await commandHandback(request);
|
|
1025
|
-
return;
|
|
1026
|
-
case "register_channel":
|
|
1027
|
-
await commandRegisterChannel(request);
|
|
1028
|
-
return;
|
|
1029
|
-
case "link":
|
|
1030
|
-
await commandLink(request, argument);
|
|
1031
|
-
return;
|
|
1032
|
-
case "whoami":
|
|
1033
|
-
await reply(request, request.authUser ? `${request.authUser.user.displayName} <${request.authUser.user.email}>\nGroups: ${request.authUser.groups.map((group) => group.name).join(", ")}` : "Not linked.");
|
|
1034
|
-
return;
|
|
1035
|
-
case "prompt":
|
|
1036
|
-
await handlePrompt(request, argument);
|
|
1037
|
-
return;
|
|
1038
|
-
default:
|
|
1039
|
-
await reply(request, `Unknown command: /${normalized}`);
|
|
858
|
+
const result = await commandDispatcher.dispatch(request, normalized, argument);
|
|
859
|
+
if (!result.matched) {
|
|
860
|
+
await reply(request, `Unknown command: /${normalized}`);
|
|
1040
861
|
}
|
|
1041
862
|
};
|
|
1042
863
|
const commandHelp = async (request) => {
|
|
@@ -1046,7 +867,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
1046
867
|
"",
|
|
1047
868
|
"Send a message to prompt the selected agent, or use slash commands.",
|
|
1048
869
|
"",
|
|
1049
|
-
|
|
870
|
+
`Core commands: ${discordHelpCommandList()}.`,
|
|
1050
871
|
"",
|
|
1051
872
|
renderSessionInfoPlain(session.getInfo()),
|
|
1052
873
|
].join("\n"));
|
|
@@ -1595,33 +1416,32 @@ export function createDiscordBridge(config, registry) {
|
|
|
1595
1416
|
await deliverChannelAction(runtime, request.context, commandService.renderHandback(result));
|
|
1596
1417
|
};
|
|
1597
1418
|
const commandMirror = async (request, argument) => {
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
await
|
|
1419
|
+
const session = await getSession(request, { deferThreadStart: true });
|
|
1420
|
+
const info = session.getInfo();
|
|
1421
|
+
await deliverChannelAction(runtime, request.context, commandService.renderMirrorPreference({
|
|
1422
|
+
source: "discord",
|
|
1423
|
+
contextKey: request.contextKey,
|
|
1424
|
+
argument,
|
|
1425
|
+
preferencesStore,
|
|
1426
|
+
cliMirrorSupported: capabilitiesOf(info).cliMirror,
|
|
1427
|
+
agentLabel: info.agentLabel,
|
|
1428
|
+
}));
|
|
1601
1429
|
};
|
|
1602
1430
|
const commandNotify = async (request, argument) => {
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1431
|
+
await deliverChannelAction(runtime, request.context, commandService.renderNotifyPreference({
|
|
1432
|
+
source: "discord",
|
|
1433
|
+
contextKey: request.contextKey,
|
|
1434
|
+
argument,
|
|
1435
|
+
preferencesStore,
|
|
1436
|
+
}));
|
|
1606
1437
|
};
|
|
1607
1438
|
const commandVoice = async (request, argument) => {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
preferencesStore.update(request.contextKey, { voiceLanguage: parts[1] === "auto" ? null : parts[1] });
|
|
1615
|
-
}
|
|
1616
|
-
else if ((parts[0] === "transcribe-only" || parts[0] === "transcribe_only") && parts[1]) {
|
|
1617
|
-
preferencesStore.update(request.contextKey, { voiceTranscribeOnly: ["on", "true", "yes", "1"].includes(parts[1]) });
|
|
1618
|
-
}
|
|
1619
|
-
else if (argument.trim()) {
|
|
1620
|
-
await reply(request, "Usage: `/voice`, `/voice backend auto|parakeet|faster-whisper|openai`, `/voice language auto|<code>`, or `/voice transcribe_only on|off`.");
|
|
1621
|
-
return;
|
|
1622
|
-
}
|
|
1623
|
-
const prefs = preferencesStore.get(request.contextKey);
|
|
1624
|
-
await reply(request, `Voice backend: ${prefs.voiceBackend ?? config.voicePreferredBackend}\nLanguage: ${prefs.voiceLanguage ?? config.voiceDefaultLanguage ?? "auto"}\nTranscribe only: ${prefs.voiceTranscribeOnly ?? config.voiceTranscribeOnly}`);
|
|
1439
|
+
await deliverChannelAction(runtime, request.context, await commandService.renderVoicePreference({
|
|
1440
|
+
source: "discord",
|
|
1441
|
+
contextKey: request.contextKey,
|
|
1442
|
+
argument,
|
|
1443
|
+
preferencesStore,
|
|
1444
|
+
}));
|
|
1625
1445
|
};
|
|
1626
1446
|
const commandRegisterChannel = async (request) => {
|
|
1627
1447
|
const channel = userStore.registerDiscordChannel({
|
|
@@ -1781,6 +1601,17 @@ export function createDiscordBridge(config, registry) {
|
|
|
1781
1601
|
await commandQueue(request, `${queueMatch[1]} ${queueMatch[3]}`);
|
|
1782
1602
|
return;
|
|
1783
1603
|
}
|
|
1604
|
+
const peerQueueMatch = action.match(/^discord_peer_queue_cancel:([^:]+):([^:]+)$/);
|
|
1605
|
+
if (peerQueueMatch?.[1] && peerQueueMatch[2]) {
|
|
1606
|
+
await remoteClient.webProxy(peerQueueMatch[1], {
|
|
1607
|
+
method: "POST",
|
|
1608
|
+
path: "/api/queue",
|
|
1609
|
+
body: { action: "cancel", id: peerQueueMatch[2] },
|
|
1610
|
+
contextKey: request.contextKey,
|
|
1611
|
+
}, actorFor(request), request.contextKey);
|
|
1612
|
+
await reply(request, `Cancelled remote queued prompt ${peerQueueMatch[2]}.`, { ephemeral: true });
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1784
1615
|
const artifactMatch = action.match(/^discord_artifact_(send|zip|delete):(.+):([^:]+)$/);
|
|
1785
1616
|
if (artifactMatch?.[1] && artifactMatch[2] === request.contextKey) {
|
|
1786
1617
|
await commandArtifacts(request, `${artifactMatch[1]} ${artifactMatch[3]}`);
|