@cuylabs/channel-slack-agent-core 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/README.md +7 -0
- package/dist/adapter/index.d.ts +6 -3
- package/dist/adapter/index.js +2 -2
- package/dist/{adapter-Cmd2C90g.d.ts → adapter-B3CI611y.d.ts} +1 -1
- package/dist/app-surface.d.ts +13 -3
- package/dist/app-surface.js +4 -4
- package/dist/app.d.ts +7 -3
- package/dist/app.js +5 -5
- package/dist/artifacts/index.d.ts +57 -0
- package/dist/artifacts/index.js +6 -0
- package/dist/assistant/index.d.ts +5 -3
- package/dist/assistant/index.js +2 -2
- package/dist/{chunk-FNT4TXNQ.js → chunk-236WN6JD.js} +1 -1
- package/dist/{chunk-5NQYLOAW.js → chunk-2R7B7NJR.js} +1 -1
- package/dist/{chunk-P7KK5GQG.js → chunk-6T6N4MRK.js} +13 -25
- package/dist/{chunk-ZDVD46RT.js → chunk-7YZWCSML.js} +263 -4
- package/dist/chunk-C7CHMYV6.js +226 -0
- package/dist/{chunk-TIQGJ52F.js → chunk-FQWFB54C.js} +14 -2
- package/dist/chunk-NNCVHQC4.js +94 -0
- package/dist/{chunk-VCXPNQRB.js → chunk-TCNJY7QA.js} +1 -1
- package/dist/{chunk-NOVWLAVP.js → chunk-TMADMHBN.js} +209 -20
- package/dist/{chunk-J6CW2RGO.js → chunk-X7ILLZZP.js} +359 -2
- package/dist/{chunk-QEJ7TAZJ.js → chunk-YSDFYHPC.js} +2 -2
- package/dist/express-assistant.d.ts +4 -2
- package/dist/express-assistant.js +3 -3
- package/dist/express.d.ts +5 -2
- package/dist/express.js +3 -3
- package/dist/history/index.d.ts +7 -3
- package/dist/index.d.ts +12 -6
- package/dist/index.js +28 -10
- package/dist/interactive/index.d.ts +43 -2
- package/dist/interactive/index.js +9 -3
- package/dist/{options-C7OYeNR-.d.ts → options-BcDReOJv.d.ts} +48 -0
- package/dist/{options-Uf-qmQKN.d.ts → options-C7-VXmhD.d.ts} +62 -2
- package/dist/shared/index.d.ts +20 -6
- package/dist/shared/index.js +1 -1
- package/dist/socket.d.ts +7 -3
- package/dist/socket.js +5 -5
- package/dist/{types-BqRzb_Cd.d.ts → types-CRWzJB5G.d.ts} +35 -0
- package/dist/types-CiwGU6zC.d.ts +56 -0
- package/dist/views/index.d.ts +8 -0
- package/dist/views/index.js +10 -0
- package/docs/README.md +7 -0
- package/docs/concepts/final-response-artifacts.md +39 -0
- package/docs/concepts/interactive-requests.md +43 -0
- package/docs/concepts/tool-task-rendering.md +46 -0
- package/docs/concepts/view-workflows.md +52 -0
- package/docs/reference/exports.md +18 -16
- package/package.json +14 -4
|
@@ -7,10 +7,11 @@ import {
|
|
|
7
7
|
UnsupportedSlackInteractiveRequestError,
|
|
8
8
|
bridgeAgentEventsToSlack,
|
|
9
9
|
resolveSlackEventBridgeOptions
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TMADMHBN.js";
|
|
11
11
|
|
|
12
12
|
// src/assistant/bridge.ts
|
|
13
13
|
import { Assistant } from "@slack/bolt";
|
|
14
|
+
import { resolveSlackTurnCancelActionId as resolveSlackTurnCancelActionId2 } from "@cuylabs/channel-slack/turn-controls";
|
|
14
15
|
|
|
15
16
|
// src/assistant/options.ts
|
|
16
17
|
var DEFAULT_INITIAL_REPLY = "Hi, how can I help?";
|
|
@@ -239,6 +240,11 @@ function createAssistantSink(params) {
|
|
|
239
240
|
return stream;
|
|
240
241
|
}
|
|
241
242
|
return {
|
|
243
|
+
artifactClient: params.client,
|
|
244
|
+
artifactTarget: {
|
|
245
|
+
channelId: params.channel,
|
|
246
|
+
threadTs: params.threadTs
|
|
247
|
+
},
|
|
242
248
|
async postMessage() {
|
|
243
249
|
throw new Error(
|
|
244
250
|
"Assistant bridge uses chat-stream mode and does not support postMessage."
|
|
@@ -289,7 +295,7 @@ function createAssistantInteractiveResponder(params) {
|
|
|
289
295
|
|
|
290
296
|
// src/assistant/lifecycle/user-message.ts
|
|
291
297
|
function createUserMessageHandler(deps) {
|
|
292
|
-
const { options, feedbackBlock, sessionStrategy } = deps;
|
|
298
|
+
const { options, feedbackBlock, sessionStrategy, turnControlController } = deps;
|
|
293
299
|
const taskDisplayMode = options.taskDisplayMode ?? "timeline";
|
|
294
300
|
const messageFormatter = resolveSlackMessageFormatter(
|
|
295
301
|
options.formatChatMarkdown ?? {}
|
|
@@ -427,6 +433,7 @@ function createUserMessageHandler(deps) {
|
|
|
427
433
|
...options.formatToolTitle ? { formatToolTitle: options.formatToolTitle } : {},
|
|
428
434
|
...options.formatToolUpdate ? { formatToolUpdate: options.formatToolUpdate } : {},
|
|
429
435
|
...options.formatToolDetails ? { formatToolDetails: options.formatToolDetails } : {},
|
|
436
|
+
...options.formatToolResultOutput ? { formatToolResultOutput: options.formatToolResultOutput } : {},
|
|
430
437
|
...options.formatToolError ? { formatToolError: options.formatToolError } : {},
|
|
431
438
|
...options.formatReasoningUpdate ? { formatReasoningUpdate: options.formatReasoningUpdate } : {},
|
|
432
439
|
...options.chatStreamBufferSize !== void 0 ? { chatStreamBufferSize: options.chatStreamBufferSize } : {},
|
|
@@ -434,6 +441,22 @@ function createUserMessageHandler(deps) {
|
|
|
434
441
|
...options.maxTaskUpdateTextChars !== void 0 ? { maxTaskUpdateTextChars: options.maxTaskUpdateTextChars } : {},
|
|
435
442
|
...options.maxTaskUpdateFieldChars !== void 0 ? { maxTaskUpdateFieldChars: options.maxTaskUpdateFieldChars } : {},
|
|
436
443
|
...chatStreamFinalArgs ? { chatStreamFinalArgs } : {},
|
|
444
|
+
...options.publishFinalResponseArtifact ? {
|
|
445
|
+
publishFinalResponseArtifact: options.publishFinalResponseArtifact
|
|
446
|
+
} : {},
|
|
447
|
+
...options.finalResponseArtifactMode ? { finalResponseArtifactMode: options.finalResponseArtifactMode } : {},
|
|
448
|
+
...options.finalResponseArtifactStreamThreshold !== void 0 ? {
|
|
449
|
+
finalResponseArtifactStreamThreshold: options.finalResponseArtifactStreamThreshold
|
|
450
|
+
} : {},
|
|
451
|
+
...options.formatFinalResponseArtifactContinuationNotice ? {
|
|
452
|
+
formatFinalResponseArtifactContinuationNotice: options.formatFinalResponseArtifactContinuationNotice
|
|
453
|
+
} : {},
|
|
454
|
+
...options.formatFinalResponseArtifactMessage ? {
|
|
455
|
+
formatFinalResponseArtifactMessage: options.formatFinalResponseArtifactMessage
|
|
456
|
+
} : {},
|
|
457
|
+
...options.onFinalResponseArtifactError ? {
|
|
458
|
+
onFinalResponseArtifactError: options.onFinalResponseArtifactError
|
|
459
|
+
} : {},
|
|
437
460
|
formatMessageText: messageFormatter,
|
|
438
461
|
onStatusChange: async (label, event) => {
|
|
439
462
|
const update = await resolveStatusUpdate(
|
|
@@ -475,6 +498,17 @@ function createUserMessageHandler(deps) {
|
|
|
475
498
|
});
|
|
476
499
|
const abortController = new AbortController();
|
|
477
500
|
timeoutId = timeoutMs > 0 ? setTimeout(() => abortController.abort(), timeoutMs) : void 0;
|
|
501
|
+
const cancelControl = turnControlController?.createCancelControl({
|
|
502
|
+
abortController,
|
|
503
|
+
channel,
|
|
504
|
+
client,
|
|
505
|
+
logger: diagnosticsLogger,
|
|
506
|
+
sessionId,
|
|
507
|
+
...teamId ? { teamId } : {},
|
|
508
|
+
threadTs,
|
|
509
|
+
userId: userId ?? "unknown"
|
|
510
|
+
});
|
|
511
|
+
cancelControl?.show("turn-start");
|
|
478
512
|
await runWithSlackTurnContext(
|
|
479
513
|
{
|
|
480
514
|
slackActivity: parsed,
|
|
@@ -511,6 +545,7 @@ function createUserMessageHandler(deps) {
|
|
|
511
545
|
const translated = (async function* () {
|
|
512
546
|
try {
|
|
513
547
|
for await (const event of events) {
|
|
548
|
+
cancelControl?.sync(event);
|
|
514
549
|
if (event.type === "error") {
|
|
515
550
|
translatedError = toError(
|
|
516
551
|
event.error
|
|
@@ -539,6 +574,8 @@ ${formatStreamError(translatedError)}`
|
|
|
539
574
|
${formatStreamError(translatedError)}`
|
|
540
575
|
};
|
|
541
576
|
yield { type: "complete" };
|
|
577
|
+
} finally {
|
|
578
|
+
cancelControl?.clear("turn-finished");
|
|
542
579
|
}
|
|
543
580
|
})();
|
|
544
581
|
const finalText = await bridgeAgentEventsToSlack(
|
|
@@ -595,6 +632,213 @@ ${formatStreamError(translatedError)}`
|
|
|
595
632
|
};
|
|
596
633
|
}
|
|
597
634
|
|
|
635
|
+
// src/assistant/turn-controls.ts
|
|
636
|
+
import { randomUUID } from "crypto";
|
|
637
|
+
import {
|
|
638
|
+
createSlackTurnCancelMessage,
|
|
639
|
+
registerSlackTurnCancelAction,
|
|
640
|
+
resolveSlackTurnCancelActionId
|
|
641
|
+
} from "@cuylabs/channel-slack/turn-controls";
|
|
642
|
+
function createSlackAssistantTurnControlController(options) {
|
|
643
|
+
const resolvedCancelOptions = resolveCancelOptions(options);
|
|
644
|
+
if (!resolvedCancelOptions) {
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
const cancelOptions = resolvedCancelOptions;
|
|
648
|
+
const actionId = resolveSlackTurnCancelActionId(cancelOptions.actionId);
|
|
649
|
+
const activeControls = /* @__PURE__ */ new Map();
|
|
650
|
+
function install(app) {
|
|
651
|
+
registerSlackTurnCancelAction(app, {
|
|
652
|
+
actionId,
|
|
653
|
+
onCancel: (context) => handleCancelAction(context)
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function createCancelControl(controlOptions) {
|
|
657
|
+
const control = {
|
|
658
|
+
abortController: controlOptions.abortController,
|
|
659
|
+
channel: controlOptions.channel,
|
|
660
|
+
client: controlOptions.client,
|
|
661
|
+
controlId: randomUUID(),
|
|
662
|
+
logger: controlOptions.logger,
|
|
663
|
+
pending: Promise.resolve(),
|
|
664
|
+
sessionId: controlOptions.sessionId,
|
|
665
|
+
...controlOptions.teamId ? { teamId: controlOptions.teamId } : {},
|
|
666
|
+
threadTs: controlOptions.threadTs,
|
|
667
|
+
userId: controlOptions.userId,
|
|
668
|
+
visible: false
|
|
669
|
+
};
|
|
670
|
+
activeControls.set(control.controlId, control);
|
|
671
|
+
return {
|
|
672
|
+
show(reason) {
|
|
673
|
+
showCancelControl(control, reason);
|
|
674
|
+
},
|
|
675
|
+
clear(reason) {
|
|
676
|
+
clearCancelControl(control, reason);
|
|
677
|
+
},
|
|
678
|
+
sync(event) {
|
|
679
|
+
syncCancelControlForEvent(control, event);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
async function handleCancelAction(context) {
|
|
684
|
+
const control = activeControls.get(context.controlId);
|
|
685
|
+
if (!control) {
|
|
686
|
+
await context.deleteMessage().catch(() => void 0);
|
|
687
|
+
await acknowledge(context, cancelOptions.alreadyCompletedAck);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (context.userId !== control.userId) {
|
|
691
|
+
await acknowledge(context, cancelOptions.unauthorizedAck);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
clearCancelControl(control, "button-cancel");
|
|
695
|
+
activeControls.delete(control.controlId);
|
|
696
|
+
if (!control.abortController.signal.aborted) {
|
|
697
|
+
control.abortController.abort(createSlackTurnCancelledError());
|
|
698
|
+
}
|
|
699
|
+
await cancelOptions.onCancel?.({
|
|
700
|
+
controlId: control.controlId,
|
|
701
|
+
sessionId: control.sessionId,
|
|
702
|
+
channelId: control.channel,
|
|
703
|
+
threadTs: control.threadTs,
|
|
704
|
+
userId: control.userId,
|
|
705
|
+
...control.teamId ? { teamId: control.teamId } : {}
|
|
706
|
+
});
|
|
707
|
+
await acknowledge(context, cancelOptions.canceledAck);
|
|
708
|
+
}
|
|
709
|
+
function showCancelControl(control, reason) {
|
|
710
|
+
if (control.visible || control.abortController.signal.aborted) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
control.visible = true;
|
|
714
|
+
enqueueControlUpdate(control, "show", reason, async () => {
|
|
715
|
+
const message = createSlackTurnCancelMessage({
|
|
716
|
+
actionId,
|
|
717
|
+
controlId: control.controlId,
|
|
718
|
+
messageText: cancelOptions.messageText,
|
|
719
|
+
buttonText: cancelOptions.buttonText,
|
|
720
|
+
sessionId: control.sessionId
|
|
721
|
+
});
|
|
722
|
+
if (control.target) {
|
|
723
|
+
await control.client.chat.update({
|
|
724
|
+
channel: control.target.channel,
|
|
725
|
+
ts: control.target.ts,
|
|
726
|
+
text: message.text,
|
|
727
|
+
blocks: message.blocks
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const response = await control.client.chat.postMessage({
|
|
732
|
+
channel: control.channel,
|
|
733
|
+
thread_ts: control.threadTs,
|
|
734
|
+
text: message.text,
|
|
735
|
+
blocks: message.blocks
|
|
736
|
+
});
|
|
737
|
+
if (response.channel && response.ts) {
|
|
738
|
+
control.target = { channel: response.channel, ts: response.ts };
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
function clearCancelControl(control, reason) {
|
|
743
|
+
const terminal = isTerminalClearReason(reason);
|
|
744
|
+
if (terminal) {
|
|
745
|
+
activeControls.delete(control.controlId);
|
|
746
|
+
}
|
|
747
|
+
if (!control.visible) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
control.visible = false;
|
|
751
|
+
enqueueControlUpdate(control, "clear", reason, async () => {
|
|
752
|
+
if (!control.target) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const target = control.target;
|
|
756
|
+
control.target = void 0;
|
|
757
|
+
await control.client.chat.delete({
|
|
758
|
+
channel: target.channel,
|
|
759
|
+
ts: target.ts
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
function syncCancelControlForEvent(control, event) {
|
|
764
|
+
switch (event.type) {
|
|
765
|
+
case "text-start":
|
|
766
|
+
case "text-delta":
|
|
767
|
+
case "text-end":
|
|
768
|
+
if (cancelOptions.visibleWhen === "before-output") {
|
|
769
|
+
clearCancelControl(control, "streaming-started");
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
case "reasoning-start":
|
|
773
|
+
case "reasoning-delta":
|
|
774
|
+
case "reasoning-end":
|
|
775
|
+
case "tool-start":
|
|
776
|
+
case "approval-request":
|
|
777
|
+
case "human-input-request":
|
|
778
|
+
showCancelControl(control, event.type);
|
|
779
|
+
break;
|
|
780
|
+
case "status":
|
|
781
|
+
if (isWaitingForAgentStatus(event.status)) {
|
|
782
|
+
showCancelControl(control, `status:${event.status}`);
|
|
783
|
+
} else if (event.status === "idle" || event.status === "error") {
|
|
784
|
+
clearCancelControl(control, `status:${event.status}`);
|
|
785
|
+
}
|
|
786
|
+
break;
|
|
787
|
+
case "complete":
|
|
788
|
+
clearCancelControl(control, "turn-finished");
|
|
789
|
+
break;
|
|
790
|
+
case "error":
|
|
791
|
+
clearCancelControl(control, "turn-error");
|
|
792
|
+
break;
|
|
793
|
+
default:
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function enqueueControlUpdate(control, action, reason, update) {
|
|
798
|
+
control.pending = control.pending.then(update);
|
|
799
|
+
control.pending = control.pending.catch((error) => {
|
|
800
|
+
control.logger.warn?.("Slack turn cancel control update failed", {
|
|
801
|
+
action,
|
|
802
|
+
controlId: control.controlId,
|
|
803
|
+
error: formatErrorForLog(error),
|
|
804
|
+
reason,
|
|
805
|
+
sessionId: control.sessionId
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
async function acknowledge(context, text) {
|
|
810
|
+
if (text === false) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
await context.acknowledgeEphemeral(text ?? "Canceled the running request.");
|
|
814
|
+
}
|
|
815
|
+
return { install, createCancelControl };
|
|
816
|
+
}
|
|
817
|
+
function resolveCancelOptions(options) {
|
|
818
|
+
if (!options?.cancel) {
|
|
819
|
+
return void 0;
|
|
820
|
+
}
|
|
821
|
+
const input = options.cancel === true ? {} : options.cancel;
|
|
822
|
+
return {
|
|
823
|
+
...input,
|
|
824
|
+
visibleWhen: input.visibleWhen ?? "before-output",
|
|
825
|
+
canceledAck: input.canceledAck ?? "Canceled the running request.",
|
|
826
|
+
alreadyCompletedAck: input.alreadyCompletedAck ?? "That request is no longer running.",
|
|
827
|
+
unauthorizedAck: input.unauthorizedAck ?? "Only the user who started this request can cancel it."
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function isWaitingForAgentStatus(status) {
|
|
831
|
+
return status === "processing" || status === "thinking" || status === "reasoning" || status === "calling-tool" || status === "waiting-approval" || status === "waiting-input";
|
|
832
|
+
}
|
|
833
|
+
function isTerminalClearReason(reason) {
|
|
834
|
+
return reason === "streaming-started" || reason === "turn-finished" || reason === "turn-error" || reason === "button-cancel";
|
|
835
|
+
}
|
|
836
|
+
function createSlackTurnCancelledError() {
|
|
837
|
+
return Object.assign(new Error("Slack turn was cancelled by the user."), {
|
|
838
|
+
category: "cancelled"
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
598
842
|
// src/assistant/bridge.ts
|
|
599
843
|
import { createSlackAssistantThreadContextStore } from "@cuylabs/channel-slack/assistant";
|
|
600
844
|
function createSlackAssistantBridge(options) {
|
|
@@ -612,13 +856,20 @@ function createSlackAssistantBridge(options) {
|
|
|
612
856
|
const feedbackConfig = options.feedback === false ? void 0 : options.feedback ?? {};
|
|
613
857
|
const feedbackBlock = feedbackConfig ? createSlackFeedbackBlock(feedbackConfig) : void 0;
|
|
614
858
|
const feedbackActionId = feedbackConfig?.actionId ?? SLACK_FEEDBACK_ACTION_ID;
|
|
859
|
+
const turnControlController = createSlackAssistantTurnControlController(
|
|
860
|
+
options.turnControls
|
|
861
|
+
);
|
|
862
|
+
const turnCancelActionId = turnControlController ? resolveSlackTurnCancelActionId2(
|
|
863
|
+
resolveTurnCancelActionIdOption(options.turnControls)
|
|
864
|
+
) : void 0;
|
|
615
865
|
const threadStarted = createThreadStartedHandler(options);
|
|
616
866
|
const threadContextChanged = createThreadContextChangedHandler();
|
|
617
867
|
const threadContextStore = options.threadContextStore ?? createSlackAssistantThreadContextStore();
|
|
618
868
|
const userMessage = createUserMessageHandler({
|
|
619
869
|
options,
|
|
620
870
|
feedbackBlock,
|
|
621
|
-
sessionStrategy
|
|
871
|
+
sessionStrategy,
|
|
872
|
+
...turnControlController ? { turnControlController } : {}
|
|
622
873
|
});
|
|
623
874
|
const config = {
|
|
624
875
|
threadStarted,
|
|
@@ -640,13 +891,21 @@ function createSlackAssistantBridge(options) {
|
|
|
640
891
|
})
|
|
641
892
|
});
|
|
642
893
|
}
|
|
894
|
+
turnControlController?.install(app);
|
|
643
895
|
}
|
|
644
896
|
return {
|
|
645
897
|
assistant,
|
|
646
898
|
install,
|
|
647
|
-
...feedbackConfig ? { feedbackActionId } : {}
|
|
899
|
+
...feedbackConfig ? { feedbackActionId } : {},
|
|
900
|
+
...turnCancelActionId ? { turnCancelActionId } : {}
|
|
648
901
|
};
|
|
649
902
|
}
|
|
903
|
+
function resolveTurnCancelActionIdOption(options) {
|
|
904
|
+
if (!options || !options.cancel) {
|
|
905
|
+
return void 0;
|
|
906
|
+
}
|
|
907
|
+
return typeof options.cancel === "object" ? options.cancel.actionId : void 0;
|
|
908
|
+
}
|
|
650
909
|
|
|
651
910
|
// src/assistant/index.ts
|
|
652
911
|
import {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/views/controller.ts
|
|
2
|
+
import { registerSlackViewWorkflow } from "@cuylabs/channel-slack/views";
|
|
3
|
+
|
|
4
|
+
// src/views/state.ts
|
|
5
|
+
function extractSlackAgentViewStateValues(viewOrState) {
|
|
6
|
+
const state = isRecord(viewOrState) ? isRecord(viewOrState.state) ? viewOrState.state : viewOrState : {};
|
|
7
|
+
const rawValues = isRecord(state.values) ? state.values : {};
|
|
8
|
+
const values = {};
|
|
9
|
+
for (const [blockId, rawActions] of Object.entries(rawValues)) {
|
|
10
|
+
if (!isRecord(rawActions)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const actions = {};
|
|
14
|
+
for (const [actionId, rawAction] of Object.entries(rawActions)) {
|
|
15
|
+
actions[actionId] = extractSlackAgentViewActionValue(rawAction);
|
|
16
|
+
}
|
|
17
|
+
values[blockId] = actions;
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
}
|
|
21
|
+
function readSlackAgentViewStateValue(valuesOrView, blockId, actionId) {
|
|
22
|
+
const values = looksLikeExtractedStateValues(valuesOrView) ? valuesOrView : extractSlackAgentViewStateValues(valuesOrView);
|
|
23
|
+
return values[blockId]?.[actionId];
|
|
24
|
+
}
|
|
25
|
+
function extractSlackAgentViewActionValue(rawAction) {
|
|
26
|
+
if (!isRecord(rawAction)) {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
if ("value" in rawAction && typeof rawAction.value !== "undefined") {
|
|
30
|
+
return normalizeValue(rawAction.value);
|
|
31
|
+
}
|
|
32
|
+
if (isRecord(rawAction.selected_option)) {
|
|
33
|
+
return readOptionValue(rawAction.selected_option);
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(rawAction.selected_options)) {
|
|
36
|
+
return rawAction.selected_options.map(readOptionValue).filter(isString);
|
|
37
|
+
}
|
|
38
|
+
for (const field of [
|
|
39
|
+
"selected_user",
|
|
40
|
+
"selected_users",
|
|
41
|
+
"selected_channel",
|
|
42
|
+
"selected_channels",
|
|
43
|
+
"selected_conversation",
|
|
44
|
+
"selected_conversations",
|
|
45
|
+
"selected_date",
|
|
46
|
+
"selected_time",
|
|
47
|
+
"selected_date_time"
|
|
48
|
+
]) {
|
|
49
|
+
const value = rawAction[field];
|
|
50
|
+
if (typeof value !== "undefined") {
|
|
51
|
+
return normalizeValue(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeof rawAction.checked === "boolean") {
|
|
55
|
+
return rawAction.checked;
|
|
56
|
+
}
|
|
57
|
+
return rawAction;
|
|
58
|
+
}
|
|
59
|
+
function readOptionValue(option) {
|
|
60
|
+
if (!isRecord(option)) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
if (typeof option.value === "string") {
|
|
64
|
+
return option.value;
|
|
65
|
+
}
|
|
66
|
+
const text = option.text;
|
|
67
|
+
if (isRecord(text) && typeof text.text === "string") {
|
|
68
|
+
return text.text;
|
|
69
|
+
}
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
function normalizeValue(value) {
|
|
73
|
+
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
return value.filter(isString);
|
|
78
|
+
}
|
|
79
|
+
return isRecord(value) ? value : void 0;
|
|
80
|
+
}
|
|
81
|
+
function looksLikeExtractedStateValues(value) {
|
|
82
|
+
if (!isRecord(value) || "state" in value || "values" in value) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return Object.values(value).every(isRecord);
|
|
86
|
+
}
|
|
87
|
+
function isRecord(value) {
|
|
88
|
+
return typeof value === "object" && value !== null;
|
|
89
|
+
}
|
|
90
|
+
function isString(value) {
|
|
91
|
+
return typeof value === "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/views/controller.ts
|
|
95
|
+
var DEFAULT_NAMESPACE = "agent_slack_view";
|
|
96
|
+
var installedCallbackIds = /* @__PURE__ */ new WeakMap();
|
|
97
|
+
function createSlackAgentViewWorkflowController({
|
|
98
|
+
namespace = DEFAULT_NAMESPACE,
|
|
99
|
+
workflows = [],
|
|
100
|
+
onError
|
|
101
|
+
} = {}) {
|
|
102
|
+
const normalizedNamespace = normalizeIdentifier(namespace, "namespace");
|
|
103
|
+
const registeredWorkflows = /* @__PURE__ */ new Map();
|
|
104
|
+
const installedApps = /* @__PURE__ */ new Set();
|
|
105
|
+
function register(workflow) {
|
|
106
|
+
const workflowId = normalizeIdentifier(workflow.id, "workflow id");
|
|
107
|
+
if (!workflow.onSubmission && !workflow.onClose) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Slack agent view workflow '${workflowId}' must define onSubmission or onClose.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (registeredWorkflows.has(workflowId)) {
|
|
113
|
+
throw new Error(`Duplicate Slack agent view workflow id: ${workflowId}`);
|
|
114
|
+
}
|
|
115
|
+
const callbackId = normalizeOptionalIdentifier(workflow.callbackId) ?? callbackIdFromWorkflowId(normalizedNamespace, workflowId);
|
|
116
|
+
if ([...registeredWorkflows.values()].some(
|
|
117
|
+
(item) => item.callbackId === callbackId
|
|
118
|
+
)) {
|
|
119
|
+
throw new Error(`Duplicate Slack view callback id: ${callbackId}`);
|
|
120
|
+
}
|
|
121
|
+
const registered = {
|
|
122
|
+
...workflow,
|
|
123
|
+
id: workflowId,
|
|
124
|
+
callbackId
|
|
125
|
+
};
|
|
126
|
+
registeredWorkflows.set(workflowId, registered);
|
|
127
|
+
for (const app of installedApps) {
|
|
128
|
+
installWorkflow(app, registered);
|
|
129
|
+
}
|
|
130
|
+
return callbackId;
|
|
131
|
+
}
|
|
132
|
+
function callbackIdFor(workflowId) {
|
|
133
|
+
const normalizedWorkflowId = normalizeIdentifier(workflowId, "workflow id");
|
|
134
|
+
return registeredWorkflows.get(normalizedWorkflowId)?.callbackId ?? callbackIdFromWorkflowId(normalizedNamespace, normalizedWorkflowId);
|
|
135
|
+
}
|
|
136
|
+
function list() {
|
|
137
|
+
return [...registeredWorkflows.values()].map(({ id, callbackId }) => ({
|
|
138
|
+
id,
|
|
139
|
+
callbackId
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
function install(app) {
|
|
143
|
+
installedApps.add(app);
|
|
144
|
+
for (const workflow of registeredWorkflows.values()) {
|
|
145
|
+
installWorkflow(app, workflow);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function installWorkflow(app, workflow) {
|
|
149
|
+
const callbackIds = callbackIdsForApp(app);
|
|
150
|
+
if (callbackIds.has(workflow.callbackId)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Slack view callback id '${workflow.callbackId}' is already installed on this Bolt app.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
callbackIds.add(workflow.callbackId);
|
|
156
|
+
registerSlackViewWorkflow({
|
|
157
|
+
boltApp: app,
|
|
158
|
+
callbackId: workflow.callbackId,
|
|
159
|
+
...workflow.decodePrivateMetadata ? { decodePrivateMetadata: workflow.decodePrivateMetadata } : {},
|
|
160
|
+
...workflow.onSubmission ? {
|
|
161
|
+
onSubmission: async (context) => await workflow.onSubmission(enrichContext(workflow, context))
|
|
162
|
+
} : {},
|
|
163
|
+
...workflow.onClose ? {
|
|
164
|
+
onClose: async (context) => await workflow.onClose(enrichContext(workflow, context))
|
|
165
|
+
} : {},
|
|
166
|
+
onError: async (error, context) => {
|
|
167
|
+
const enriched = enrichContext(workflow, {
|
|
168
|
+
...context,
|
|
169
|
+
metadata: context.metadata
|
|
170
|
+
});
|
|
171
|
+
if (workflow.onError) {
|
|
172
|
+
await workflow.onError(error, enriched);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await onError?.(error, enriched);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
for (const workflow of workflows) {
|
|
180
|
+
register(workflow);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
namespace: normalizedNamespace,
|
|
184
|
+
register,
|
|
185
|
+
callbackIdFor,
|
|
186
|
+
list,
|
|
187
|
+
install
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function enrichContext(workflow, context) {
|
|
191
|
+
const stateValues = extractSlackAgentViewStateValues(context.view);
|
|
192
|
+
return {
|
|
193
|
+
...context,
|
|
194
|
+
workflow: { id: workflow.id, callbackId: workflow.callbackId },
|
|
195
|
+
stateValues,
|
|
196
|
+
getStateValue: (blockId, actionId) => readSlackAgentViewStateValue(stateValues, blockId, actionId)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function callbackIdsForApp(app) {
|
|
200
|
+
let callbackIds = installedCallbackIds.get(app);
|
|
201
|
+
if (!callbackIds) {
|
|
202
|
+
callbackIds = /* @__PURE__ */ new Set();
|
|
203
|
+
installedCallbackIds.set(app, callbackIds);
|
|
204
|
+
}
|
|
205
|
+
return callbackIds;
|
|
206
|
+
}
|
|
207
|
+
function callbackIdFromWorkflowId(namespace, workflowId) {
|
|
208
|
+
return `${namespace}_${workflowId}`;
|
|
209
|
+
}
|
|
210
|
+
function normalizeOptionalIdentifier(value) {
|
|
211
|
+
const normalized = value?.trim();
|
|
212
|
+
return normalized ? normalized : void 0;
|
|
213
|
+
}
|
|
214
|
+
function normalizeIdentifier(value, label) {
|
|
215
|
+
const normalized = value.trim().replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
216
|
+
if (!normalized) {
|
|
217
|
+
throw new Error(`Slack agent view workflow ${label} cannot be empty.`);
|
|
218
|
+
}
|
|
219
|
+
return normalized;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export {
|
|
223
|
+
extractSlackAgentViewStateValues,
|
|
224
|
+
readSlackAgentViewStateValue,
|
|
225
|
+
createSlackAgentViewWorkflowController
|
|
226
|
+
};
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
UnsupportedSlackInteractiveRequestError,
|
|
3
3
|
bridgeAgentEventsToSlack,
|
|
4
4
|
resolveSlackEventBridgeOptions
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TMADMHBN.js";
|
|
6
6
|
|
|
7
7
|
// src/adapter/adapter.ts
|
|
8
8
|
import { withinScope } from "@cuylabs/agent-core";
|
|
@@ -73,6 +73,11 @@ function buildThreadPayload(info, respondInThread) {
|
|
|
73
73
|
function buildResponseSink(say, client, info, respondInThread, chatStreamStartArgs, sayStream) {
|
|
74
74
|
const threadPayload = buildThreadPayload(info, respondInThread);
|
|
75
75
|
return {
|
|
76
|
+
artifactClient: client,
|
|
77
|
+
artifactTarget: {
|
|
78
|
+
channelId: info.channelId,
|
|
79
|
+
...threadPayload.thread_ts ? { threadTs: threadPayload.thread_ts } : {}
|
|
80
|
+
},
|
|
76
81
|
async postMessage(text) {
|
|
77
82
|
const result = await say({ text, ...threadPayload });
|
|
78
83
|
const response = result;
|
|
@@ -167,6 +172,7 @@ function createSlackChannelAdapter(options) {
|
|
|
167
172
|
formatToolTitle: options.formatToolTitle,
|
|
168
173
|
formatToolUpdate: options.formatToolUpdate,
|
|
169
174
|
formatToolDetails: options.formatToolDetails,
|
|
175
|
+
formatToolResultOutput: options.formatToolResultOutput,
|
|
170
176
|
formatToolError: options.formatToolError,
|
|
171
177
|
formatReasoningUpdate: options.formatReasoningUpdate,
|
|
172
178
|
interactiveMode: options.interactiveMode,
|
|
@@ -180,7 +186,13 @@ function createSlackChannelAdapter(options) {
|
|
|
180
186
|
maxTaskUpdates: options.maxTaskUpdates,
|
|
181
187
|
maxTaskUpdateTextChars: options.maxTaskUpdateTextChars,
|
|
182
188
|
maxTaskUpdateFieldChars: options.maxTaskUpdateFieldChars,
|
|
183
|
-
chatStreamFinalArgs: options.chatStreamFinalArgs
|
|
189
|
+
chatStreamFinalArgs: options.chatStreamFinalArgs,
|
|
190
|
+
publishFinalResponseArtifact: options.publishFinalResponseArtifact,
|
|
191
|
+
finalResponseArtifactMode: options.finalResponseArtifactMode,
|
|
192
|
+
finalResponseArtifactStreamThreshold: options.finalResponseArtifactStreamThreshold,
|
|
193
|
+
formatFinalResponseArtifactContinuationNotice: options.formatFinalResponseArtifactContinuationNotice,
|
|
194
|
+
formatFinalResponseArtifactMessage: options.formatFinalResponseArtifactMessage,
|
|
195
|
+
onFinalResponseArtifactError: options.onFinalResponseArtifactError
|
|
184
196
|
}
|
|
185
197
|
);
|
|
186
198
|
const timeout = options.timeout ?? 12e4;
|