@cuylabs/channel-slack 0.5.0 → 0.6.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 +25 -136
- package/dist/app-home.d.ts +23 -0
- package/dist/app-home.js +35 -0
- package/dist/artifacts/index.d.ts +135 -0
- package/dist/artifacts/index.js +299 -0
- package/dist/{assistant.d.ts → assistant/index.d.ts} +1 -1
- package/dist/{assistant.js → assistant/index.js} +2 -2
- package/dist/auth/index.d.ts +56 -0
- package/dist/auth/index.js +168 -0
- package/dist/{chunk-IDVDMJ5U.js → chunk-6JSGIVQH.js} +110 -3
- package/dist/chunk-6WHFQUYQ.js +54 -0
- package/dist/{bolt.js → chunk-73QXT7MA.js} +29 -322
- package/dist/{chunk-CMR6B76C.js → chunk-DNVSH7H5.js} +407 -1
- package/dist/chunk-QJYCHWN6.js +76 -0
- package/dist/chunk-S3SWPYXJ.js +81 -0
- package/dist/{chunk-JZG4IETE.js → chunk-X4WBBBYM.js} +0 -52
- package/dist/core.js +5 -3
- package/dist/diagnostics/index.d.ts +71 -0
- package/dist/{diagnostics.js → diagnostics/index.js} +5 -1
- package/dist/entrypoints/index.d.ts +120 -0
- package/dist/entrypoints/index.js +132 -0
- package/dist/{history.d.ts → history/index.d.ts} +2 -2
- package/dist/{history.js → history/index.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -15
- package/dist/{policy.d.ts → policy/index.d.ts} +103 -2
- package/dist/{policy.js → policy/index.js} +13 -1
- package/dist/runtime-BNBHOZSQ.d.ts +53 -0
- package/dist/{setup.d.ts → setup/index.d.ts} +30 -3
- package/dist/{setup.js → setup/index.js} +134 -3
- package/dist/transports/http/index.d.ts +68 -0
- package/dist/transports/http/index.js +8 -0
- package/dist/transports/index.d.ts +8 -0
- package/dist/transports/index.js +24 -0
- package/dist/transports/socket/index.d.ts +94 -0
- package/dist/transports/socket/index.js +19 -0
- package/dist/types-B9NfCVrk.d.ts +141 -0
- package/docs/README.md +31 -0
- package/docs/concepts/activity.md +3 -3
- package/docs/concepts/artifacts.md +56 -0
- package/docs/concepts/entrypoints.md +73 -0
- package/docs/concepts/message-policy.md +13 -0
- package/docs/concepts/setup-requirements.md +23 -0
- package/docs/concepts/{bolt-runtime.md → transport-runtime.md} +14 -4
- package/docs/recipes/app-mention-handler.md +5 -0
- package/docs/recipes/generate-slack-manifest.md +16 -0
- package/docs/recipes/publish-artifact.md +45 -0
- package/docs/recipes/slash-command-and-shortcut.md +51 -0
- package/docs/recipes/socket-mode-app.md +1 -1
- package/docs/reference/channel-slack-boundary.md +10 -6
- package/docs/reference/exports.md +7 -2
- package/docs/reference/source-layout.md +35 -0
- package/package.json +63 -39
- package/dist/bolt.d.ts +0 -364
- package/dist/chunk-NE57BLLU.js +0 -0
- package/dist/diagnostics.d.ts +0 -22
- package/dist/shared.d.ts +0 -2
- package/dist/shared.js +0 -43
- /package/dist/{feedback.d.ts → feedback/index.d.ts} +0 -0
- /package/dist/{feedback.js → feedback/index.js} +0 -0
- /package/dist/{targets.d.ts → targets/index.d.ts} +0 -0
- /package/dist/{targets.js → targets/index.js} +0 -0
- /package/dist/{users.d.ts → users/index.d.ts} +0 -0
- /package/dist/{users.js → users/index.js} +0 -0
|
@@ -651,6 +651,406 @@ function createSlackMessagePolicyResolver(config = {}) {
|
|
|
651
651
|
};
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
// src/policy/thread-participation.ts
|
|
655
|
+
var DEFAULT_MAX_THREADS = 1e4;
|
|
656
|
+
var DEFAULT_POSTGRES_TABLE = "channel_slack_thread_participation";
|
|
657
|
+
var DEFAULT_POSTGRES_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
658
|
+
var DEFAULT_POSTGRES_PRUNE_BATCH_SIZE = 1e3;
|
|
659
|
+
var DEFAULT_POSTGRES_PRUNE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
660
|
+
function resolveSlackThreadParticipationEligibility({
|
|
661
|
+
activity,
|
|
662
|
+
policy,
|
|
663
|
+
stateStore
|
|
664
|
+
}) {
|
|
665
|
+
if (!stateStore) {
|
|
666
|
+
return { available: false, reason: "state-store-unavailable" };
|
|
667
|
+
}
|
|
668
|
+
if (activity.channelType === "dm") {
|
|
669
|
+
return { available: false, reason: "direct-message" };
|
|
670
|
+
}
|
|
671
|
+
if (activity.channelType !== "channel" && activity.channelType !== "thread") {
|
|
672
|
+
return { available: false, reason: "unsupported-channel-type" };
|
|
673
|
+
}
|
|
674
|
+
if (policy.messagePolicy !== "mentioned-threads") {
|
|
675
|
+
return {
|
|
676
|
+
available: false,
|
|
677
|
+
reason: "message-policy-not-mentioned-threads"
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
if (policy.threadReplyPolicy === "mention-required") {
|
|
681
|
+
return {
|
|
682
|
+
available: false,
|
|
683
|
+
reason: "thread-replies-require-mention"
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
const threadTs = activity.threadTs ?? activity.messageTs;
|
|
687
|
+
if (!threadTs) {
|
|
688
|
+
return { available: false, reason: "missing-thread-timestamp" };
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
available: true,
|
|
692
|
+
threadKey: createSlackMessagePolicyThreadKey(activity),
|
|
693
|
+
threadTs
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function createInMemorySlackThreadParticipationStateStore({
|
|
697
|
+
maxThreads = DEFAULT_MAX_THREADS,
|
|
698
|
+
now = () => /* @__PURE__ */ new Date()
|
|
699
|
+
} = {}) {
|
|
700
|
+
const states = /* @__PURE__ */ new Map();
|
|
701
|
+
const order = [];
|
|
702
|
+
const resolvedMaxThreads = Math.max(1, maxThreads);
|
|
703
|
+
function rememberKey(key) {
|
|
704
|
+
if (!states.has(key)) {
|
|
705
|
+
order.push(key);
|
|
706
|
+
}
|
|
707
|
+
while (order.length > resolvedMaxThreads) {
|
|
708
|
+
const oldest = order.shift();
|
|
709
|
+
if (oldest !== void 0) {
|
|
710
|
+
states.delete(oldest);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
async get(key) {
|
|
716
|
+
return states.get(key);
|
|
717
|
+
},
|
|
718
|
+
async setQuiet(key, state) {
|
|
719
|
+
rememberKey(key);
|
|
720
|
+
states.set(key, {
|
|
721
|
+
...state,
|
|
722
|
+
mode: "quiet",
|
|
723
|
+
updatedAt: now().toISOString()
|
|
724
|
+
});
|
|
725
|
+
},
|
|
726
|
+
async clear(key) {
|
|
727
|
+
states.delete(key);
|
|
728
|
+
const index = order.indexOf(key);
|
|
729
|
+
if (index >= 0) {
|
|
730
|
+
order.splice(index, 1);
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
async prune() {
|
|
734
|
+
return { quietThreadsDeleted: 0 };
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function createPostgresSlackThreadParticipationStateStore({
|
|
739
|
+
client,
|
|
740
|
+
connectionString,
|
|
741
|
+
ensureSchema = true,
|
|
742
|
+
onPruneError,
|
|
743
|
+
pruneBatchSize = DEFAULT_POSTGRES_PRUNE_BATCH_SIZE,
|
|
744
|
+
pruneIntervalMs = DEFAULT_POSTGRES_PRUNE_INTERVAL_MS,
|
|
745
|
+
retentionMs = DEFAULT_POSTGRES_RETENTION_MS,
|
|
746
|
+
schema,
|
|
747
|
+
tableName = DEFAULT_POSTGRES_TABLE
|
|
748
|
+
}) {
|
|
749
|
+
let activeClient = client;
|
|
750
|
+
let ownsClient = false;
|
|
751
|
+
let initialized;
|
|
752
|
+
let pruneTimer;
|
|
753
|
+
async function getClient() {
|
|
754
|
+
if (activeClient) {
|
|
755
|
+
return activeClient;
|
|
756
|
+
}
|
|
757
|
+
if (!connectionString) {
|
|
758
|
+
throw new Error(
|
|
759
|
+
"connectionString is required when a Postgres Slack thread participation client is not provided"
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
const Pool = await importPostgresPoolConstructor2();
|
|
763
|
+
activeClient = new Pool({ connectionString });
|
|
764
|
+
ownsClient = true;
|
|
765
|
+
return activeClient;
|
|
766
|
+
}
|
|
767
|
+
async function ensureInitialized() {
|
|
768
|
+
const currentClient = await getClient();
|
|
769
|
+
initialized ??= initializePostgresSlackThreadParticipationState({
|
|
770
|
+
client: currentClient,
|
|
771
|
+
ensureSchema,
|
|
772
|
+
schema,
|
|
773
|
+
tableName
|
|
774
|
+
});
|
|
775
|
+
await initialized;
|
|
776
|
+
startPruneTimer();
|
|
777
|
+
return currentClient;
|
|
778
|
+
}
|
|
779
|
+
function startPruneTimer() {
|
|
780
|
+
if (pruneTimer || pruneIntervalMs <= 0) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
pruneTimer = setInterval(() => {
|
|
784
|
+
void prune().catch((error) => {
|
|
785
|
+
onPruneError?.(error);
|
|
786
|
+
});
|
|
787
|
+
}, pruneIntervalMs);
|
|
788
|
+
pruneTimer.unref?.();
|
|
789
|
+
}
|
|
790
|
+
async function prune() {
|
|
791
|
+
const currentClient = await ensureInitialized();
|
|
792
|
+
return prunePostgresSlackThreadParticipationState({
|
|
793
|
+
client: currentClient,
|
|
794
|
+
pruneBatchSize,
|
|
795
|
+
retentionMs,
|
|
796
|
+
schema,
|
|
797
|
+
tableName
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
async get(key) {
|
|
802
|
+
const currentClient = await ensureInitialized();
|
|
803
|
+
const result = await currentClient.query(
|
|
804
|
+
`SELECT
|
|
805
|
+
mode,
|
|
806
|
+
reason,
|
|
807
|
+
source_message_ts,
|
|
808
|
+
updated_at,
|
|
809
|
+
updated_by_user_id
|
|
810
|
+
FROM ${qualifiedTableName2({ schema, tableName })}
|
|
811
|
+
WHERE key = $1::text`,
|
|
812
|
+
[key]
|
|
813
|
+
);
|
|
814
|
+
const row = result.rows[0];
|
|
815
|
+
if (!row || row.mode !== "quiet") {
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
mode: "quiet",
|
|
820
|
+
...row.reason ? { reason: row.reason } : {},
|
|
821
|
+
...row.source_message_ts ? { sourceMessageTs: row.source_message_ts } : {},
|
|
822
|
+
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : String(row.updated_at),
|
|
823
|
+
...row.updated_by_user_id ? { updatedByUserId: row.updated_by_user_id } : {}
|
|
824
|
+
};
|
|
825
|
+
},
|
|
826
|
+
async setQuiet(key, state, context) {
|
|
827
|
+
const currentClient = await ensureInitialized();
|
|
828
|
+
await currentClient.query(
|
|
829
|
+
`INSERT INTO ${qualifiedTableName2({ schema, tableName })} (
|
|
830
|
+
key,
|
|
831
|
+
team_id,
|
|
832
|
+
channel_id,
|
|
833
|
+
thread_ts,
|
|
834
|
+
mode,
|
|
835
|
+
reason,
|
|
836
|
+
updated_by_user_id,
|
|
837
|
+
source_message_ts,
|
|
838
|
+
updated_at
|
|
839
|
+
)
|
|
840
|
+
VALUES ($1::text, $2::text, $3::text, $4::text, 'quiet', $5::text, $6::text, $7::text, now())
|
|
841
|
+
ON CONFLICT (key) DO UPDATE SET
|
|
842
|
+
team_id = EXCLUDED.team_id,
|
|
843
|
+
channel_id = EXCLUDED.channel_id,
|
|
844
|
+
thread_ts = EXCLUDED.thread_ts,
|
|
845
|
+
mode = 'quiet',
|
|
846
|
+
reason = EXCLUDED.reason,
|
|
847
|
+
updated_by_user_id = EXCLUDED.updated_by_user_id,
|
|
848
|
+
source_message_ts = EXCLUDED.source_message_ts,
|
|
849
|
+
updated_at = now()`,
|
|
850
|
+
[
|
|
851
|
+
key,
|
|
852
|
+
context.activity.teamId ?? null,
|
|
853
|
+
context.activity.channelId,
|
|
854
|
+
context.activity.threadTs ?? context.activity.messageTs ?? null,
|
|
855
|
+
state.reason ?? null,
|
|
856
|
+
state.updatedByUserId ?? null,
|
|
857
|
+
state.sourceMessageTs ?? context.activity.messageTs ?? null
|
|
858
|
+
]
|
|
859
|
+
);
|
|
860
|
+
},
|
|
861
|
+
async clear(key) {
|
|
862
|
+
const currentClient = await ensureInitialized();
|
|
863
|
+
await currentClient.query(
|
|
864
|
+
`DELETE FROM ${qualifiedTableName2({ schema, tableName })}
|
|
865
|
+
WHERE key = $1::text`,
|
|
866
|
+
[key]
|
|
867
|
+
);
|
|
868
|
+
},
|
|
869
|
+
prune,
|
|
870
|
+
async close() {
|
|
871
|
+
if (pruneTimer) {
|
|
872
|
+
clearInterval(pruneTimer);
|
|
873
|
+
pruneTimer = void 0;
|
|
874
|
+
}
|
|
875
|
+
if (ownsClient) {
|
|
876
|
+
await activeClient?.end?.();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
async function initializePostgresSlackThreadParticipationState({
|
|
882
|
+
client,
|
|
883
|
+
ensureSchema = true,
|
|
884
|
+
schema,
|
|
885
|
+
tableName = DEFAULT_POSTGRES_TABLE
|
|
886
|
+
}) {
|
|
887
|
+
if (schema && ensureSchema) {
|
|
888
|
+
await client.query(
|
|
889
|
+
`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier2(schema)}`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const table = qualifiedTableName2({ schema, tableName });
|
|
893
|
+
await client.query(`
|
|
894
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
895
|
+
key text PRIMARY KEY,
|
|
896
|
+
team_id text,
|
|
897
|
+
channel_id text NOT NULL,
|
|
898
|
+
thread_ts text,
|
|
899
|
+
mode text NOT NULL CHECK (mode IN ('quiet')),
|
|
900
|
+
reason text,
|
|
901
|
+
updated_by_user_id text,
|
|
902
|
+
source_message_ts text,
|
|
903
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
904
|
+
)
|
|
905
|
+
`);
|
|
906
|
+
const indexPrefix = slackThreadParticipationIndexPrefix({
|
|
907
|
+
schema,
|
|
908
|
+
tableName
|
|
909
|
+
});
|
|
910
|
+
await client.query(
|
|
911
|
+
`CREATE INDEX IF NOT EXISTS ${quoteIdentifier2(
|
|
912
|
+
`${indexPrefix}_updated_idx`
|
|
913
|
+
)} ON ${table} (updated_at DESC)`
|
|
914
|
+
);
|
|
915
|
+
await client.query(
|
|
916
|
+
`CREATE INDEX IF NOT EXISTS ${quoteIdentifier2(
|
|
917
|
+
`${indexPrefix}_channel_thread_idx`
|
|
918
|
+
)} ON ${table} (channel_id, thread_ts)`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
async function prunePostgresSlackThreadParticipationState({
|
|
922
|
+
client,
|
|
923
|
+
pruneBatchSize = DEFAULT_POSTGRES_PRUNE_BATCH_SIZE,
|
|
924
|
+
retentionMs = DEFAULT_POSTGRES_RETENTION_MS,
|
|
925
|
+
schema,
|
|
926
|
+
tableName = DEFAULT_POSTGRES_TABLE
|
|
927
|
+
}) {
|
|
928
|
+
if (retentionMs <= 0) {
|
|
929
|
+
return { quietThreadsDeleted: 0 };
|
|
930
|
+
}
|
|
931
|
+
const batchSize = Math.max(1, Math.floor(pruneBatchSize));
|
|
932
|
+
const table = qualifiedTableName2({ schema, tableName });
|
|
933
|
+
const result = await client.query(
|
|
934
|
+
`WITH expired AS (
|
|
935
|
+
SELECT key
|
|
936
|
+
FROM ${table}
|
|
937
|
+
WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')
|
|
938
|
+
ORDER BY updated_at ASC
|
|
939
|
+
LIMIT $2::integer
|
|
940
|
+
)
|
|
941
|
+
DELETE FROM ${table} target
|
|
942
|
+
USING expired
|
|
943
|
+
WHERE target.key = expired.key`,
|
|
944
|
+
[Math.max(1, Math.floor(retentionMs)), batchSize]
|
|
945
|
+
);
|
|
946
|
+
return { quietThreadsDeleted: result.rowCount ?? 0 };
|
|
947
|
+
}
|
|
948
|
+
function qualifiedTableName2({
|
|
949
|
+
schema,
|
|
950
|
+
tableName
|
|
951
|
+
}) {
|
|
952
|
+
return schema ? `${quoteIdentifier2(schema)}.${quoteIdentifier2(tableName)}` : quoteIdentifier2(tableName);
|
|
953
|
+
}
|
|
954
|
+
function quoteIdentifier2(value) {
|
|
955
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
956
|
+
}
|
|
957
|
+
function slackThreadParticipationIndexPrefix({
|
|
958
|
+
schema,
|
|
959
|
+
tableName
|
|
960
|
+
}) {
|
|
961
|
+
const raw = [schema, tableName].filter(Boolean).join("_");
|
|
962
|
+
return raw.replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || "channel_slack_thread_participation";
|
|
963
|
+
}
|
|
964
|
+
async function importPostgresPoolConstructor2() {
|
|
965
|
+
const dynamicImport = new Function(
|
|
966
|
+
"specifier",
|
|
967
|
+
"return import(specifier)"
|
|
968
|
+
);
|
|
969
|
+
try {
|
|
970
|
+
const pg = await dynamicImport("pg");
|
|
971
|
+
return pg.Pool;
|
|
972
|
+
} catch (error) {
|
|
973
|
+
throw new Error(
|
|
974
|
+
`The "pg" package is required when using connectionString with createPostgresSlackThreadParticipationStateStore. Install pg or pass a client. ${formatImportError2(
|
|
975
|
+
error
|
|
976
|
+
)}`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function formatImportError2(error) {
|
|
981
|
+
return error instanceof Error ? error.message : String(error);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/policy/thread-message-policy.ts
|
|
985
|
+
function createAsyncSlackThreadAwareMessagePolicyResolver(config = {}) {
|
|
986
|
+
const { threadParticipationStore, ...messagePolicyConfig } = config;
|
|
987
|
+
const resolver = createAsyncSlackMessagePolicyResolver(messagePolicyConfig);
|
|
988
|
+
async function resolve(activity) {
|
|
989
|
+
const threadParticipation = await resolveThreadParticipationState({
|
|
990
|
+
activity,
|
|
991
|
+
config,
|
|
992
|
+
stateStore: threadParticipationStore
|
|
993
|
+
});
|
|
994
|
+
if (threadParticipation?.quiet) {
|
|
995
|
+
if (!activity.isMention) {
|
|
996
|
+
return {
|
|
997
|
+
accepted: false,
|
|
998
|
+
activity,
|
|
999
|
+
reason: "quiet-thread",
|
|
1000
|
+
threadKey: threadParticipation.key,
|
|
1001
|
+
...threadParticipation.reason ? { threadParticipationReason: threadParticipation.reason } : {},
|
|
1002
|
+
...threadParticipation.threadTs ? { threadTs: threadParticipation.threadTs } : {}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
await threadParticipationStore?.clear(threadParticipation.key, {
|
|
1006
|
+
activity,
|
|
1007
|
+
key: threadParticipation.key
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const decision = await resolver.resolve(activity);
|
|
1011
|
+
if (!threadParticipation?.quiet || !activity.isMention) {
|
|
1012
|
+
return decision;
|
|
1013
|
+
}
|
|
1014
|
+
return {
|
|
1015
|
+
...decision,
|
|
1016
|
+
reactivatedThreadParticipation: {
|
|
1017
|
+
threadKey: threadParticipation.key,
|
|
1018
|
+
...threadParticipation.reason ? { reason: threadParticipation.reason } : {},
|
|
1019
|
+
...threadParticipation.threadTs ? { threadTs: threadParticipation.threadTs } : {}
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
resolve,
|
|
1025
|
+
async resolveMessage(activity) {
|
|
1026
|
+
const decision = await resolve(activity);
|
|
1027
|
+
return decision.accepted ? decision.text : void 0;
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
async function resolveThreadParticipationState({
|
|
1032
|
+
activity,
|
|
1033
|
+
config,
|
|
1034
|
+
stateStore
|
|
1035
|
+
}) {
|
|
1036
|
+
const eligibility = resolveSlackThreadParticipationEligibility({
|
|
1037
|
+
activity,
|
|
1038
|
+
policy: config,
|
|
1039
|
+
stateStore
|
|
1040
|
+
});
|
|
1041
|
+
if (!eligibility.available || !eligibility.threadKey || !stateStore) {
|
|
1042
|
+
return void 0;
|
|
1043
|
+
}
|
|
1044
|
+
const key = eligibility.threadKey;
|
|
1045
|
+
const state = await stateStore.get(key, { activity, key });
|
|
1046
|
+
return state?.mode === "quiet" ? {
|
|
1047
|
+
key,
|
|
1048
|
+
quiet: true,
|
|
1049
|
+
...state.reason ? { reason: state.reason } : {},
|
|
1050
|
+
...eligibility.threadTs ? { threadTs: eligibility.threadTs } : {}
|
|
1051
|
+
} : void 0;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
654
1054
|
export {
|
|
655
1055
|
createSlackMessagePolicyMessageKey,
|
|
656
1056
|
createSlackMessagePolicyThreadKey,
|
|
@@ -660,5 +1060,11 @@ export {
|
|
|
660
1060
|
initializePostgresSlackMessagePolicyState,
|
|
661
1061
|
prunePostgresSlackMessagePolicyState,
|
|
662
1062
|
shouldRegisterSlackPassiveChannelMessages,
|
|
663
|
-
createSlackMessagePolicyResolver
|
|
1063
|
+
createSlackMessagePolicyResolver,
|
|
1064
|
+
resolveSlackThreadParticipationEligibility,
|
|
1065
|
+
createInMemorySlackThreadParticipationStateStore,
|
|
1066
|
+
createPostgresSlackThreadParticipationStateStore,
|
|
1067
|
+
initializePostgresSlackThreadParticipationState,
|
|
1068
|
+
prunePostgresSlackThreadParticipationState,
|
|
1069
|
+
createAsyncSlackThreadAwareMessagePolicyResolver
|
|
664
1070
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeSlackEventsPath,
|
|
3
|
+
resolveDirectAuth,
|
|
4
|
+
trimToUndefined
|
|
5
|
+
} from "./chunk-S3SWPYXJ.js";
|
|
6
|
+
|
|
7
|
+
// src/transports/http/bolt-app.ts
|
|
8
|
+
async function createSlackBoltApp(options = {}) {
|
|
9
|
+
const boltModule = await import("@slack/bolt");
|
|
10
|
+
const routePath = normalizeSlackEventsPath(options.path ?? "/slack/events");
|
|
11
|
+
const signingSecret = trimToUndefined(
|
|
12
|
+
options.signingSecret ?? process.env.SLACK_SIGNING_SECRET
|
|
13
|
+
);
|
|
14
|
+
if (!signingSecret) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Slack signing secret is required. Pass `signingSecret` or set SLACK_SIGNING_SECRET."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const auth = resolveDirectAuth(options);
|
|
20
|
+
const receiver = new boltModule.ExpressReceiver({
|
|
21
|
+
...options.receiverOptions,
|
|
22
|
+
signingSecret,
|
|
23
|
+
endpoints: routePath,
|
|
24
|
+
processBeforeResponse: options.processBeforeResponse,
|
|
25
|
+
signatureVerification: options.signatureVerification,
|
|
26
|
+
// Bolt is typed against Express 4 while this repo uses Express 5 types.
|
|
27
|
+
// The runtime contract is compatible; keep the cast localized here.
|
|
28
|
+
app: options.app,
|
|
29
|
+
...auth.mode === "oauth" ? {
|
|
30
|
+
clientId: auth.clientId,
|
|
31
|
+
clientSecret: auth.clientSecret,
|
|
32
|
+
stateSecret: auth.stateSecret,
|
|
33
|
+
installationStore: auth.installationStore,
|
|
34
|
+
redirectUri: auth.redirectUri,
|
|
35
|
+
scopes: auth.scopes,
|
|
36
|
+
installerOptions: {
|
|
37
|
+
stateStore: auth.stateStore,
|
|
38
|
+
stateVerification: auth.stateVerification,
|
|
39
|
+
legacyStateVerification: auth.legacyStateVerification,
|
|
40
|
+
stateCookieName: auth.stateCookieName,
|
|
41
|
+
stateCookieExpirationSeconds: auth.stateCookieExpirationSeconds,
|
|
42
|
+
metadata: auth.metadata,
|
|
43
|
+
userScopes: auth.userScopes,
|
|
44
|
+
installPath: auth.installPath,
|
|
45
|
+
redirectUriPath: auth.callbackPath,
|
|
46
|
+
renderHtmlForInstallPath: auth.renderHtmlForInstallPath,
|
|
47
|
+
installPathOptions: auth.installPathOptions,
|
|
48
|
+
callbackOptions: auth.callbackOptions,
|
|
49
|
+
directInstall: auth.directInstall,
|
|
50
|
+
authVersion: auth.authVersion,
|
|
51
|
+
authorizationUrl: auth.authorizationUrl
|
|
52
|
+
}
|
|
53
|
+
} : {}
|
|
54
|
+
});
|
|
55
|
+
const boltApp = new boltModule.App({
|
|
56
|
+
...options.boltAppOptions,
|
|
57
|
+
receiver,
|
|
58
|
+
...auth.mode === "single-workspace" ? {
|
|
59
|
+
token: auth.botToken,
|
|
60
|
+
botId: auth.botId,
|
|
61
|
+
botUserId: auth.botUserId
|
|
62
|
+
} : {},
|
|
63
|
+
...auth.mode === "authorize" ? { authorize: auth.authorize } : {}
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
boltApp,
|
|
67
|
+
receiver,
|
|
68
|
+
app: options.app ?? receiver.app,
|
|
69
|
+
authMode: auth.mode,
|
|
70
|
+
routePath
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
createSlackBoltApp
|
|
76
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/auth/resolve.ts
|
|
2
|
+
function resolveDirectAuth(options) {
|
|
3
|
+
const provided = options.auth;
|
|
4
|
+
if (!provided || provided.mode === void 0 || provided.mode === "single-workspace") {
|
|
5
|
+
const singleWorkspace = provided;
|
|
6
|
+
const botToken = firstNonEmptyString(
|
|
7
|
+
singleWorkspace?.botToken,
|
|
8
|
+
options.botToken,
|
|
9
|
+
process.env.SLACK_BOT_TOKEN
|
|
10
|
+
);
|
|
11
|
+
if (!botToken) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Slack bot token is required for single-workspace mode. Pass `botToken`, use `auth: { mode: "oauth", ... }`, or use `auth: { mode: "authorize", ... }`.'
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
mode: "single-workspace",
|
|
18
|
+
botToken,
|
|
19
|
+
botId: singleWorkspace?.botId,
|
|
20
|
+
botUserId: singleWorkspace?.botUserId
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (provided.mode === "authorize") {
|
|
24
|
+
return provided;
|
|
25
|
+
}
|
|
26
|
+
const oauth = provided;
|
|
27
|
+
const clientId = firstNonEmptyString(
|
|
28
|
+
oauth.clientId,
|
|
29
|
+
process.env.SLACK_CLIENT_ID
|
|
30
|
+
);
|
|
31
|
+
const clientSecret = firstNonEmptyString(
|
|
32
|
+
oauth.clientSecret,
|
|
33
|
+
process.env.SLACK_CLIENT_SECRET
|
|
34
|
+
);
|
|
35
|
+
if (!clientId || !clientSecret) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"Slack OAuth mode requires `clientId` and `clientSecret` or SLACK_CLIENT_ID / SLACK_CLIENT_SECRET."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (oauth.stateVerification !== false && !trimToUndefined(oauth.stateSecret) && !oauth.stateStore && !trimToUndefined(process.env.SLACK_STATE_SECRET)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Slack OAuth mode requires `stateSecret` or a custom `stateStore` when state verification is enabled."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
...oauth,
|
|
47
|
+
mode: "oauth",
|
|
48
|
+
clientId,
|
|
49
|
+
clientSecret,
|
|
50
|
+
stateSecret: firstNonEmptyString(
|
|
51
|
+
oauth.stateSecret,
|
|
52
|
+
process.env.SLACK_STATE_SECRET
|
|
53
|
+
)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeSlackEventsPath(path) {
|
|
57
|
+
const trimmed = path.trim();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
throw new Error("Slack events path must not be empty.");
|
|
60
|
+
}
|
|
61
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
62
|
+
}
|
|
63
|
+
function trimToUndefined(value) {
|
|
64
|
+
const trimmed = value?.trim();
|
|
65
|
+
return trimmed || void 0;
|
|
66
|
+
}
|
|
67
|
+
function firstNonEmptyString(...values) {
|
|
68
|
+
for (const value of values) {
|
|
69
|
+
const trimmed = trimToUndefined(value);
|
|
70
|
+
if (trimmed) {
|
|
71
|
+
return trimmed;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
resolveDirectAuth,
|
|
79
|
+
normalizeSlackEventsPath,
|
|
80
|
+
trimToUndefined
|
|
81
|
+
};
|
|
@@ -1,53 +1,3 @@
|
|
|
1
|
-
// src/shared/formatting.ts
|
|
2
|
-
function resolveSlackMessageFormatter(options) {
|
|
3
|
-
if (options.formatMessageText) {
|
|
4
|
-
return options.formatMessageText;
|
|
5
|
-
}
|
|
6
|
-
if (options.formatChatMarkdown === false) {
|
|
7
|
-
return identity;
|
|
8
|
-
}
|
|
9
|
-
return markdownToSlackMrkdwn;
|
|
10
|
-
}
|
|
11
|
-
function markdownToSlackMrkdwn(markdown) {
|
|
12
|
-
if (!markdown) {
|
|
13
|
-
return markdown;
|
|
14
|
-
}
|
|
15
|
-
const fences = [];
|
|
16
|
-
const withoutFences = markdown.replace(
|
|
17
|
-
/```[^\n`]*\n([\s\S]*?)```/g,
|
|
18
|
-
(_match, code) => {
|
|
19
|
-
const index = fences.push(`\`\`\`
|
|
20
|
-
${code}\`\`\``) - 1;
|
|
21
|
-
return `@@SLACK_FENCE_${index}@@`;
|
|
22
|
-
}
|
|
23
|
-
);
|
|
24
|
-
const boldSegments = [];
|
|
25
|
-
const withBoldPlaceholders = withoutFences.split("\n").map(formatMarkdownLine).join("\n").replace(/\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g, "<$2|$1>").replace(/\*\*([^*\n]+)\*\*/g, (_match, text) => {
|
|
26
|
-
const index = boldSegments.push(`*${text}*`) - 1;
|
|
27
|
-
return `@@SLACK_BOLD_${index}@@`;
|
|
28
|
-
}).replace(/__([^_\n]+)__/g, (_match, text) => {
|
|
29
|
-
const index = boldSegments.push(`*${text}*`) - 1;
|
|
30
|
-
return `@@SLACK_BOLD_${index}@@`;
|
|
31
|
-
});
|
|
32
|
-
const converted = withBoldPlaceholders.replace(
|
|
33
|
-
/(^|[\s([{"'])\*([^*\n][^*\n]*?)\*(?=[\s.,;:!?)}\]'"]|$)/g,
|
|
34
|
-
"$1_$2_"
|
|
35
|
-
);
|
|
36
|
-
return converted.replace(/@@SLACK_BOLD_(\d+)@@/g, (_match, rawIndex) => {
|
|
37
|
-
const index = Number(rawIndex);
|
|
38
|
-
return boldSegments[index] ?? "";
|
|
39
|
-
}).replace(/@@SLACK_FENCE_(\d+)@@/g, (_match, rawIndex) => {
|
|
40
|
-
const index = Number(rawIndex);
|
|
41
|
-
return fences[index] ?? "";
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
function formatMarkdownLine(line) {
|
|
45
|
-
return line.replace(/^(\s*)#{1,6}\s+(.+?)\s*#*\s*$/, "$1**$2**");
|
|
46
|
-
}
|
|
47
|
-
function identity(text) {
|
|
48
|
-
return text;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
1
|
// src/shared/turn-context.ts
|
|
52
2
|
import { AsyncLocalStorage } from "async_hooks";
|
|
53
3
|
var store = new AsyncLocalStorage();
|
|
@@ -132,8 +82,6 @@ function resolveThreadAwareSlackSessionId(info) {
|
|
|
132
82
|
}
|
|
133
83
|
|
|
134
84
|
export {
|
|
135
|
-
resolveSlackMessageFormatter,
|
|
136
|
-
markdownToSlackMrkdwn,
|
|
137
85
|
currentSlackTurnContext,
|
|
138
86
|
runWithSlackTurnContext,
|
|
139
87
|
formatSlackAttributedFollowUp,
|
package/dist/core.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
currentSlackTurnContext,
|
|
3
3
|
formatSlackAttributedFollowUp,
|
|
4
|
-
markdownToSlackMrkdwn,
|
|
5
|
-
resolveSlackMessageFormatter,
|
|
6
4
|
resolveThreadAwareSlackSessionId,
|
|
7
5
|
runWithSlackTurnContext
|
|
8
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-X4WBBBYM.js";
|
|
7
|
+
import {
|
|
8
|
+
markdownToSlackMrkdwn,
|
|
9
|
+
resolveSlackMessageFormatter
|
|
10
|
+
} from "./chunk-6WHFQUYQ.js";
|
|
9
11
|
import {
|
|
10
12
|
extractSlackActionToken,
|
|
11
13
|
extractSlackAuthContext,
|