@nordbyte/nordrelay 0.7.0 → 0.8.1
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 +35 -0
- package/README.md +118 -49
- package/dist/activity-events.js +2 -2
- package/dist/adapter-conformance.js +61 -0
- package/dist/bot.js +18 -31
- package/dist/channel-adapter.js +33 -6
- package/dist/channel-command-catalog.js +6 -0
- package/dist/channel-command-core.js +60 -0
- package/dist/channel-command-service.js +20 -4
- package/dist/channel-mirror-registry.js +9 -2
- package/dist/channel-prompt-engine.js +177 -0
- package/dist/channel-turn-lifecycle.js +73 -0
- package/dist/config-metadata.js +67 -8
- package/dist/config.js +48 -1
- package/dist/context-key.js +32 -0
- package/dist/discord-bot.js +99 -327
- package/dist/index.js +9 -0
- package/dist/metrics.js +2 -0
- package/dist/peer-client.js +90 -2
- package/dist/peer-readiness.js +77 -0
- package/dist/peer-runtime-service.js +22 -0
- package/dist/peer-server.js +20 -4
- package/dist/peer-store.js +17 -2
- package/dist/relay-runtime-helpers.js +3 -1
- package/dist/relay-runtime.js +7 -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/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 +8 -0
- package/dist/web-dashboard-access-routes.js +62 -0
- package/dist/web-dashboard-assets.js +1 -0
- package/dist/web-dashboard-pages.js +14 -4
- package/dist/web-dashboard-peer-routes.js +32 -11
- package/dist/web-dashboard.js +34 -0
- package/dist/web-state.js +2 -2
- package/dist/webui-assets/dashboard.css +193 -0
- package/dist/webui-assets/dashboard.js +546 -145
- package/package.json +3 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +105 -11
package/dist/discord-bot.js
CHANGED
|
@@ -12,8 +12,10 @@ import { AuditLogStore } from "./audit-log.js";
|
|
|
12
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";
|
|
16
17
|
import { discordHelpCommandList } from "./channel-command-catalog.js";
|
|
18
|
+
import { createChannelPromptEngine } from "./channel-prompt-engine.js";
|
|
17
19
|
import { runChannelPeerPrompt } from "./channel-peer-prompt.js";
|
|
18
20
|
import { deliverChannelAction } from "./channel-runtime.js";
|
|
19
21
|
import { checkAuthStatus, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
|
|
@@ -595,157 +597,41 @@ export function createDiscordBridge(config, registry) {
|
|
|
595
597
|
}
|
|
596
598
|
const busyState = getBusyState(request.contextKey);
|
|
597
599
|
busyState.processing = true;
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
let accumulatedText = "";
|
|
603
|
-
let responseMessageId;
|
|
604
|
-
let planMessageId;
|
|
605
|
-
let flushTimer;
|
|
606
|
-
let lastEditAt = 0;
|
|
607
|
-
let running = true;
|
|
608
|
-
let finalized = false;
|
|
609
|
-
const toolCounts = new Map();
|
|
610
|
-
const toolVerbosity = config.toolVerbosity;
|
|
611
|
-
const startedAt = Date.now();
|
|
612
|
-
const turnId = randomUUID().slice(0, 12);
|
|
613
|
-
const progress = {
|
|
614
|
-
status: "running",
|
|
600
|
+
const engine = createChannelPromptEngine({
|
|
601
|
+
runtime,
|
|
602
|
+
context: request.context,
|
|
603
|
+
contextKey: request.contextKey,
|
|
615
604
|
promptDescription: envelope.description,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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;
|
|
621
633
|
turnProgress.set(request.contextKey, progress);
|
|
622
|
-
|
|
623
|
-
if (flushTimer || !running) {
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
const delay = Math.max(0, EDIT_DEBOUNCE_MS - (Date.now() - lastEditAt));
|
|
627
|
-
flushTimer = setTimeout(() => {
|
|
628
|
-
flushTimer = undefined;
|
|
629
|
-
void flushResponse().catch((error) => console.error("Failed to edit Discord response:", error));
|
|
630
|
-
}, delay);
|
|
631
|
-
};
|
|
632
|
-
const ensureResponse = async () => {
|
|
633
|
-
if (responseMessageId)
|
|
634
|
-
return;
|
|
635
|
-
const preview = trimDiscordMessage(accumulatedText || "Working...");
|
|
636
|
-
const sent = await runtime.sendMessage(request.context, {
|
|
637
|
-
text: preview,
|
|
638
|
-
fallbackText: preview,
|
|
639
|
-
buttons: [[{ label: "Abort", action: `discord_abort:${request.contextKey}` }]],
|
|
640
|
-
});
|
|
641
|
-
responseMessageId = sent.messageId;
|
|
642
|
-
responseOwners.set(responseMessageId, request.contextKey);
|
|
643
|
-
lastEditAt = Date.now();
|
|
644
|
-
};
|
|
645
|
-
const flushResponse = async (force = false) => {
|
|
646
|
-
if (!accumulatedText.trim())
|
|
647
|
-
return;
|
|
648
|
-
await ensureResponse();
|
|
649
|
-
if (!responseMessageId)
|
|
650
|
-
return;
|
|
651
|
-
const now = Date.now();
|
|
652
|
-
if (!force && now - lastEditAt < EDIT_DEBOUNCE_MS)
|
|
653
|
-
return;
|
|
654
|
-
await runtime.editMessage(request.context, responseMessageId, {
|
|
655
|
-
text: trimDiscordMessage(accumulatedText),
|
|
656
|
-
fallbackText: trimDiscordMessage(accumulatedText),
|
|
657
|
-
buttons: [[{ label: "Abort", action: `discord_abort:${request.contextKey}` }]],
|
|
658
|
-
});
|
|
659
|
-
lastEditAt = Date.now();
|
|
660
|
-
};
|
|
661
|
-
const finalize = async () => {
|
|
662
|
-
if (finalized) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
finalized = true;
|
|
666
|
-
running = false;
|
|
667
|
-
clearInterval(typing);
|
|
668
|
-
if (flushTimer) {
|
|
669
|
-
clearTimeout(flushTimer);
|
|
670
|
-
flushTimer = undefined;
|
|
671
|
-
}
|
|
672
|
-
const finalText = accumulatedText.trim() || "Done.";
|
|
673
|
-
const chunks = splitDiscordMessage(finalText);
|
|
674
|
-
if (responseMessageId) {
|
|
675
|
-
const [first, ...rest] = chunks;
|
|
676
|
-
await runtime.editMessage(request.context, responseMessageId, { text: first ?? "Done.", fallbackText: first ?? "Done." });
|
|
677
|
-
for (const chunk of rest) {
|
|
678
|
-
await runtime.sendMessage(request.context, { text: chunk, fallbackText: chunk });
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
else {
|
|
682
|
-
for (const chunk of chunks) {
|
|
683
|
-
await runtime.sendMessage(request.context, { text: chunk, fallbackText: chunk });
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
const callbacks = {
|
|
688
|
-
onTextDelta: (delta) => {
|
|
689
|
-
accumulatedText += delta;
|
|
690
|
-
progress.textCharacters = accumulatedText.length;
|
|
691
|
-
progress.updatedAt = Date.now();
|
|
692
|
-
void ensureResponse().then(() => scheduleFlush()).catch((error) => console.error("Failed to send Discord response:", error));
|
|
693
|
-
},
|
|
694
|
-
onToolStart: (toolName) => {
|
|
695
|
-
toolCounts.set(toolName, (toolCounts.get(toolName) ?? 0) + 1);
|
|
696
|
-
progress.currentTool = toolName;
|
|
697
|
-
progress.lastTool = toolName;
|
|
698
|
-
progress.updatedAt = Date.now();
|
|
699
|
-
appendActivity(request, {
|
|
700
|
-
status: "running",
|
|
701
|
-
type: "tool_started",
|
|
702
|
-
prompt: envelope.description,
|
|
703
|
-
detail: toolName,
|
|
704
|
-
threadId: session.getInfo().threadId,
|
|
705
|
-
workspace: session.getInfo().workspace,
|
|
706
|
-
agentId: session.getInfo().agentId,
|
|
707
|
-
});
|
|
708
|
-
if (toolVerbosity === "all") {
|
|
709
|
-
void runtime.sendMessage(request.context, { text: `Tool started: ${toolName}`, fallbackText: `Tool started: ${toolName}` }).catch(() => { });
|
|
710
|
-
}
|
|
711
|
-
},
|
|
712
|
-
onToolUpdate: () => { },
|
|
713
|
-
onToolEnd: (_toolCallId, isError) => {
|
|
714
|
-
progress.currentTool = undefined;
|
|
715
|
-
progress.updatedAt = Date.now();
|
|
716
|
-
appendActivity(request, {
|
|
717
|
-
status: isError ? "failed" : "completed",
|
|
718
|
-
type: isError ? "tool_failed" : "tool_completed",
|
|
719
|
-
prompt: envelope.description,
|
|
720
|
-
detail: "tool",
|
|
721
|
-
threadId: session.getInfo().threadId,
|
|
722
|
-
workspace: session.getInfo().workspace,
|
|
723
|
-
agentId: session.getInfo().agentId,
|
|
724
|
-
});
|
|
725
|
-
},
|
|
726
|
-
onTodoUpdate: (items) => {
|
|
727
|
-
progress.updatedAt = Date.now();
|
|
728
|
-
const text = [
|
|
729
|
-
"Plan:",
|
|
730
|
-
...items.map((item) => `${item.completed ? "[x]" : "[ ]"} ${item.text}`),
|
|
731
|
-
].join("\n");
|
|
732
|
-
if (!planMessageId) {
|
|
733
|
-
void runtime.sendMessage(request.context, { text, fallbackText: text }).then((result) => {
|
|
734
|
-
planMessageId = result.messageId;
|
|
735
|
-
}).catch(() => { });
|
|
736
|
-
}
|
|
737
|
-
else {
|
|
738
|
-
void runtime.editMessage(request.context, planMessageId, { text, fallbackText: text }).catch(() => { });
|
|
739
|
-
}
|
|
740
|
-
},
|
|
741
|
-
onTurnComplete: () => { },
|
|
742
|
-
onAgentEnd: () => {
|
|
743
|
-
progress.status = "completed";
|
|
744
|
-
progress.completedAt = Date.now();
|
|
745
|
-
progress.updatedAt = progress.completedAt;
|
|
746
|
-
void finalize().catch((error) => console.error("Failed to finalize Discord response:", error));
|
|
747
|
-
},
|
|
748
|
-
};
|
|
634
|
+
engine.start();
|
|
749
635
|
try {
|
|
750
636
|
const info = session.getInfo();
|
|
751
637
|
if ((info.capabilities ?? capabilitiesOf(info)).auth) {
|
|
@@ -777,15 +663,15 @@ export function createDiscordBridge(config, registry) {
|
|
|
777
663
|
workspace: currentInfo.workspace,
|
|
778
664
|
description: envelope.description,
|
|
779
665
|
});
|
|
780
|
-
await session.prompt(envelope.input, callbacks);
|
|
666
|
+
await session.prompt(envelope.input, engine.callbacks);
|
|
781
667
|
updateSession(request, session);
|
|
782
668
|
progress.status = "completed";
|
|
783
669
|
progress.completedAt = Date.now();
|
|
784
670
|
progress.updatedAt = progress.completedAt;
|
|
785
|
-
await finalize();
|
|
786
|
-
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));
|
|
787
673
|
if (config.discordAutoSendArtifacts) {
|
|
788
|
-
await sendRecentDiscordArtifacts(artifactDeps, request, session, new Date(startedAt), turnId);
|
|
674
|
+
await sendRecentDiscordArtifacts(artifactDeps, request, session, new Date(engine.startedAt), engine.turnId);
|
|
789
675
|
}
|
|
790
676
|
appendActivity(request, {
|
|
791
677
|
status: "completed",
|
|
@@ -794,7 +680,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
794
680
|
threadId: session.getInfo().threadId,
|
|
795
681
|
workspace: session.getInfo().workspace,
|
|
796
682
|
agentId: session.getInfo().agentId,
|
|
797
|
-
durationMs: Date.now() - startedAt,
|
|
683
|
+
durationMs: Date.now() - engine.startedAt,
|
|
798
684
|
});
|
|
799
685
|
audit(request, {
|
|
800
686
|
action: "prompt_completed",
|
|
@@ -810,13 +696,8 @@ export function createDiscordBridge(config, registry) {
|
|
|
810
696
|
progress.completedAt = Date.now();
|
|
811
697
|
progress.updatedAt = progress.completedAt;
|
|
812
698
|
progress.error = friendlyErrorText(error);
|
|
813
|
-
const errorText = renderPromptFailure(accumulatedText, error);
|
|
814
|
-
|
|
815
|
-
await runtime.editMessage(request.context, responseMessageId, { text: trimDiscordMessage(errorText), fallbackText: trimDiscordMessage(errorText) }).catch(() => { });
|
|
816
|
-
}
|
|
817
|
-
else {
|
|
818
|
-
await reply(request, errorText).catch(() => { });
|
|
819
|
-
}
|
|
699
|
+
const errorText = renderPromptFailure(engine.accumulatedText(), error);
|
|
700
|
+
await engine.fail(errorText);
|
|
820
701
|
appendActivity(request, {
|
|
821
702
|
status: "failed",
|
|
822
703
|
type: "prompt_failed",
|
|
@@ -825,7 +706,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
825
706
|
threadId: session.getInfo().threadId,
|
|
826
707
|
workspace: session.getInfo().workspace,
|
|
827
708
|
agentId: session.getInfo().agentId,
|
|
828
|
-
durationMs: Date.now() - startedAt,
|
|
709
|
+
durationMs: Date.now() - engine.startedAt,
|
|
829
710
|
});
|
|
830
711
|
audit(request, {
|
|
831
712
|
action: "prompt_failed",
|
|
@@ -838,8 +719,7 @@ export function createDiscordBridge(config, registry) {
|
|
|
838
719
|
});
|
|
839
720
|
}
|
|
840
721
|
finally {
|
|
841
|
-
|
|
842
|
-
clearInterval(typing);
|
|
722
|
+
engine.stop();
|
|
843
723
|
busyState.processing = false;
|
|
844
724
|
await drainQueue(request).catch((error) => console.error("Failed to drain Discord queue:", error));
|
|
845
725
|
}
|
|
@@ -916,6 +796,58 @@ export function createDiscordBridge(config, registry) {
|
|
|
916
796
|
if (state)
|
|
917
797
|
state.artifactsDeliveredForTurnId = turnId;
|
|
918
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
|
+
});
|
|
919
851
|
const handleCommand = async (request, command, argument) => {
|
|
920
852
|
const normalized = command.toLowerCase();
|
|
921
853
|
const permission = requiredPermissionForDiscordCommand(normalized, argument);
|
|
@@ -923,169 +855,9 @@ export function createDiscordBridge(config, registry) {
|
|
|
923
855
|
return;
|
|
924
856
|
}
|
|
925
857
|
audit(request, { action: "command", status: "ok", description: `/${normalized} ${argument}`.trim() });
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
await commandHelp(request);
|
|
930
|
-
return;
|
|
931
|
-
case "channels":
|
|
932
|
-
await deliverChannelAction(runtime, request.context, commandService.renderChannels());
|
|
933
|
-
return;
|
|
934
|
-
case "peers":
|
|
935
|
-
await deliverChannelAction(runtime, request.context, commandService.renderPeers());
|
|
936
|
-
return;
|
|
937
|
-
case "target":
|
|
938
|
-
await deliverChannelAction(runtime, request.context, commandService.renderTargetPreference({
|
|
939
|
-
source: "discord",
|
|
940
|
-
contextKey: request.contextKey,
|
|
941
|
-
argument,
|
|
942
|
-
preferencesStore,
|
|
943
|
-
}));
|
|
944
|
-
return;
|
|
945
|
-
case "agents":
|
|
946
|
-
await deliverChannelAction(runtime, request.context, commandService.renderAgents());
|
|
947
|
-
return;
|
|
948
|
-
case "agent":
|
|
949
|
-
await commandAgent(request, argument);
|
|
950
|
-
return;
|
|
951
|
-
case "auth":
|
|
952
|
-
await commandAuth(request);
|
|
953
|
-
return;
|
|
954
|
-
case "login":
|
|
955
|
-
await commandLogin(request);
|
|
956
|
-
return;
|
|
957
|
-
case "logout":
|
|
958
|
-
await commandLogout(request);
|
|
959
|
-
return;
|
|
960
|
-
case "session":
|
|
961
|
-
await commandSession(request);
|
|
962
|
-
return;
|
|
963
|
-
case "sessions":
|
|
964
|
-
await commandSessions(request, argument);
|
|
965
|
-
return;
|
|
966
|
-
case "new":
|
|
967
|
-
await commandNew(request, argument);
|
|
968
|
-
return;
|
|
969
|
-
case "switch":
|
|
970
|
-
case "attach":
|
|
971
|
-
await commandSwitch(request, argument);
|
|
972
|
-
return;
|
|
973
|
-
case "model":
|
|
974
|
-
await commandModel(request, argument);
|
|
975
|
-
return;
|
|
976
|
-
case "reasoning":
|
|
977
|
-
case "effort":
|
|
978
|
-
await commandReasoning(request, argument);
|
|
979
|
-
return;
|
|
980
|
-
case "fast":
|
|
981
|
-
await commandFast(request, argument);
|
|
982
|
-
return;
|
|
983
|
-
case "launch":
|
|
984
|
-
case "launch_profiles":
|
|
985
|
-
case "launch-profiles":
|
|
986
|
-
await commandLaunch(request, argument);
|
|
987
|
-
return;
|
|
988
|
-
case "queue":
|
|
989
|
-
await commandQueue(request, argument);
|
|
990
|
-
return;
|
|
991
|
-
case "clearqueue":
|
|
992
|
-
promptStore.clear(request.contextKey);
|
|
993
|
-
await reply(request, "Queue cleared.");
|
|
994
|
-
return;
|
|
995
|
-
case "cancel":
|
|
996
|
-
await commandQueue(request, `cancel ${argument}`);
|
|
997
|
-
return;
|
|
998
|
-
case "abort":
|
|
999
|
-
case "stop":
|
|
1000
|
-
await commandAbort(request);
|
|
1001
|
-
return;
|
|
1002
|
-
case "retry":
|
|
1003
|
-
await commandRetry(request);
|
|
1004
|
-
return;
|
|
1005
|
-
case "sync":
|
|
1006
|
-
await commandSync(request);
|
|
1007
|
-
return;
|
|
1008
|
-
case "tasks":
|
|
1009
|
-
case "progress":
|
|
1010
|
-
await commandProgress(request);
|
|
1011
|
-
return;
|
|
1012
|
-
case "activity":
|
|
1013
|
-
await commandActivity(request, argument);
|
|
1014
|
-
return;
|
|
1015
|
-
case "audit":
|
|
1016
|
-
await commandAudit(request, argument);
|
|
1017
|
-
return;
|
|
1018
|
-
case "artifacts":
|
|
1019
|
-
await commandArtifacts(request, argument);
|
|
1020
|
-
return;
|
|
1021
|
-
case "logs":
|
|
1022
|
-
await commandLogs(request, argument);
|
|
1023
|
-
return;
|
|
1024
|
-
case "version":
|
|
1025
|
-
case "health":
|
|
1026
|
-
case "status":
|
|
1027
|
-
await commandVersion(request);
|
|
1028
|
-
return;
|
|
1029
|
-
case "diagnostics":
|
|
1030
|
-
await commandDiagnostics(request);
|
|
1031
|
-
return;
|
|
1032
|
-
case "support":
|
|
1033
|
-
await commandDiagnostics(request);
|
|
1034
|
-
return;
|
|
1035
|
-
case "restart":
|
|
1036
|
-
await commandRestart(request);
|
|
1037
|
-
return;
|
|
1038
|
-
case "update":
|
|
1039
|
-
await commandUpdate(request, argument);
|
|
1040
|
-
return;
|
|
1041
|
-
case "lock":
|
|
1042
|
-
await commandLock(request);
|
|
1043
|
-
return;
|
|
1044
|
-
case "unlock":
|
|
1045
|
-
lockStore.clear(request.contextKey);
|
|
1046
|
-
await reply(request, "Session unlocked.");
|
|
1047
|
-
return;
|
|
1048
|
-
case "locks":
|
|
1049
|
-
await reply(request, lockStore.list().map((lock) => `${lock.contextKey}: ${lock.ownerLabel || lock.ownerUserId}`).join("\n") || "No active locks.");
|
|
1050
|
-
return;
|
|
1051
|
-
case "mirror":
|
|
1052
|
-
await commandMirror(request, argument);
|
|
1053
|
-
return;
|
|
1054
|
-
case "notify":
|
|
1055
|
-
await commandNotify(request, argument);
|
|
1056
|
-
return;
|
|
1057
|
-
case "voice":
|
|
1058
|
-
await commandVoice(request, argument);
|
|
1059
|
-
return;
|
|
1060
|
-
case "workspaces":
|
|
1061
|
-
await commandWorkspaces(request);
|
|
1062
|
-
return;
|
|
1063
|
-
case "pin":
|
|
1064
|
-
await commandPin(request, argument);
|
|
1065
|
-
return;
|
|
1066
|
-
case "unpin":
|
|
1067
|
-
await commandUnpin(request, argument);
|
|
1068
|
-
return;
|
|
1069
|
-
case "pinned":
|
|
1070
|
-
await commandPinned(request);
|
|
1071
|
-
return;
|
|
1072
|
-
case "handback":
|
|
1073
|
-
await commandHandback(request);
|
|
1074
|
-
return;
|
|
1075
|
-
case "register_channel":
|
|
1076
|
-
await commandRegisterChannel(request);
|
|
1077
|
-
return;
|
|
1078
|
-
case "link":
|
|
1079
|
-
await commandLink(request, argument);
|
|
1080
|
-
return;
|
|
1081
|
-
case "whoami":
|
|
1082
|
-
await reply(request, request.authUser ? `${request.authUser.user.displayName} <${request.authUser.user.email}>\nGroups: ${request.authUser.groups.map((group) => group.name).join(", ")}` : "Not linked.");
|
|
1083
|
-
return;
|
|
1084
|
-
case "prompt":
|
|
1085
|
-
await handlePrompt(request, argument);
|
|
1086
|
-
return;
|
|
1087
|
-
default:
|
|
1088
|
-
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}`);
|
|
1089
861
|
}
|
|
1090
862
|
};
|
|
1091
863
|
const commandHelp = async (request) => {
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { webhookCallback } from "grammy";
|
|
|
5
5
|
import { agentLabel } from "./agent.js";
|
|
6
6
|
import { createBot, registerCommands } from "./bot.js";
|
|
7
7
|
import { createDiscordBridge } from "./discord-bot.js";
|
|
8
|
+
import { createSlackBridge } from "./slack-bot.js";
|
|
8
9
|
import { checkAuthStatus } from "./codex-auth.js";
|
|
9
10
|
import { describeCodexCli, resolveCodexCli } from "./codex-cli.js";
|
|
10
11
|
import { checkClaudeCodeAuthStatus } from "./claude-code-auth.js";
|
|
@@ -27,6 +28,7 @@ import { UserStore } from "./user-management.js";
|
|
|
27
28
|
let registry;
|
|
28
29
|
let bot;
|
|
29
30
|
let discordBridge;
|
|
31
|
+
let slackBridge;
|
|
30
32
|
let webhookServer;
|
|
31
33
|
let peerServer;
|
|
32
34
|
let peerRuntime;
|
|
@@ -43,6 +45,8 @@ try {
|
|
|
43
45
|
}
|
|
44
46
|
discordBridge = createDiscordBridge(config, registry);
|
|
45
47
|
await discordBridge?.start();
|
|
48
|
+
slackBridge = createSlackBridge(config, registry);
|
|
49
|
+
await slackBridge?.start();
|
|
46
50
|
if (config.peerEnabled) {
|
|
47
51
|
peerRuntime = new RelayRuntime(config);
|
|
48
52
|
peerServer = await startPeerServer({ config, runtime: peerRuntime });
|
|
@@ -94,6 +98,7 @@ try {
|
|
|
94
98
|
console.log("Session mode: per chat context");
|
|
95
99
|
console.log(`Telegram: ${config.telegramEnabled ? config.telegramTransport : "disabled"}`);
|
|
96
100
|
console.log(`Discord: ${config.discordEnabled ? "enabled" : "disabled"}`);
|
|
101
|
+
console.log(`Slack: ${config.slackEnabled ? (config.slackSocketMode ? "socket-mode" : `http:${config.slackPort}`) : "disabled"}`);
|
|
97
102
|
console.log(`Peers: ${peerServer ? peerServer.url : "disabled"}`);
|
|
98
103
|
await writeConnectorState({
|
|
99
104
|
status: "ready",
|
|
@@ -111,6 +116,7 @@ try {
|
|
|
111
116
|
openClawGateway: config.openClawGatewayUrl,
|
|
112
117
|
telegramTransport: config.telegramTransport,
|
|
113
118
|
discordEnabled: config.discordEnabled,
|
|
119
|
+
slackEnabled: config.slackEnabled,
|
|
114
120
|
peerEnabled: config.peerEnabled,
|
|
115
121
|
peerUrl: peerServer?.url,
|
|
116
122
|
peerTlsFingerprint: peerServer?.tlsFingerprint,
|
|
@@ -164,6 +170,9 @@ const shutdown = (signal) => {
|
|
|
164
170
|
void discordBridge?.stop().catch((error) => {
|
|
165
171
|
console.warn("Failed to stop Discord bridge:", error instanceof Error ? error.message : String(error));
|
|
166
172
|
});
|
|
173
|
+
void slackBridge?.stop().catch((error) => {
|
|
174
|
+
console.warn("Failed to stop Slack bridge:", error instanceof Error ? error.message : String(error));
|
|
175
|
+
});
|
|
167
176
|
webhookServer?.close();
|
|
168
177
|
void peerServer?.close().catch((error) => {
|
|
169
178
|
console.warn("Failed to stop peer server:", error instanceof Error ? error.message : String(error));
|
package/dist/metrics.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
2
2
|
import { getDiscordRateLimitMetrics } from "./discord-rate-limit.js";
|
|
3
|
+
import { getSlackRateLimitMetrics } from "./slack-rate-limit.js";
|
|
3
4
|
import { getTelegramRateLimitMetrics } from "./telegram-rate-limit.js";
|
|
4
5
|
const startedAt = Date.now();
|
|
5
6
|
const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
|
|
@@ -35,6 +36,7 @@ export function buildRuntimeMetrics(input) {
|
|
|
35
36
|
adapters: {
|
|
36
37
|
telegram: getTelegramRateLimitMetrics(),
|
|
37
38
|
discord: getDiscordRateLimitMetrics(),
|
|
39
|
+
slack: getSlackRateLimitMetrics(),
|
|
38
40
|
},
|
|
39
41
|
};
|
|
40
42
|
}
|