@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.
Files changed (64) hide show
  1. package/README.md +25 -136
  2. package/dist/app-home.d.ts +23 -0
  3. package/dist/app-home.js +35 -0
  4. package/dist/artifacts/index.d.ts +135 -0
  5. package/dist/artifacts/index.js +299 -0
  6. package/dist/{assistant.d.ts → assistant/index.d.ts} +1 -1
  7. package/dist/{assistant.js → assistant/index.js} +2 -2
  8. package/dist/auth/index.d.ts +56 -0
  9. package/dist/auth/index.js +168 -0
  10. package/dist/{chunk-IDVDMJ5U.js → chunk-6JSGIVQH.js} +110 -3
  11. package/dist/chunk-6WHFQUYQ.js +54 -0
  12. package/dist/{bolt.js → chunk-73QXT7MA.js} +29 -322
  13. package/dist/{chunk-CMR6B76C.js → chunk-DNVSH7H5.js} +407 -1
  14. package/dist/chunk-QJYCHWN6.js +76 -0
  15. package/dist/chunk-S3SWPYXJ.js +81 -0
  16. package/dist/{chunk-JZG4IETE.js → chunk-X4WBBBYM.js} +0 -52
  17. package/dist/core.js +5 -3
  18. package/dist/diagnostics/index.d.ts +71 -0
  19. package/dist/{diagnostics.js → diagnostics/index.js} +5 -1
  20. package/dist/entrypoints/index.d.ts +120 -0
  21. package/dist/entrypoints/index.js +132 -0
  22. package/dist/{history.d.ts → history/index.d.ts} +2 -2
  23. package/dist/{history.js → history/index.js} +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +28 -15
  26. package/dist/{policy.d.ts → policy/index.d.ts} +103 -2
  27. package/dist/{policy.js → policy/index.js} +13 -1
  28. package/dist/runtime-BNBHOZSQ.d.ts +53 -0
  29. package/dist/{setup.d.ts → setup/index.d.ts} +30 -3
  30. package/dist/{setup.js → setup/index.js} +134 -3
  31. package/dist/transports/http/index.d.ts +68 -0
  32. package/dist/transports/http/index.js +8 -0
  33. package/dist/transports/index.d.ts +8 -0
  34. package/dist/transports/index.js +24 -0
  35. package/dist/transports/socket/index.d.ts +94 -0
  36. package/dist/transports/socket/index.js +19 -0
  37. package/dist/types-B9NfCVrk.d.ts +141 -0
  38. package/docs/README.md +31 -0
  39. package/docs/concepts/activity.md +3 -3
  40. package/docs/concepts/artifacts.md +56 -0
  41. package/docs/concepts/entrypoints.md +73 -0
  42. package/docs/concepts/message-policy.md +13 -0
  43. package/docs/concepts/setup-requirements.md +23 -0
  44. package/docs/concepts/{bolt-runtime.md → transport-runtime.md} +14 -4
  45. package/docs/recipes/app-mention-handler.md +5 -0
  46. package/docs/recipes/generate-slack-manifest.md +16 -0
  47. package/docs/recipes/publish-artifact.md +45 -0
  48. package/docs/recipes/slash-command-and-shortcut.md +51 -0
  49. package/docs/recipes/socket-mode-app.md +1 -1
  50. package/docs/reference/channel-slack-boundary.md +10 -6
  51. package/docs/reference/exports.md +7 -2
  52. package/docs/reference/source-layout.md +35 -0
  53. package/package.json +63 -39
  54. package/dist/bolt.d.ts +0 -364
  55. package/dist/chunk-NE57BLLU.js +0 -0
  56. package/dist/diagnostics.d.ts +0 -22
  57. package/dist/shared.d.ts +0 -2
  58. package/dist/shared.js +0 -43
  59. /package/dist/{feedback.d.ts → feedback/index.d.ts} +0 -0
  60. /package/dist/{feedback.js → feedback/index.js} +0 -0
  61. /package/dist/{targets.d.ts → targets/index.d.ts} +0 -0
  62. /package/dist/{targets.js → targets/index.js} +0 -0
  63. /package/dist/{users.d.ts → users/index.d.ts} +0 -0
  64. /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-JZG4IETE.js";
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,