@cuylabs/channel-slack 0.10.0 → 0.12.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 +36 -3
- package/dist/adapter/index.d.ts +53 -0
- package/dist/adapter/index.js +13 -0
- package/dist/app-surface.d.ts +86 -0
- package/dist/app-surface.js +15 -0
- package/dist/app.d.ts +58 -0
- package/dist/app.js +86 -0
- package/dist/artifacts/index.d.ts +57 -3
- package/dist/artifacts/index.js +88 -0
- package/dist/assistant/index.d.ts +18 -53
- package/dist/assistant/index.js +15 -184
- package/dist/bolt-app-BM0tiL7c.d.ts +49 -0
- package/dist/{chunk-TWJGVDA2.js → chunk-37RN2YUI.js} +88 -1
- package/dist/chunk-LFQCINHI.js +187 -0
- package/dist/chunk-Q6YX7HHK.js +1062 -0
- package/dist/chunk-RHOIVQLD.js +127 -0
- package/dist/chunk-RTDLIYEE.js +446 -0
- package/dist/core.d.ts +5 -201
- package/dist/core.js +10 -12
- package/dist/feedback/index.d.ts +2 -2
- package/dist/feedback/index.js +5 -120
- package/dist/formatting-C-kwQseI.d.ts +25 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +10 -12
- package/dist/interactive/index.d.ts +68 -4
- package/dist/interactive/index.js +432 -0
- package/dist/options-B0xQCaez.d.ts +221 -0
- package/dist/options-DQacQDmD.d.ts +368 -0
- package/dist/runtime/index.d.ts +6 -220
- package/dist/socket.d.ts +142 -0
- package/dist/socket.js +77 -0
- package/dist/transports/index.d.ts +2 -1
- package/dist/transports/socket/index.d.ts +4 -49
- package/dist/turn-BGAXddH_.d.ts +178 -0
- package/dist/types-wLZzyI9r.d.ts +375 -0
- package/docs/README.md +1 -0
- package/docs/concepts/interactive-requests.md +85 -0
- package/docs/reference/channel-slack-boundary.md +6 -3
- package/docs/reference/exports.md +6 -2
- package/docs/reference/source-layout.md +1 -0
- package/package.json +23 -3
- package/dist/chunk-ISOMBQXE.js +0 -89
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
openSlackModal
|
|
3
|
+
} from "../chunk-IRFKUPJN.js";
|
|
4
|
+
|
|
1
5
|
// src/interactive/blocks.ts
|
|
2
6
|
var MAX_BLOCK_TEXT = 2800;
|
|
3
7
|
function buildApprovalRequestMessage(request, actionIds) {
|
|
@@ -634,6 +638,433 @@ async function importPostgresPoolConstructor() {
|
|
|
634
638
|
function formatImportError(error) {
|
|
635
639
|
return error instanceof Error ? error.message : String(error);
|
|
636
640
|
}
|
|
641
|
+
|
|
642
|
+
// src/interactive/controller.ts
|
|
643
|
+
var DEFAULT_NAMESPACE = "agent_slack";
|
|
644
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
645
|
+
var installedActionIds = /* @__PURE__ */ new WeakMap();
|
|
646
|
+
function createSlackInteractiveController(options = {}) {
|
|
647
|
+
const store = options.store ?? createInMemorySlackInteractiveRequestStore();
|
|
648
|
+
const actionIds = resolveActionIds(
|
|
649
|
+
options.namespace ?? DEFAULT_NAMESPACE,
|
|
650
|
+
options.actionIds
|
|
651
|
+
);
|
|
652
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
653
|
+
const waiters = /* @__PURE__ */ new Map();
|
|
654
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
655
|
+
async function ensurePending(kind, request) {
|
|
656
|
+
const existing = await store.get(request.id);
|
|
657
|
+
if (existing) {
|
|
658
|
+
assertRecordKind(existing, kind);
|
|
659
|
+
trackPending(existing);
|
|
660
|
+
return existing;
|
|
661
|
+
}
|
|
662
|
+
const createdAt = nowIso();
|
|
663
|
+
const record = await store.upsert({
|
|
664
|
+
id: request.id,
|
|
665
|
+
kind,
|
|
666
|
+
request,
|
|
667
|
+
status: "pending",
|
|
668
|
+
createdAt,
|
|
669
|
+
updatedAt: createdAt
|
|
670
|
+
});
|
|
671
|
+
assertRecordKind(record, kind);
|
|
672
|
+
trackPending(record);
|
|
673
|
+
return record;
|
|
674
|
+
}
|
|
675
|
+
function trackPending(record) {
|
|
676
|
+
if (record.status === "pending") {
|
|
677
|
+
pendingIds.add(record.id);
|
|
678
|
+
} else {
|
|
679
|
+
pendingIds.delete(record.id);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
async function waitForResolution(kind, request, waitOptions = {}) {
|
|
683
|
+
const existing = await ensurePending(kind, request);
|
|
684
|
+
if (existing.status === "resolved") {
|
|
685
|
+
if (!existing.resolution) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
`Slack interactive request ${request.id} is resolved without a resolution.`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
return existing.resolution;
|
|
691
|
+
}
|
|
692
|
+
if (waiters.has(request.id)) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Slack interactive request is already waiting: ${request.id}. Resolve or cancel the in-flight request before requesting again.`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
return await new Promise((resolve, reject) => {
|
|
698
|
+
const cleanupCallbacks = [];
|
|
699
|
+
const timeoutMs = waitOptions.timeoutMs ?? requestTimeoutMs;
|
|
700
|
+
if (timeoutMs > 0) {
|
|
701
|
+
const timeoutId = setTimeout(() => {
|
|
702
|
+
void cancel(request.id, "Slack interactive request timed out.");
|
|
703
|
+
}, timeoutMs);
|
|
704
|
+
cleanupCallbacks.push(() => clearTimeout(timeoutId));
|
|
705
|
+
}
|
|
706
|
+
let abortImmediately = false;
|
|
707
|
+
if (waitOptions.signal) {
|
|
708
|
+
const onAbort = () => {
|
|
709
|
+
void cancel(request.id, "Slack interactive request aborted.");
|
|
710
|
+
};
|
|
711
|
+
if (waitOptions.signal.aborted) {
|
|
712
|
+
abortImmediately = true;
|
|
713
|
+
} else {
|
|
714
|
+
waitOptions.signal.addEventListener("abort", onAbort, {
|
|
715
|
+
once: true
|
|
716
|
+
});
|
|
717
|
+
cleanupCallbacks.push(
|
|
718
|
+
() => waitOptions.signal?.removeEventListener("abort", onAbort)
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
waiters.set(request.id, {
|
|
723
|
+
resolve,
|
|
724
|
+
reject,
|
|
725
|
+
cleanup: () => {
|
|
726
|
+
for (const cleanup of cleanupCallbacks.splice(0)) {
|
|
727
|
+
cleanup();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
if (abortImmediately) {
|
|
732
|
+
void cancel(request.id, "Slack interactive request aborted.");
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async function resolveRequest(requestId, resolution) {
|
|
737
|
+
const record = await store.resolve(requestId, resolution);
|
|
738
|
+
if (!record) {
|
|
739
|
+
const waiter2 = waiters.get(requestId);
|
|
740
|
+
if (waiter2) {
|
|
741
|
+
waiters.delete(requestId);
|
|
742
|
+
waiter2.cleanup();
|
|
743
|
+
waiter2.reject(
|
|
744
|
+
new Error(`Slack interactive request no longer exists: ${requestId}`)
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
pendingIds.delete(requestId);
|
|
748
|
+
return void 0;
|
|
749
|
+
}
|
|
750
|
+
const resolvedResolution = record.resolution ?? resolution;
|
|
751
|
+
const waiter = waiters.get(requestId);
|
|
752
|
+
if (waiter) {
|
|
753
|
+
waiters.delete(requestId);
|
|
754
|
+
waiter.cleanup();
|
|
755
|
+
waiter.resolve(resolvedResolution);
|
|
756
|
+
}
|
|
757
|
+
pendingIds.delete(requestId);
|
|
758
|
+
await options.onResolve?.(requestId, resolvedResolution);
|
|
759
|
+
return { record, resolution: resolvedResolution };
|
|
760
|
+
}
|
|
761
|
+
async function cancel(requestId, reason = "Cancelled") {
|
|
762
|
+
const waiter = waiters.get(requestId);
|
|
763
|
+
if (waiter) {
|
|
764
|
+
waiters.delete(requestId);
|
|
765
|
+
waiter.cleanup();
|
|
766
|
+
waiter.reject(new Error(reason));
|
|
767
|
+
}
|
|
768
|
+
const existing = await store.get(requestId);
|
|
769
|
+
if (existing?.status === "pending") {
|
|
770
|
+
await store.delete(requestId);
|
|
771
|
+
pendingIds.delete(requestId);
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
pendingIds.delete(requestId);
|
|
775
|
+
return Boolean(waiter);
|
|
776
|
+
}
|
|
777
|
+
async function cancelAll(reason = "Cancelled") {
|
|
778
|
+
await Promise.all(
|
|
779
|
+
[...pendingIds].map((requestId) => cancel(requestId, reason))
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
async function handleInteractiveRequest(context) {
|
|
783
|
+
const request = context.request;
|
|
784
|
+
const record = await ensurePending(context.kind, request);
|
|
785
|
+
if (record.status === "resolved" || record.target) {
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
const message = context.kind === "approval" ? buildApprovalRequestMessage(
|
|
789
|
+
request,
|
|
790
|
+
actionIds
|
|
791
|
+
) : buildHumanInputRequestMessage(
|
|
792
|
+
request,
|
|
793
|
+
actionIds
|
|
794
|
+
);
|
|
795
|
+
const ref = await context.responder.postMessage(message);
|
|
796
|
+
await store.attachTarget(request.id, {
|
|
797
|
+
channel: ref.channel,
|
|
798
|
+
ts: ref.ts,
|
|
799
|
+
userId: context.user.userId,
|
|
800
|
+
teamId: context.user.teamId,
|
|
801
|
+
...context.slackActivity.threadTs ? { threadTs: context.slackActivity.threadTs } : {}
|
|
802
|
+
});
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
function install(app) {
|
|
806
|
+
assertActionIdsCanInstall(app, actionIds);
|
|
807
|
+
app.action(actionIds.approvalAllow, async (args) => {
|
|
808
|
+
await handleAction(args, {
|
|
809
|
+
kind: "approval",
|
|
810
|
+
action: "allow"
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
app.action(actionIds.approvalDeny, async (args) => {
|
|
814
|
+
await handleAction(args, {
|
|
815
|
+
kind: "approval",
|
|
816
|
+
action: "deny"
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
app.action(actionIds.approvalRemember, async (args) => {
|
|
820
|
+
const value = firstActionValue(args);
|
|
821
|
+
const rememberScope = typeof value.rememberScope === "string" ? value.rememberScope : void 0;
|
|
822
|
+
await handleAction(args, {
|
|
823
|
+
kind: "approval",
|
|
824
|
+
action: "remember",
|
|
825
|
+
...rememberScope ? { rememberScope } : {}
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
app.action(actionIds.humanConfirm, async (args) => {
|
|
829
|
+
await handleAction(args, {
|
|
830
|
+
kind: "human-input",
|
|
831
|
+
response: { kind: "confirm", confirmed: true, text: "Confirmed" }
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
app.action(actionIds.humanDeny, async (args) => {
|
|
835
|
+
await handleAction(args, {
|
|
836
|
+
kind: "human-input",
|
|
837
|
+
response: { kind: "confirm", confirmed: false, text: "Cancelled" }
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
app.action(actionIds.humanOpen, async (args) => {
|
|
841
|
+
await openHumanInputModal(args);
|
|
842
|
+
});
|
|
843
|
+
app.view(actionIds.humanSubmit, async (args) => {
|
|
844
|
+
await submitHumanInputModal(args);
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
async function handleAction(args, resolutionInput) {
|
|
848
|
+
const actionArgs = args;
|
|
849
|
+
await actionArgs.ack();
|
|
850
|
+
const requestId = extractRequestId(firstActionValue(args));
|
|
851
|
+
if (!requestId) return;
|
|
852
|
+
const record = await store.get(requestId);
|
|
853
|
+
if (!record || record.status === "resolved") {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const actor = extractActor(actionArgs.body);
|
|
857
|
+
if (!await isAuthorized(record, actor)) {
|
|
858
|
+
await postEphemeral(
|
|
859
|
+
actionArgs,
|
|
860
|
+
"Only the original requester can resolve this request."
|
|
861
|
+
);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const resolved = await resolveRequest(requestId, resolutionInput);
|
|
865
|
+
if (resolved?.record.target) {
|
|
866
|
+
await updateOriginalMessage(
|
|
867
|
+
actionArgs,
|
|
868
|
+
resolved.record,
|
|
869
|
+
resolved.resolution
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async function openHumanInputModal(args) {
|
|
874
|
+
const actionArgs = args;
|
|
875
|
+
await actionArgs.ack();
|
|
876
|
+
const requestId = extractRequestId(firstActionValue(args));
|
|
877
|
+
if (!requestId || !actionArgs.body.trigger_id) return;
|
|
878
|
+
const record = await store.get(requestId);
|
|
879
|
+
if (!record || record.kind !== "human-input") return;
|
|
880
|
+
const actor = extractActor(actionArgs.body);
|
|
881
|
+
if (!await isAuthorized(record, actor)) {
|
|
882
|
+
await postEphemeral(
|
|
883
|
+
actionArgs,
|
|
884
|
+
"Only the original requester can answer this request."
|
|
885
|
+
);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
await openSlackModal({
|
|
889
|
+
client: actionArgs.client,
|
|
890
|
+
triggerId: actionArgs.body.trigger_id,
|
|
891
|
+
view: buildHumanInputModal(
|
|
892
|
+
record.request,
|
|
893
|
+
actionIds
|
|
894
|
+
)
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
async function submitHumanInputModal(args) {
|
|
898
|
+
const viewArgs = args;
|
|
899
|
+
await viewArgs.ack();
|
|
900
|
+
const requestId = extractRequestIdFromView(viewArgs.view);
|
|
901
|
+
if (!requestId) return;
|
|
902
|
+
const record = await store.get(requestId);
|
|
903
|
+
if (!record || record.kind !== "human-input" || record.status === "resolved") {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const actor = extractActor(viewArgs.body);
|
|
907
|
+
if (!await isAuthorized(record, actor)) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const response = responseFromView(
|
|
911
|
+
record.request,
|
|
912
|
+
viewArgs.view
|
|
913
|
+
);
|
|
914
|
+
const resolution = {
|
|
915
|
+
kind: "human-input",
|
|
916
|
+
response
|
|
917
|
+
};
|
|
918
|
+
const resolved = await resolveRequest(requestId, resolution);
|
|
919
|
+
if (resolved?.record.target) {
|
|
920
|
+
await viewArgs.client.chat.update({
|
|
921
|
+
channel: resolved.record.target.channel,
|
|
922
|
+
ts: resolved.record.target.ts,
|
|
923
|
+
...buildResolvedMessage(
|
|
924
|
+
"Slack response received.",
|
|
925
|
+
resolved.resolution
|
|
926
|
+
)
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async function isAuthorized(record, actor) {
|
|
931
|
+
if (options.authorize) {
|
|
932
|
+
return await options.authorize(record, actor);
|
|
933
|
+
}
|
|
934
|
+
return record.target?.userId === actor.userId;
|
|
935
|
+
}
|
|
936
|
+
async function updateOriginalMessage(args, record, resolution) {
|
|
937
|
+
const target = record.target;
|
|
938
|
+
if (!target) return;
|
|
939
|
+
const label = resolution.kind === "approval" ? `${resolution.action} selected.` : resolution.response.text;
|
|
940
|
+
await args.client.chat.update({
|
|
941
|
+
channel: target.channel,
|
|
942
|
+
ts: target.ts,
|
|
943
|
+
...buildResolvedMessage(label, resolution)
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
actionIds,
|
|
948
|
+
store,
|
|
949
|
+
approval: {
|
|
950
|
+
async onRequest(request, options2) {
|
|
951
|
+
const resolution = await waitForResolution(
|
|
952
|
+
"approval",
|
|
953
|
+
request,
|
|
954
|
+
options2
|
|
955
|
+
);
|
|
956
|
+
if (resolution.kind !== "approval") {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`Unexpected human-input resolution for ${request.id}.`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
return resolution;
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
humanInput: {
|
|
965
|
+
async onRequest(request, options2) {
|
|
966
|
+
const resolution = await waitForResolution(
|
|
967
|
+
"human-input",
|
|
968
|
+
request,
|
|
969
|
+
options2
|
|
970
|
+
);
|
|
971
|
+
if (resolution.kind !== "human-input") {
|
|
972
|
+
throw new Error(`Unexpected approval resolution for ${request.id}.`);
|
|
973
|
+
}
|
|
974
|
+
return resolution.response;
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
cancel,
|
|
978
|
+
cancelAll,
|
|
979
|
+
handleInteractiveRequest,
|
|
980
|
+
install
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function assertRecordKind(record, kind) {
|
|
984
|
+
if (record.kind !== kind) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
`Slack interactive request ${record.id} already exists as ${record.kind}; cannot reuse it as ${kind}.`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
function resolveActionIds(namespace, overrides) {
|
|
991
|
+
const prefix = normalizeActionIdNamespace(namespace);
|
|
992
|
+
return {
|
|
993
|
+
approvalAllow: `${prefix}_approval_allow`,
|
|
994
|
+
approvalDeny: `${prefix}_approval_deny`,
|
|
995
|
+
approvalRemember: `${prefix}_approval_remember`,
|
|
996
|
+
humanConfirm: `${prefix}_human_confirm`,
|
|
997
|
+
humanDeny: `${prefix}_human_deny`,
|
|
998
|
+
humanOpen: `${prefix}_human_open`,
|
|
999
|
+
humanSubmit: `${prefix}_human_submit`,
|
|
1000
|
+
...overrides ?? {}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function normalizeActionIdNamespace(namespace) {
|
|
1004
|
+
const trimmed = namespace.trim();
|
|
1005
|
+
if (!trimmed) {
|
|
1006
|
+
throw new Error("Slack interactive action namespace cannot be empty.");
|
|
1007
|
+
}
|
|
1008
|
+
return trimmed;
|
|
1009
|
+
}
|
|
1010
|
+
function assertActionIdsCanInstall(app, actionIds) {
|
|
1011
|
+
const ids = Object.values(actionIds);
|
|
1012
|
+
const duplicateWithinController = ids.find(
|
|
1013
|
+
(id, index) => ids.indexOf(id) !== index
|
|
1014
|
+
);
|
|
1015
|
+
if (duplicateWithinController) {
|
|
1016
|
+
throw new Error(
|
|
1017
|
+
`Duplicate Slack interactive action id configured: ${duplicateWithinController}`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
const appKey = app;
|
|
1021
|
+
const installed = installedActionIds.get(appKey) ?? /* @__PURE__ */ new Set();
|
|
1022
|
+
const duplicate = ids.find((id) => installed.has(id));
|
|
1023
|
+
if (duplicate) {
|
|
1024
|
+
throw new Error(
|
|
1025
|
+
`Slack interactive action id '${duplicate}' is already installed on this Bolt app. Provide a unique createSlackInteractiveController({ namespace }) or actionIds config.`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
for (const id of ids) {
|
|
1029
|
+
installed.add(id);
|
|
1030
|
+
}
|
|
1031
|
+
installedActionIds.set(appKey, installed);
|
|
1032
|
+
}
|
|
1033
|
+
function firstActionValue(args) {
|
|
1034
|
+
const body = args.body;
|
|
1035
|
+
return decodeActionValue(body.actions?.[0]?.value);
|
|
1036
|
+
}
|
|
1037
|
+
function extractRequestId(value) {
|
|
1038
|
+
return typeof value.requestId === "string" && value.requestId.length > 0 ? value.requestId : void 0;
|
|
1039
|
+
}
|
|
1040
|
+
function extractRequestIdFromView(view) {
|
|
1041
|
+
return extractRequestId(decodeActionValue(view.private_metadata));
|
|
1042
|
+
}
|
|
1043
|
+
function extractActor(body) {
|
|
1044
|
+
return {
|
|
1045
|
+
userId: body.user?.id ?? "unknown",
|
|
1046
|
+
...body.user?.team_id ?? body.team?.id ? { teamId: body.user?.team_id ?? body.team?.id } : {}
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
async function postEphemeral(args, text) {
|
|
1050
|
+
const channel = args.body.channel?.id;
|
|
1051
|
+
const user = args.body.user?.id;
|
|
1052
|
+
if (!channel || !user || !args.client.chat.postEphemeral) return;
|
|
1053
|
+
await args.client.chat.postEphemeral({ channel, user, text });
|
|
1054
|
+
}
|
|
1055
|
+
function responseFromView(request, view) {
|
|
1056
|
+
const input = view.state?.values?.input?.value;
|
|
1057
|
+
if (request.kind === "choice") {
|
|
1058
|
+
const selected = input?.selected_options?.map((option) => option.value ?? "").filter(Boolean) ?? (input?.selected_option?.value ? [input.selected_option.value] : []);
|
|
1059
|
+
return {
|
|
1060
|
+
kind: "choice",
|
|
1061
|
+
selected,
|
|
1062
|
+
text: selected.join(", ")
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const text = input?.value ?? "";
|
|
1066
|
+
return { kind: "text", text };
|
|
1067
|
+
}
|
|
637
1068
|
export {
|
|
638
1069
|
buildApprovalRequestMessage,
|
|
639
1070
|
buildHumanInputModal,
|
|
@@ -642,6 +1073,7 @@ export {
|
|
|
642
1073
|
cloneRecord,
|
|
643
1074
|
createInMemorySlackInteractiveRequestStore,
|
|
644
1075
|
createPostgresSlackInteractiveRequestStore,
|
|
1076
|
+
createSlackInteractiveController,
|
|
645
1077
|
decodeActionValue,
|
|
646
1078
|
encodeActionValue,
|
|
647
1079
|
initializePostgresSlackInteractiveRequestStore,
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { d as SlackArtifactClient, g as SlackArtifactPublication } from './types-C8nkPuD4.js';
|
|
2
|
+
import { c as SlackInteractiveApprovalRequest, e as SlackInteractiveHumanInputRequest } from './types-Cywfj8Mj.js';
|
|
3
|
+
import { a as SlackEventInteractiveRequestHandler } from './interactive-CbKYkkc_.js';
|
|
4
|
+
|
|
5
|
+
interface SlackTurnSourceChatOptions {
|
|
6
|
+
abort?: AbortSignal;
|
|
7
|
+
system?: string;
|
|
8
|
+
}
|
|
9
|
+
interface SlackTurnSource {
|
|
10
|
+
chat(sessionId: string, message: string, options?: SlackTurnSourceChatOptions): AsyncGenerator<SlackTurnEvent>;
|
|
11
|
+
}
|
|
12
|
+
interface SlackFinalResponseArtifactContext {
|
|
13
|
+
text: string;
|
|
14
|
+
formattedText: string;
|
|
15
|
+
client: SlackArtifactClient;
|
|
16
|
+
channelId: string;
|
|
17
|
+
threadTs?: string;
|
|
18
|
+
}
|
|
19
|
+
interface SlackFinalResponseArtifactResult {
|
|
20
|
+
publication: SlackArtifactPublication;
|
|
21
|
+
}
|
|
22
|
+
type SlackFinalResponseArtifactDeliveryMode = "supplemental" | "replace";
|
|
23
|
+
type SlackFinalResponseArtifactPublisher = (context: SlackFinalResponseArtifactContext) => Promise<SlackFinalResponseArtifactResult | undefined>;
|
|
24
|
+
type SlackTurnStatus = "thinking" | "reasoning" | "calling-tool" | "waiting-approval" | "waiting-input" | "processing" | "error" | string;
|
|
25
|
+
interface SlackTurnEventBase {
|
|
26
|
+
type: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
interface SlackTurnTextStartEvent extends SlackTurnEventBase {
|
|
30
|
+
type: "text-start";
|
|
31
|
+
}
|
|
32
|
+
interface SlackTurnTextDeltaEvent extends SlackTurnEventBase {
|
|
33
|
+
type: "text-delta";
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
interface SlackTurnTextEndEvent extends SlackTurnEventBase {
|
|
37
|
+
type: "text-end";
|
|
38
|
+
}
|
|
39
|
+
interface SlackTurnReasoningStartEvent extends SlackTurnEventBase {
|
|
40
|
+
type: "reasoning-start";
|
|
41
|
+
}
|
|
42
|
+
interface SlackTurnReasoningEndEvent extends SlackTurnEventBase {
|
|
43
|
+
type: "reasoning-end";
|
|
44
|
+
}
|
|
45
|
+
interface SlackTurnToolStartEvent extends SlackTurnEventBase {
|
|
46
|
+
type: "tool-start";
|
|
47
|
+
toolCallId: string;
|
|
48
|
+
toolName: string;
|
|
49
|
+
input?: unknown;
|
|
50
|
+
}
|
|
51
|
+
interface SlackTurnToolResultEvent extends SlackTurnEventBase {
|
|
52
|
+
type: "tool-result";
|
|
53
|
+
toolCallId: string;
|
|
54
|
+
toolName: string;
|
|
55
|
+
result?: unknown;
|
|
56
|
+
}
|
|
57
|
+
interface SlackTurnToolErrorEvent extends SlackTurnEventBase {
|
|
58
|
+
type: "tool-error";
|
|
59
|
+
toolCallId: string;
|
|
60
|
+
toolName: string;
|
|
61
|
+
error: string;
|
|
62
|
+
}
|
|
63
|
+
interface SlackTurnSubagentEventBase extends SlackTurnEventBase {
|
|
64
|
+
dispatchId: string;
|
|
65
|
+
role: string;
|
|
66
|
+
title?: string;
|
|
67
|
+
parentDispatchId?: string;
|
|
68
|
+
agentPath?: string;
|
|
69
|
+
depth?: number;
|
|
70
|
+
}
|
|
71
|
+
interface SlackTurnSubagentStartEvent extends SlackTurnSubagentEventBase {
|
|
72
|
+
type: "subagent-start";
|
|
73
|
+
}
|
|
74
|
+
interface SlackTurnSubagentEventEvent extends SlackTurnSubagentEventBase {
|
|
75
|
+
type: "subagent-event";
|
|
76
|
+
event: SlackTurnEvent;
|
|
77
|
+
}
|
|
78
|
+
interface SlackTurnSubagentCompleteEvent extends SlackTurnSubagentEventBase {
|
|
79
|
+
type: "subagent-complete";
|
|
80
|
+
output?: unknown;
|
|
81
|
+
}
|
|
82
|
+
interface SlackTurnSubagentErrorEvent extends SlackTurnSubagentEventBase {
|
|
83
|
+
type: "subagent-error";
|
|
84
|
+
error: string;
|
|
85
|
+
}
|
|
86
|
+
interface SlackTurnStatusEvent extends SlackTurnEventBase {
|
|
87
|
+
type: "status";
|
|
88
|
+
status: SlackTurnStatus;
|
|
89
|
+
}
|
|
90
|
+
interface SlackTurnCompleteEvent extends SlackTurnEventBase {
|
|
91
|
+
type: "complete";
|
|
92
|
+
output?: string;
|
|
93
|
+
}
|
|
94
|
+
interface SlackTurnErrorEvent extends SlackTurnEventBase {
|
|
95
|
+
type: "error";
|
|
96
|
+
error: unknown;
|
|
97
|
+
}
|
|
98
|
+
interface SlackTurnApprovalRequestEvent extends SlackTurnEventBase {
|
|
99
|
+
type: "approval-request";
|
|
100
|
+
request: SlackInteractiveApprovalRequest;
|
|
101
|
+
}
|
|
102
|
+
interface SlackTurnApprovalResolvedEvent extends SlackTurnEventBase {
|
|
103
|
+
type: "approval-resolved";
|
|
104
|
+
}
|
|
105
|
+
interface SlackTurnHumanInputRequestEvent extends SlackTurnEventBase {
|
|
106
|
+
type: "human-input-request";
|
|
107
|
+
request: SlackInteractiveHumanInputRequest;
|
|
108
|
+
}
|
|
109
|
+
interface SlackTurnHumanInputResolvedEvent extends SlackTurnEventBase {
|
|
110
|
+
type: "human-input-resolved";
|
|
111
|
+
}
|
|
112
|
+
type SlackTurnEvent = SlackTurnTextStartEvent | SlackTurnTextDeltaEvent | SlackTurnTextEndEvent | SlackTurnReasoningStartEvent | SlackTurnReasoningEndEvent | SlackTurnToolStartEvent | SlackTurnToolResultEvent | SlackTurnToolErrorEvent | SlackTurnSubagentStartEvent | SlackTurnSubagentEventEvent | SlackTurnSubagentCompleteEvent | SlackTurnSubagentErrorEvent | SlackTurnStatusEvent | SlackTurnCompleteEvent | SlackTurnErrorEvent | SlackTurnApprovalRequestEvent | SlackTurnApprovalResolvedEvent | SlackTurnHumanInputRequestEvent | SlackTurnHumanInputResolvedEvent;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Event-bridge configuration. The bridge is mode-aware (progressive,
|
|
116
|
+
* accumulate, chat-stream) and these options control formatting, status
|
|
117
|
+
* propagation, interactive-request handling, and chat-stream finalization.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
type ApprovalRequest = SlackTurnApprovalRequestEvent["request"];
|
|
121
|
+
type HumanInputRequest = SlackTurnHumanInputRequestEvent["request"];
|
|
122
|
+
interface SlackEventBridgeOptions {
|
|
123
|
+
showReasoning: boolean;
|
|
124
|
+
/** Render root-agent tool rows/status updates. */
|
|
125
|
+
showToolUsage: boolean;
|
|
126
|
+
/** Render subagent child tool rows in the parent Slack task timeline. */
|
|
127
|
+
showSubagentToolUsage: boolean;
|
|
128
|
+
/** Include full subagent completion output in the subagent task row. */
|
|
129
|
+
showSubagentResultInTask: boolean;
|
|
130
|
+
formatToolTitle?: (toolName: string) => string;
|
|
131
|
+
formatToolUpdate: (toolName: string) => string;
|
|
132
|
+
formatToolDetails?: (event: SlackTurnToolStartEvent) => string | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* Format the completed tool output shown in Slack task rows.
|
|
135
|
+
*
|
|
136
|
+
* Return `undefined` to use the default generic formatter. Return `null` to
|
|
137
|
+
* intentionally omit task-row output for this tool result.
|
|
138
|
+
*/
|
|
139
|
+
formatToolResultOutput?: (event: SlackTurnToolResultEvent) => string | null | undefined;
|
|
140
|
+
formatToolError: (toolName: string, error: string) => string;
|
|
141
|
+
formatReasoningUpdate: () => string;
|
|
142
|
+
formatMessageText: (text: string) => string;
|
|
143
|
+
streamingMode: "progressive" | "accumulate" | "chat-stream";
|
|
144
|
+
progressiveUpdateThreshold: number;
|
|
145
|
+
progressiveUpdateIntervalMs: number;
|
|
146
|
+
chatStreamBufferSize: number;
|
|
147
|
+
/**
|
|
148
|
+
* Maximum number of Slack task-update chunks the bridge will append to a
|
|
149
|
+
* single chat stream. Task rows are a bounded UI projection, not a durable
|
|
150
|
+
* event log.
|
|
151
|
+
*/
|
|
152
|
+
maxTaskUpdates: number;
|
|
153
|
+
/**
|
|
154
|
+
* Maximum cumulative characters from task titles/details/output that the
|
|
155
|
+
* bridge will append to one chat stream.
|
|
156
|
+
*/
|
|
157
|
+
maxTaskUpdateTextChars: number;
|
|
158
|
+
/**
|
|
159
|
+
* Maximum characters for a single task details/output field.
|
|
160
|
+
*/
|
|
161
|
+
maxTaskUpdateFieldChars: number;
|
|
162
|
+
interactiveMode: "message-and-error" | "ignore";
|
|
163
|
+
handleInteractiveRequest?: SlackEventInteractiveRequestHandler;
|
|
164
|
+
formatApprovalRequired: (request: ApprovalRequest) => string;
|
|
165
|
+
formatHumanInputRequired: (request: HumanInputRequest) => string;
|
|
166
|
+
/**
|
|
167
|
+
* Optional hook called whenever the bridge's status label changes — used by
|
|
168
|
+
* the assistant bridge to update the assistant pane's status line via
|
|
169
|
+
* `assistant.threads.setStatus`. Errors are swallowed by the caller.
|
|
170
|
+
*/
|
|
171
|
+
onStatusChange?: (label: string, event: SlackTurnEvent | undefined) => void | Promise<void>;
|
|
172
|
+
/**
|
|
173
|
+
* Optional final-args passthrough for `chatStream.stop(...)`. When the
|
|
174
|
+
* bridge stops the chat stream on `complete`, these args (e.g. final
|
|
175
|
+
* `blocks` for a feedback widget) are merged into the `stop` call. The
|
|
176
|
+
* existing `markdown_text` final-text fallback always wins on text fields.
|
|
177
|
+
*/
|
|
178
|
+
chatStreamFinalArgs?: {
|
|
179
|
+
blocks?: unknown[];
|
|
180
|
+
} & Record<string, unknown>;
|
|
181
|
+
/**
|
|
182
|
+
* Optional post-processing hook for publishing a rich artifact from the final
|
|
183
|
+
* accumulated response, such as a Slack Canvas for long answers.
|
|
184
|
+
*/
|
|
185
|
+
publishFinalResponseArtifact?: SlackFinalResponseArtifactPublisher;
|
|
186
|
+
/**
|
|
187
|
+
* Controls whether artifact publication is additive or becomes the primary
|
|
188
|
+
* final-answer surface.
|
|
189
|
+
*
|
|
190
|
+
* - `supplemental`: finalize the Slack text normally, then publish artifact.
|
|
191
|
+
* - `replace`: publish artifact first and use a compact Slack final message.
|
|
192
|
+
*
|
|
193
|
+
* @default "supplemental"
|
|
194
|
+
*/
|
|
195
|
+
finalResponseArtifactMode?: SlackFinalResponseArtifactDeliveryMode;
|
|
196
|
+
/**
|
|
197
|
+
* When replacement mode is active, continue streaming text up to this many
|
|
198
|
+
* raw characters, then suppress further text deltas and publish the full
|
|
199
|
+
* answer as an artifact at completion. This preserves normal short-answer
|
|
200
|
+
* streaming without flooding Slack for long answers.
|
|
201
|
+
*
|
|
202
|
+
* @default 4000
|
|
203
|
+
*/
|
|
204
|
+
finalResponseArtifactStreamThreshold?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Notice appended when replacement mode suppresses the remaining text stream.
|
|
207
|
+
*/
|
|
208
|
+
formatFinalResponseArtifactContinuationNotice?: (context: Pick<SlackFinalResponseArtifactContext, "text" | "formattedText">) => string;
|
|
209
|
+
/**
|
|
210
|
+
* Final compact message used when an artifact replaces the full Slack text.
|
|
211
|
+
*/
|
|
212
|
+
formatFinalResponseArtifactMessage?: (result: SlackFinalResponseArtifactResult, context: SlackFinalResponseArtifactContext) => string;
|
|
213
|
+
/**
|
|
214
|
+
* Called when `publishFinalResponseArtifact` throws. Errors from this hook
|
|
215
|
+
* are swallowed so artifact publication cannot break a completed turn.
|
|
216
|
+
*/
|
|
217
|
+
onFinalResponseArtifactError?: (error: unknown, context: SlackFinalResponseArtifactContext) => void | Promise<void>;
|
|
218
|
+
}
|
|
219
|
+
declare function resolveSlackEventBridgeOptions(partial: Partial<SlackEventBridgeOptions>): SlackEventBridgeOptions;
|
|
220
|
+
|
|
221
|
+
export { type SlackTurnToolErrorEvent as A, type SlackTurnToolResultEvent as B, type SlackTurnToolStartEvent as C, resolveSlackEventBridgeOptions as D, type SlackEventBridgeOptions as S, type SlackFinalResponseArtifactContext as a, type SlackFinalResponseArtifactDeliveryMode as b, type SlackFinalResponseArtifactPublisher as c, type SlackFinalResponseArtifactResult as d, type SlackTurnApprovalRequestEvent as e, type SlackTurnApprovalResolvedEvent as f, type SlackTurnCompleteEvent as g, type SlackTurnErrorEvent as h, type SlackTurnEvent as i, type SlackTurnEventBase as j, type SlackTurnHumanInputRequestEvent as k, type SlackTurnHumanInputResolvedEvent as l, type SlackTurnReasoningEndEvent as m, type SlackTurnReasoningStartEvent as n, type SlackTurnSource as o, type SlackTurnSourceChatOptions as p, type SlackTurnStatus as q, type SlackTurnStatusEvent as r, type SlackTurnSubagentCompleteEvent as s, type SlackTurnSubagentErrorEvent as t, type SlackTurnSubagentEventBase as u, type SlackTurnSubagentEventEvent as v, type SlackTurnSubagentStartEvent as w, type SlackTurnTextDeltaEvent as x, type SlackTurnTextEndEvent as y, type SlackTurnTextStartEvent as z };
|