@cuylabs/channel-slack 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -12
- package/dist/bolt.d.ts +30 -1
- package/dist/bolt.js +167 -2
- package/dist/chunk-CMR6B76C.js +664 -0
- package/dist/{chunk-FX2JOVX5.js → chunk-IDVDMJ5U.js} +262 -1
- package/dist/diagnostics.d.ts +21 -104
- package/dist/diagnostics.js +21 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -1
- package/dist/inspect-BpY5JA0K.d.ts +128 -0
- package/dist/policy.d.ts +47 -1
- package/dist/policy.js +7 -1
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +1 -1
- package/docs/concepts/bolt-runtime.md +31 -0
- package/docs/concepts/message-policy.md +45 -0
- package/docs/recipes/app-mention-handler.md +5 -0
- package/docs/reference/channel-slack-boundary.md +2 -2
- package/docs/reference/exports.md +12 -12
- package/package.json +1 -1
- package/dist/chunk-BODPT4I6.js +0 -322
package/dist/policy.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAsyncSlackMessagePolicyResolver,
|
|
3
3
|
createInMemorySlackMessagePolicyStateStore,
|
|
4
|
+
createPostgresSlackMessagePolicyStateStore,
|
|
4
5
|
createSlackMessagePolicyMessageKey,
|
|
5
6
|
createSlackMessagePolicyResolver,
|
|
6
7
|
createSlackMessagePolicyThreadKey,
|
|
8
|
+
initializePostgresSlackMessagePolicyState,
|
|
9
|
+
prunePostgresSlackMessagePolicyState,
|
|
7
10
|
shouldRegisterSlackPassiveChannelMessages
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CMR6B76C.js";
|
|
9
12
|
export {
|
|
10
13
|
createAsyncSlackMessagePolicyResolver,
|
|
11
14
|
createInMemorySlackMessagePolicyStateStore,
|
|
15
|
+
createPostgresSlackMessagePolicyStateStore,
|
|
12
16
|
createSlackMessagePolicyMessageKey,
|
|
13
17
|
createSlackMessagePolicyResolver,
|
|
14
18
|
createSlackMessagePolicyThreadKey,
|
|
19
|
+
initializePostgresSlackMessagePolicyState,
|
|
20
|
+
prunePostgresSlackMessagePolicyState,
|
|
15
21
|
shouldRegisterSlackPassiveChannelMessages
|
|
16
22
|
};
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InspectSlackConnectionOptions, SlackDiagnosticsClient, SlackConnectionInspection } from './
|
|
1
|
+
import { I as InspectSlackConnectionOptions, f as SlackDiagnosticsClient, c as SlackConnectionInspection } from './inspect-BpY5JA0K.js';
|
|
2
2
|
|
|
3
3
|
type SlackSetupFeature = "assistant" | "app-mentions" | "direct-messages" | "channel-messages" | "history" | "interactivity" | "feedback" | "user-profiles" | "user-emails";
|
|
4
4
|
type SlackSetupFeaturePreset = "assistant" | "channel-adapter" | "agent-app";
|
package/dist/setup.js
CHANGED
|
@@ -28,3 +28,34 @@ that can:
|
|
|
28
28
|
- optionally trip a restart guard after repeated websocket health warnings.
|
|
29
29
|
|
|
30
30
|
Use `runtime.close()` during shutdown so a process lock is released.
|
|
31
|
+
|
|
32
|
+
The runtime log reports the SDK-local process lock separately as
|
|
33
|
+
`processLockConfigured` and `processLockAcquired`. These fields describe only
|
|
34
|
+
the SDK file/process lock created by `createSlackSocketModeRuntime`; they do not
|
|
35
|
+
describe a distributed Postgres lock acquired separately.
|
|
36
|
+
|
|
37
|
+
For distributed deployments, `acquireSlackSocketModePostgresLock` can acquire a
|
|
38
|
+
Postgres advisory lock before starting the Socket Mode app:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import {
|
|
42
|
+
acquireSlackSocketModePostgresLock,
|
|
43
|
+
createSlackSocketModeRuntime,
|
|
44
|
+
} from "@cuylabs/channel-slack/bolt";
|
|
45
|
+
|
|
46
|
+
const postgresLock = await acquireSlackSocketModePostgresLock({
|
|
47
|
+
appSlug: "my-agent",
|
|
48
|
+
appToken: process.env.SLACK_APP_TOKEN,
|
|
49
|
+
connectionString: process.env.DATABASE_URL,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const runtime = createSlackSocketModeRuntime({
|
|
53
|
+
appSlug: "my-agent",
|
|
54
|
+
appToken: process.env.SLACK_APP_TOKEN,
|
|
55
|
+
lockEnabled: false,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use `postgresLock.release()` or `postgresLock.close()` during shutdown. Pass
|
|
60
|
+
`namespace` if an existing deployment already coordinates with a specific
|
|
61
|
+
advisory-lock key namespace.
|
|
@@ -16,6 +16,10 @@ const decision = policy.resolve(activity);
|
|
|
16
16
|
if (!decision.accepted) return;
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
`decision.reason` is intended for host-owned logs, metrics, and audit records.
|
|
20
|
+
For example, a host can distinguish `bot-mentioned` from
|
|
21
|
+
`mentioned-thread-reply` without the SDK choosing log levels or log shape.
|
|
22
|
+
|
|
19
23
|
## Channel Message Policy
|
|
20
24
|
|
|
21
25
|
`messagePolicy` controls passive channel messages:
|
|
@@ -27,6 +31,15 @@ if (!decision.accepted) return;
|
|
|
27
31
|
|
|
28
32
|
DMs and direct mentions are always accepted unless they are duplicates.
|
|
29
33
|
|
|
34
|
+
Accepted decisions include one of these reasons:
|
|
35
|
+
|
|
36
|
+
- `direct-message`: the message came from a DM.
|
|
37
|
+
- `bot-mentioned`: the bot was directly mentioned.
|
|
38
|
+
- `mentioned-thread-reply`: a non-mentioned reply continued a remembered
|
|
39
|
+
mentioned thread.
|
|
40
|
+
- `allowed-channel`: a passive message matched `allowed-channels`.
|
|
41
|
+
- `any-added-channel`: a passive message matched `any-added-channel`.
|
|
42
|
+
|
|
30
43
|
## Thread Reply Policy
|
|
31
44
|
|
|
32
45
|
`threadReplyPolicy` controls non-mentioned replies inside remembered mentioned
|
|
@@ -47,3 +60,35 @@ The in-memory store is enough for single-process development. Production
|
|
|
47
60
|
multi-worker deployments should pass an async durable store to
|
|
48
61
|
`createAsyncSlackMessagePolicyResolver` and implement `claimAcceptedMessage` as
|
|
49
62
|
an insert-if-absent operation.
|
|
63
|
+
|
|
64
|
+
## Postgres State
|
|
65
|
+
|
|
66
|
+
`createPostgresSlackMessagePolicyStateStore` provides a durable state store for
|
|
67
|
+
Postgres deployments:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import {
|
|
71
|
+
createAsyncSlackMessagePolicyResolver,
|
|
72
|
+
createPostgresSlackMessagePolicyStateStore,
|
|
73
|
+
} from "@cuylabs/channel-slack/policy";
|
|
74
|
+
|
|
75
|
+
const stateStore = createPostgresSlackMessagePolicyStateStore({
|
|
76
|
+
connectionString: process.env.DATABASE_URL,
|
|
77
|
+
schema: "agent_state",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const policy = createAsyncSlackMessagePolicyResolver({
|
|
81
|
+
messagePolicy: "mentioned-threads",
|
|
82
|
+
threadReplyPolicy: "original-user",
|
|
83
|
+
stateStore,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The helper creates two tables by default:
|
|
88
|
+
|
|
89
|
+
- `channel_slack_message_policy_threads`
|
|
90
|
+
- `channel_slack_message_policy_messages`
|
|
91
|
+
|
|
92
|
+
Pass `mentionedThreadsTableName` and `acceptedMessagesTableName` when migrating
|
|
93
|
+
from an existing application-owned schema. Call `stateStore.close()` during
|
|
94
|
+
shutdown if the store owns its `pg` pool.
|
|
@@ -19,6 +19,11 @@ app.event("app_mention", async ({ event, client }) => {
|
|
|
19
19
|
const activity = parseSlackMentionActivity(event);
|
|
20
20
|
const decision = policy.resolve(activity);
|
|
21
21
|
if (!decision.accepted) return;
|
|
22
|
+
logger.info("Slack message accepted", {
|
|
23
|
+
channelId: activity.channelId,
|
|
24
|
+
reason: decision.reason,
|
|
25
|
+
threadTs: activity.threadTs,
|
|
26
|
+
});
|
|
22
27
|
|
|
23
28
|
const answer = await runAgent({
|
|
24
29
|
input: decision.text,
|
|
@@ -15,13 +15,13 @@ approval or human-input contracts.
|
|
|
15
15
|
- Markdown-to-Slack formatting.
|
|
16
16
|
- Thread-aware session helpers.
|
|
17
17
|
- Slack auth and installation store helpers.
|
|
18
|
-
- Socket Mode runtime guard and
|
|
18
|
+
- Socket Mode runtime guard, process lock, and optional Postgres advisory lock.
|
|
19
19
|
- Setup requirements and manifest helpers.
|
|
20
20
|
- Diagnostics.
|
|
21
21
|
- User profile and mention helpers.
|
|
22
22
|
- Target parsing and resolution.
|
|
23
23
|
- Feedback blocks and action handler.
|
|
24
|
-
- Slack message policy resolver and state
|
|
24
|
+
- Slack message policy resolver and in-memory/Postgres state stores.
|
|
25
25
|
- Supplemental history reader, context loader, and visibility policy.
|
|
26
26
|
- Assistant message parser and thread-context store.
|
|
27
27
|
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
Use feature subpaths for new code. They make dependency expectations clearer and
|
|
4
4
|
keep application code close to the package boundary it uses.
|
|
5
5
|
|
|
6
|
-
| Export
|
|
7
|
-
|
|
|
8
|
-
| `@cuylabs/channel-slack/core`
|
|
9
|
-
| `@cuylabs/channel-slack/shared`
|
|
10
|
-
| `@cuylabs/channel-slack/policy`
|
|
11
|
-
| `@cuylabs/channel-slack/history`
|
|
12
|
-
| `@cuylabs/channel-slack/bolt`
|
|
13
|
-
| `@cuylabs/channel-slack/setup`
|
|
14
|
-
| `@cuylabs/channel-slack/diagnostics` | `@slack/web-api`
|
|
15
|
-
| `@cuylabs/channel-slack/users`
|
|
16
|
-
| `@cuylabs/channel-slack/targets`
|
|
17
|
-
| `@cuylabs/channel-slack/feedback`
|
|
6
|
+
| Export | Depends on | Notes |
|
|
7
|
+
| ------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
8
|
+
| `@cuylabs/channel-slack/core` | no Slack SDK runtime imports | Transport-neutral parsing, formatting, types, sessions, turn context |
|
|
9
|
+
| `@cuylabs/channel-slack/shared` | no Slack SDK runtime imports | Compatibility alias for `core` |
|
|
10
|
+
| `@cuylabs/channel-slack/policy` | `pg` only when using connection-string Postgres state | Message admission and in-memory/Postgres policy state |
|
|
11
|
+
| `@cuylabs/channel-slack/history` | `@slack/web-api` types | History reader accepts a Slack WebClient or minimal conversations client |
|
|
12
|
+
| `@cuylabs/channel-slack/bolt` | `@slack/bolt`, `express`; `pg` only when using connection-string Postgres locks | Bolt app factories and Socket Mode runtime helpers |
|
|
13
|
+
| `@cuylabs/channel-slack/setup` | diagnostics only when inspecting live tokens | Setup requirements and app manifest helpers |
|
|
14
|
+
| `@cuylabs/channel-slack/diagnostics` | `@slack/web-api` | Token, auth, and scope inspection |
|
|
15
|
+
| `@cuylabs/channel-slack/users` | `@slack/web-api` when using default client | User profile lookup and mention enrichment |
|
|
16
|
+
| `@cuylabs/channel-slack/targets` | no Slack SDK runtime imports unless resolving names through a client | Parse and resolve channel/user targets |
|
|
17
|
+
| `@cuylabs/channel-slack/feedback` | `@slack/types` types | Feedback block and action helpers |
|
|
18
18
|
|
|
19
19
|
The root export remains available for lightweight core and policy helpers:
|
|
20
20
|
|
package/package.json
CHANGED
package/dist/chunk-BODPT4I6.js
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
// src/policy/message/keys.ts
|
|
2
|
-
function createSlackMessagePolicyMessageKey(activity) {
|
|
3
|
-
if (!activity.messageTs) {
|
|
4
|
-
return void 0;
|
|
5
|
-
}
|
|
6
|
-
return `${activity.teamId ?? "unknown"}:${activity.channelId}:${activity.messageTs}`;
|
|
7
|
-
}
|
|
8
|
-
function createSlackMessagePolicyThreadKey(activity) {
|
|
9
|
-
return `${activity.teamId ?? "unknown"}:${activity.channelId}:${activity.threadTs ?? activity.messageTs ?? "unthreaded"}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// src/policy/message/state-store.ts
|
|
13
|
-
var DEFAULT_MAX_MENTIONED_THREADS = 1e4;
|
|
14
|
-
var DEFAULT_MAX_ACCEPTED_MESSAGES = 1e4;
|
|
15
|
-
function createInMemorySlackMessagePolicyStateStore({
|
|
16
|
-
maxAcceptedMessages = DEFAULT_MAX_ACCEPTED_MESSAGES,
|
|
17
|
-
maxMentionedThreads = DEFAULT_MAX_MENTIONED_THREADS
|
|
18
|
-
} = {}) {
|
|
19
|
-
const mentionedThreads = /* @__PURE__ */ new Map();
|
|
20
|
-
const mentionedThreadOrder = [];
|
|
21
|
-
const acceptedMessageKeys = /* @__PURE__ */ new Set();
|
|
22
|
-
const acceptedMessageOrder = [];
|
|
23
|
-
function claimAcceptedMessageKey(key) {
|
|
24
|
-
if (acceptedMessageKeys.has(key)) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
acceptedMessageKeys.add(key);
|
|
28
|
-
acceptedMessageOrder.push(key);
|
|
29
|
-
trimSetByOrder(
|
|
30
|
-
acceptedMessageKeys,
|
|
31
|
-
acceptedMessageOrder,
|
|
32
|
-
maxAcceptedMessages
|
|
33
|
-
);
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
getMentionedThread(key) {
|
|
38
|
-
return mentionedThreads.get(key);
|
|
39
|
-
},
|
|
40
|
-
hasAcceptedMessage(key) {
|
|
41
|
-
return acceptedMessageKeys.has(key);
|
|
42
|
-
},
|
|
43
|
-
claimAcceptedMessage(key) {
|
|
44
|
-
return claimAcceptedMessageKey(key);
|
|
45
|
-
},
|
|
46
|
-
rememberMentionedThread(key, state) {
|
|
47
|
-
if (!mentionedThreads.has(key)) {
|
|
48
|
-
mentionedThreadOrder.push(key);
|
|
49
|
-
}
|
|
50
|
-
mentionedThreads.set(key, state);
|
|
51
|
-
trimMapByOrder(
|
|
52
|
-
mentionedThreads,
|
|
53
|
-
mentionedThreadOrder,
|
|
54
|
-
maxMentionedThreads
|
|
55
|
-
);
|
|
56
|
-
},
|
|
57
|
-
rememberAcceptedMessage(key) {
|
|
58
|
-
claimAcceptedMessageKey(key);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function trimMapByOrder(map, order, maxEntries) {
|
|
63
|
-
if (!Number.isFinite(maxEntries) || maxEntries <= 0) {
|
|
64
|
-
map.clear();
|
|
65
|
-
order.length = 0;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
while (order.length > maxEntries) {
|
|
69
|
-
const oldest = order.shift();
|
|
70
|
-
if (oldest !== void 0) {
|
|
71
|
-
map.delete(oldest);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function trimSetByOrder(set, order, maxEntries) {
|
|
76
|
-
if (!Number.isFinite(maxEntries) || maxEntries <= 0) {
|
|
77
|
-
set.clear();
|
|
78
|
-
order.length = 0;
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
while (order.length > maxEntries) {
|
|
82
|
-
const oldest = order.shift();
|
|
83
|
-
if (oldest !== void 0) {
|
|
84
|
-
set.delete(oldest);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/policy/message/async-resolver.ts
|
|
90
|
-
function createAsyncSlackMessagePolicyResolver(config = {}) {
|
|
91
|
-
const allowedChannelIds = new Set(config.allowedChannelIds ?? []);
|
|
92
|
-
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
93
|
-
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
94
|
-
const stateStore = config.stateStore ?? createInMemorySlackMessagePolicyStateStore({
|
|
95
|
-
maxAcceptedMessages: config.maxAcceptedMessages,
|
|
96
|
-
maxMentionedThreads: config.maxMentionedThreads
|
|
97
|
-
});
|
|
98
|
-
async function rememberMentionedThread(activity) {
|
|
99
|
-
const key = createSlackMessagePolicyThreadKey(activity);
|
|
100
|
-
const originalUserId = activity.parentUserId ?? activity.userId;
|
|
101
|
-
await stateStore.rememberMentionedThread(
|
|
102
|
-
key,
|
|
103
|
-
{ originalUserId },
|
|
104
|
-
{
|
|
105
|
-
activity,
|
|
106
|
-
key
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
async function claimAcceptedMessage(activity) {
|
|
111
|
-
const key = createSlackMessagePolicyMessageKey(activity);
|
|
112
|
-
if (!key) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
const context = { activity, key };
|
|
116
|
-
if (stateStore.claimAcceptedMessage) {
|
|
117
|
-
return await stateStore.claimAcceptedMessage(key, context);
|
|
118
|
-
}
|
|
119
|
-
if (await stateStore.hasAcceptedMessage(key, context)) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
await stateStore.rememberAcceptedMessage(key, context);
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
async function isDuplicate(activity) {
|
|
126
|
-
const key = createSlackMessagePolicyMessageKey(activity);
|
|
127
|
-
return Boolean(
|
|
128
|
-
key && await stateStore.hasAcceptedMessage(key, { activity, key })
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
async function accept(activity, reason) {
|
|
132
|
-
if (!await claimAcceptedMessage(activity)) {
|
|
133
|
-
return reject(activity, "duplicate-message-event");
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
accepted: true,
|
|
137
|
-
activity,
|
|
138
|
-
reason,
|
|
139
|
-
text: activity.text
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function reject(activity, reason) {
|
|
143
|
-
return {
|
|
144
|
-
accepted: false,
|
|
145
|
-
activity,
|
|
146
|
-
reason
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
async function resolve(activity) {
|
|
150
|
-
if (activity.isMention) {
|
|
151
|
-
await rememberMentionedThread(activity);
|
|
152
|
-
return accept(activity, "bot-mentioned");
|
|
153
|
-
}
|
|
154
|
-
if (await isDuplicate(activity)) {
|
|
155
|
-
return reject(activity, "duplicate-message-event");
|
|
156
|
-
}
|
|
157
|
-
if (activity.channelType === "dm") {
|
|
158
|
-
return accept(activity, "direct-message");
|
|
159
|
-
}
|
|
160
|
-
if (messagePolicy === "disabled") {
|
|
161
|
-
return reject(activity, "passive-channel-messages-disabled");
|
|
162
|
-
}
|
|
163
|
-
if (messagePolicy === "any-added-channel") {
|
|
164
|
-
return accept(activity, "any-added-channel");
|
|
165
|
-
}
|
|
166
|
-
if (messagePolicy === "allowed-channels") {
|
|
167
|
-
return allowedChannelIds.has(activity.channelId) ? accept(activity, "allowed-channel") : reject(activity, "channel-not-allowed");
|
|
168
|
-
}
|
|
169
|
-
const threadKey = createSlackMessagePolicyThreadKey(activity);
|
|
170
|
-
const threadState = await stateStore.getMentionedThread(threadKey, {
|
|
171
|
-
activity,
|
|
172
|
-
key: threadKey
|
|
173
|
-
});
|
|
174
|
-
if (!threadState) {
|
|
175
|
-
return reject(activity, "thread-not-mentioned");
|
|
176
|
-
}
|
|
177
|
-
if (threadReplyPolicy === "mention-required") {
|
|
178
|
-
return reject(activity, "thread-reply-mention-required");
|
|
179
|
-
}
|
|
180
|
-
if (threadReplyPolicy === "original-user" && threadState.originalUserId && activity.userId !== threadState.originalUserId) {
|
|
181
|
-
return reject(activity, "thread-reply-not-original-user");
|
|
182
|
-
}
|
|
183
|
-
return accept(activity, "mentioned-thread-reply");
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
resolve,
|
|
187
|
-
async resolveMessage(activity) {
|
|
188
|
-
const decision = await resolve(activity);
|
|
189
|
-
return decision.accepted ? decision.text : void 0;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// src/policy/message/passive.ts
|
|
195
|
-
function shouldRegisterSlackPassiveChannelMessages(config = {}) {
|
|
196
|
-
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
197
|
-
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
198
|
-
if (messagePolicy === "disabled") {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
if (messagePolicy === "mentioned-threads" && threadReplyPolicy === "mention-required") {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
if (messagePolicy === "allowed-channels") {
|
|
205
|
-
return Boolean(config.allowedChannelIds?.length);
|
|
206
|
-
}
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// src/policy/message/resolver.ts
|
|
211
|
-
function createSlackMessagePolicyResolver(config = {}) {
|
|
212
|
-
const allowedChannelIds = new Set(config.allowedChannelIds ?? []);
|
|
213
|
-
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
214
|
-
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
215
|
-
const stateStore = config.stateStore ?? createInMemorySlackMessagePolicyStateStore({
|
|
216
|
-
maxAcceptedMessages: config.maxAcceptedMessages,
|
|
217
|
-
maxMentionedThreads: config.maxMentionedThreads
|
|
218
|
-
});
|
|
219
|
-
function rememberMentionedThread(activity) {
|
|
220
|
-
const key = createSlackMessagePolicyThreadKey(activity);
|
|
221
|
-
const originalUserId = activity.parentUserId ?? activity.userId;
|
|
222
|
-
stateStore.rememberMentionedThread(
|
|
223
|
-
key,
|
|
224
|
-
{ originalUserId },
|
|
225
|
-
{
|
|
226
|
-
activity,
|
|
227
|
-
key
|
|
228
|
-
}
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
function claimAcceptedMessage(activity) {
|
|
232
|
-
const key = createSlackMessagePolicyMessageKey(activity);
|
|
233
|
-
if (!key) {
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
const context = { activity, key };
|
|
237
|
-
if (stateStore.claimAcceptedMessage) {
|
|
238
|
-
return stateStore.claimAcceptedMessage(key, context);
|
|
239
|
-
}
|
|
240
|
-
if (stateStore.hasAcceptedMessage(key, context)) {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
stateStore.rememberAcceptedMessage(key, context);
|
|
244
|
-
return true;
|
|
245
|
-
}
|
|
246
|
-
function isDuplicate(activity) {
|
|
247
|
-
const key = createSlackMessagePolicyMessageKey(activity);
|
|
248
|
-
return Boolean(
|
|
249
|
-
key && stateStore.hasAcceptedMessage(key, { activity, key })
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
function accept(activity, reason) {
|
|
253
|
-
if (!claimAcceptedMessage(activity)) {
|
|
254
|
-
return reject(activity, "duplicate-message-event");
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
accepted: true,
|
|
258
|
-
activity,
|
|
259
|
-
reason,
|
|
260
|
-
text: activity.text
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
function reject(activity, reason) {
|
|
264
|
-
return {
|
|
265
|
-
accepted: false,
|
|
266
|
-
activity,
|
|
267
|
-
reason
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function resolve(activity) {
|
|
271
|
-
if (activity.isMention) {
|
|
272
|
-
rememberMentionedThread(activity);
|
|
273
|
-
return accept(activity, "bot-mentioned");
|
|
274
|
-
}
|
|
275
|
-
if (isDuplicate(activity)) {
|
|
276
|
-
return reject(activity, "duplicate-message-event");
|
|
277
|
-
}
|
|
278
|
-
if (activity.channelType === "dm") {
|
|
279
|
-
return accept(activity, "direct-message");
|
|
280
|
-
}
|
|
281
|
-
if (messagePolicy === "disabled") {
|
|
282
|
-
return reject(activity, "passive-channel-messages-disabled");
|
|
283
|
-
}
|
|
284
|
-
if (messagePolicy === "any-added-channel") {
|
|
285
|
-
return accept(activity, "any-added-channel");
|
|
286
|
-
}
|
|
287
|
-
if (messagePolicy === "allowed-channels") {
|
|
288
|
-
return allowedChannelIds.has(activity.channelId) ? accept(activity, "allowed-channel") : reject(activity, "channel-not-allowed");
|
|
289
|
-
}
|
|
290
|
-
const threadKey = createSlackMessagePolicyThreadKey(activity);
|
|
291
|
-
const threadState = stateStore.getMentionedThread(threadKey, {
|
|
292
|
-
activity,
|
|
293
|
-
key: threadKey
|
|
294
|
-
});
|
|
295
|
-
if (!threadState) {
|
|
296
|
-
return reject(activity, "thread-not-mentioned");
|
|
297
|
-
}
|
|
298
|
-
if (threadReplyPolicy === "mention-required") {
|
|
299
|
-
return reject(activity, "thread-reply-mention-required");
|
|
300
|
-
}
|
|
301
|
-
if (threadReplyPolicy === "original-user" && threadState.originalUserId && activity.userId !== threadState.originalUserId) {
|
|
302
|
-
return reject(activity, "thread-reply-not-original-user");
|
|
303
|
-
}
|
|
304
|
-
return accept(activity, "mentioned-thread-reply");
|
|
305
|
-
}
|
|
306
|
-
return {
|
|
307
|
-
resolve,
|
|
308
|
-
resolveMessage(activity) {
|
|
309
|
-
const decision = resolve(activity);
|
|
310
|
-
return decision.accepted ? decision.text : void 0;
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export {
|
|
316
|
-
createSlackMessagePolicyMessageKey,
|
|
317
|
-
createSlackMessagePolicyThreadKey,
|
|
318
|
-
createInMemorySlackMessagePolicyStateStore,
|
|
319
|
-
createAsyncSlackMessagePolicyResolver,
|
|
320
|
-
shouldRegisterSlackPassiveChannelMessages,
|
|
321
|
-
createSlackMessagePolicyResolver
|
|
322
|
-
};
|