@zhin.js/adapter-slack 6.0.2 → 6.0.3
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/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +29 -23
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/protocol.d.ts +11 -1
- package/lib/protocol.js +16 -3
- package/package.json +15 -14
- package/src/endpoint.ts +30 -24
- package/src/index.ts +1 -7
- package/src/protocol.ts +25 -4
- /package/commands/endpoint/add/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [name].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [afc0e66]
|
|
8
|
+
- Updated dependencies [2e41ad5]
|
|
9
|
+
- Updated dependencies [9f57124]
|
|
10
|
+
- @zhin.js/core@1.5.2
|
|
11
|
+
- @zhin.js/adapter@1.1.5
|
|
12
|
+
- @zhin.js/im-contract@1.0.1
|
|
13
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
14
|
+
- @zhin.js/command@1.0.7
|
|
15
|
+
- @zhin.js/host-http@1.0.6
|
|
16
|
+
- @zhin.js/agent@1.1.3
|
|
17
|
+
- zhin.js@6.0.2
|
|
18
|
+
|
|
3
19
|
## 6.0.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-slack
|
|
|
24
24
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
|
|
27
|
-
入站:`gateway.receive({
|
|
28
|
-
出站:`send({
|
|
27
|
+
入站:`gateway.receive({ conversation, message: { conversation, id }, content: text, sender, metadata })`(`conversation` 为 ConversationRef:`channel_type: im` → kind `private`,其余 → kind `group`;线程根 ts 进 `threadId`)
|
|
28
|
+
出站:`send({ conversation, payload })` → Web API(`conversation.id` 为 channel,`conversation.threadId` 为 thread_ts)
|
|
29
29
|
|
|
30
30
|
### 平台权限(platform permit)
|
|
31
31
|
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -138,10 +138,7 @@ export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHand
|
|
|
138
138
|
open(): void;
|
|
139
139
|
close(): void;
|
|
140
140
|
stop(): Promise<void>;
|
|
141
|
-
send({
|
|
142
|
-
readonly target: string;
|
|
143
|
-
readonly payload: unknown;
|
|
144
|
-
}): Promise<string>;
|
|
141
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
145
142
|
/** Test / internal: admit a message event when open. */
|
|
146
143
|
admit(event: SlackMessageEvent | SlackEvent): void;
|
|
147
144
|
admitInteraction(payload: SlackInteractionPayload): void;
|
package/lib/endpoint.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { SocketModeClient } from '@slack/socket-mode';
|
|
5
5
|
import { WebClient } from '@slack/web-api';
|
|
6
6
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
|
-
import { formatInboundContent, formatInteractionContent, formatSlashContent,
|
|
7
|
+
import { formatInboundContent, formatInteractionContent, formatSlashContent, resolveSlackChannelType, slackInboundConversation, } from './protocol.js';
|
|
8
8
|
import { registerSlackAgentEndpoint } from './slack-agent-deps.js';
|
|
9
9
|
import { createSlackInboundFilterState, shouldDropSlackInboundMessage, } from './slack-inbound-filter.js';
|
|
10
10
|
import { formatSlackMessageRef, parseSlackMessageRef } from './slack-message-ref.js';
|
|
@@ -106,10 +106,11 @@ export class SlackEndpoint {
|
|
|
106
106
|
this.#started = false;
|
|
107
107
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
108
108
|
}
|
|
109
|
-
async send({
|
|
109
|
+
async send({ conversation, payload }) {
|
|
110
110
|
if (!this.#client)
|
|
111
111
|
throw new Error('Slack client not connected');
|
|
112
|
-
const
|
|
112
|
+
const channel = conversation.id;
|
|
113
|
+
const threadTs = conversation.threadId;
|
|
113
114
|
const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, logger);
|
|
114
115
|
if (result.ts)
|
|
115
116
|
this.trackMessageChannel(result.ts, channel);
|
|
@@ -127,17 +128,22 @@ export class SlackEndpoint {
|
|
|
127
128
|
if (!msg.channel || !msg.ts)
|
|
128
129
|
return;
|
|
129
130
|
this.trackMessageChannel(msg.ts, msg.channel);
|
|
131
|
+
const threadTs = msg.thread_ts && msg.thread_ts !== msg.ts ? msg.thread_ts : undefined;
|
|
132
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
133
|
+
channelId: msg.channel,
|
|
134
|
+
channelType: msg.channel_type,
|
|
135
|
+
threadId: threadTs,
|
|
136
|
+
});
|
|
130
137
|
void this.#options.gateway.receive({
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
conversation,
|
|
139
|
+
message: { conversation, id: msg.ts },
|
|
133
140
|
content: formatInboundContent(msg),
|
|
134
141
|
sender: msg.user ?? msg.channel,
|
|
135
|
-
id: inboundMessageId(msg),
|
|
136
142
|
metadata: Object.freeze({
|
|
137
143
|
endpoint: this.#options.config.name,
|
|
138
144
|
channelType: resolveSlackChannelType(msg),
|
|
139
145
|
userId: msg.user,
|
|
140
|
-
threadTs
|
|
146
|
+
threadTs,
|
|
141
147
|
ts: msg.ts,
|
|
142
148
|
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
143
149
|
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
@@ -145,7 +151,7 @@ export class SlackEndpoint {
|
|
|
145
151
|
}).catch((err) => {
|
|
146
152
|
logger.warn(formatCompact({
|
|
147
153
|
op: 'slack_gateway_receive_failed',
|
|
148
|
-
target:
|
|
154
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
149
155
|
error: err instanceof Error ? err.message : String(err),
|
|
150
156
|
}));
|
|
151
157
|
});
|
|
@@ -161,12 +167,17 @@ export class SlackEndpoint {
|
|
|
161
167
|
if (payload.response_url) {
|
|
162
168
|
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
163
169
|
}
|
|
170
|
+
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
171
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
172
|
+
channelId: channelId || userId,
|
|
173
|
+
// block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
|
|
174
|
+
channelType: channelId ? undefined : 'im',
|
|
175
|
+
});
|
|
164
176
|
void this.#options.gateway.receive({
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
conversation,
|
|
178
|
+
message: { conversation, id: actionTs },
|
|
167
179
|
content: formatInteractionContent(payload),
|
|
168
180
|
sender: userId,
|
|
169
|
-
id: payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`,
|
|
170
181
|
metadata: Object.freeze({
|
|
171
182
|
endpoint: this.#options.config.name,
|
|
172
183
|
eventType: 'block_actions',
|
|
@@ -176,7 +187,7 @@ export class SlackEndpoint {
|
|
|
176
187
|
}).catch((err) => {
|
|
177
188
|
logger.warn(formatCompact({
|
|
178
189
|
op: 'slack_gateway_receive_failed',
|
|
179
|
-
target:
|
|
190
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
180
191
|
error: err instanceof Error ? err.message : String(err),
|
|
181
192
|
}));
|
|
182
193
|
});
|
|
@@ -185,12 +196,14 @@ export class SlackEndpoint {
|
|
|
185
196
|
if (!this.#open)
|
|
186
197
|
return;
|
|
187
198
|
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
199
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
200
|
+
channelId: cmd.channel_id,
|
|
201
|
+
});
|
|
188
202
|
void this.#options.gateway.receive({
|
|
189
|
-
|
|
190
|
-
|
|
203
|
+
conversation,
|
|
204
|
+
message: { conversation, id: cmd.trigger_id },
|
|
191
205
|
content: formatSlashContent(cmd),
|
|
192
206
|
sender: cmd.user_id,
|
|
193
|
-
id: cmd.trigger_id,
|
|
194
207
|
metadata: Object.freeze({
|
|
195
208
|
endpoint: this.#options.config.name,
|
|
196
209
|
eventType: 'slash_command',
|
|
@@ -199,7 +212,7 @@ export class SlackEndpoint {
|
|
|
199
212
|
}).catch((err) => {
|
|
200
213
|
logger.warn(formatCompact({
|
|
201
214
|
op: 'slack_gateway_receive_failed',
|
|
202
|
-
target:
|
|
215
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
203
216
|
error: err instanceof Error ? err.message : String(err),
|
|
204
217
|
}));
|
|
205
218
|
});
|
|
@@ -352,13 +365,6 @@ export class SlackEndpoint {
|
|
|
352
365
|
await this.#socket.start();
|
|
353
366
|
}
|
|
354
367
|
}
|
|
355
|
-
function parseSendTarget(target) {
|
|
356
|
-
const parsed = parseSlackMessageRef(target);
|
|
357
|
-
if (parsed && /^\d+\.\d+$/.test(parsed.ts)) {
|
|
358
|
-
return { channel: parsed.channel, threadTs: parsed.ts };
|
|
359
|
-
}
|
|
360
|
-
return { channel: target };
|
|
361
|
-
}
|
|
362
368
|
/** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
|
|
363
369
|
const SLACK_LIST_PAGE_SIZE = 1000;
|
|
364
370
|
function createSlackEndpointManagement(endpoint) {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue,
|
|
1
|
+
export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue, keyboardToBlockKitBlocks, normalizeWebhookPath, readTextBody, resolveSlackChannelType, resolveSlackConfig, slackInboundConversation, verifySlackSignature, type ResolvedSlackConfig, type SlackAdapterConfig, type SlackBlockAction, type SlackEvent, type SlackEventEnvelope, type SlackInteractionPayload, type SlackMessageEvent, type SlackSlashCommand, type SlackUrlVerification, type SlackWireSegment, } from './protocol.js';
|
|
2
2
|
export { SlackEndpoint, type SlackEndpointOptions, type SlackSocketLike, type SlackWebClientLike, } from './endpoint.js';
|
|
3
3
|
export { registerSlackWebhookRoutes, handleSlackWebhookRequest, type SlackWebhookHandler, } from './webhook.js';
|
|
4
4
|
export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, type SlackAgentDeps, type SlackAgentEndpoint, } from './slack-agent-deps.js';
|
|
5
5
|
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, registerSlackPlatformPermitChecker, slackGroupPermitResolver, } from './platform-permit.js';
|
|
6
|
-
export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
|
|
7
6
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
8
7
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
9
8
|
export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue,
|
|
1
|
+
export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue, keyboardToBlockKitBlocks, normalizeWebhookPath, readTextBody, resolveSlackChannelType, resolveSlackConfig, slackInboundConversation, verifySlackSignature, } from './protocol.js';
|
|
2
2
|
export { SlackEndpoint, } from './endpoint.js';
|
|
3
3
|
export { registerSlackWebhookRoutes, handleSlackWebhookRequest, } from './webhook.js';
|
|
4
4
|
export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, } from './slack-agent-deps.js';
|
|
5
5
|
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, registerSlackPlatformPermitChecker, slackGroupPermitResolver, } from './platform-permit.js';
|
|
6
|
-
export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
|
|
7
6
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
8
7
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
9
8
|
export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
2
3
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
3
4
|
export interface SlackAdapterConfig {
|
|
4
5
|
readonly name?: string;
|
|
@@ -115,11 +116,20 @@ export interface SlackWireSegment {
|
|
|
115
116
|
export declare function resolveSlackConfig(config?: SlackAdapterConfig): ResolvedSlackConfig;
|
|
116
117
|
export declare function normalizeWebhookPath(path: string): string;
|
|
117
118
|
export declare function resolveSlackChannelType(event: Pick<SlackEvent, 'channel_type'>): 'private' | 'group';
|
|
119
|
+
/**
|
|
120
|
+
* 入站归一化 → ConversationRef:channel_type `im` → private,其余(channel/group/mpim)
|
|
121
|
+
* → group;交互/斜杠命令无 channel_type 时按平台 id 前缀推断(D* 为 DM)。Slack 无
|
|
122
|
+
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
123
|
+
*/
|
|
124
|
+
export declare function slackInboundConversation(endpointId: string, opts: {
|
|
125
|
+
readonly channelId: string;
|
|
126
|
+
readonly channelType?: string;
|
|
127
|
+
readonly threadId?: string;
|
|
128
|
+
}): ConversationRef;
|
|
118
129
|
/** Build inbound text for MessageGateway.receive. */
|
|
119
130
|
export declare function formatInboundContent(event: SlackMessageEvent | SlackEvent): string;
|
|
120
131
|
export declare function formatInteractionContent(payload: SlackInteractionPayload): string;
|
|
121
132
|
export declare function formatSlashContent(cmd: SlackSlashCommand): string;
|
|
122
|
-
export declare function inboundMessageId(event: SlackMessageEvent): string;
|
|
123
133
|
/**
|
|
124
134
|
* Wire-encode an already-rendered outbound payload into Slack text + Block Kit blocks.
|
|
125
135
|
* Segment canonicalization is intentionally not done here.
|
package/lib/protocol.js
CHANGED
|
@@ -63,6 +63,22 @@ export function normalizeWebhookPath(path) {
|
|
|
63
63
|
export function resolveSlackChannelType(event) {
|
|
64
64
|
return event.channel_type === 'im' ? 'private' : 'group';
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 入站归一化 → ConversationRef:channel_type `im` → private,其余(channel/group/mpim)
|
|
68
|
+
* → group;交互/斜杠命令无 channel_type 时按平台 id 前缀推断(D* 为 DM)。Slack 无
|
|
69
|
+
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
70
|
+
*/
|
|
71
|
+
export function slackInboundConversation(endpointId, opts) {
|
|
72
|
+
const kind = opts.channelType != null
|
|
73
|
+
? (opts.channelType === 'im' ? 'private' : 'group')
|
|
74
|
+
: (opts.channelId.startsWith('D') ? 'private' : 'group');
|
|
75
|
+
return {
|
|
76
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
77
|
+
kind,
|
|
78
|
+
id: opts.channelId,
|
|
79
|
+
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
66
82
|
/** Build inbound text for MessageGateway.receive. */
|
|
67
83
|
export function formatInboundContent(event) {
|
|
68
84
|
const text = typeof event.text === 'string' ? event.text : '';
|
|
@@ -83,9 +99,6 @@ export function formatInteractionContent(payload) {
|
|
|
83
99
|
export function formatSlashContent(cmd) {
|
|
84
100
|
return `${cmd.command} ${cmd.text}`.trim();
|
|
85
101
|
}
|
|
86
|
-
export function inboundMessageId(event) {
|
|
87
|
-
return `${event.channel}:${event.ts}`;
|
|
88
|
-
}
|
|
89
102
|
/**
|
|
90
103
|
* Wire-encode an already-rendered outbound payload into Slack text + Block Kit blocks.
|
|
91
104
|
* Segment canonicalization is intentionally not done here.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-slack",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
4
4
|
"description": "Zhin.js Slack adapter for Plugin Runtime (Socket Mode / HTTP Events)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,21 +34,22 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@slack/socket-mode": "^2.0.7",
|
|
36
36
|
"@slack/web-api": "^7.19.0",
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/command": "1.0.
|
|
39
|
-
"@zhin.js/core": "1.5.
|
|
40
|
-
"@zhin.js/host-http": "1.0.
|
|
37
|
+
"@zhin.js/adapter": "1.1.5",
|
|
38
|
+
"@zhin.js/command": "1.0.7",
|
|
39
|
+
"@zhin.js/core": "1.5.2",
|
|
40
|
+
"@zhin.js/host-http": "1.0.6",
|
|
41
|
+
"@zhin.js/im-contract": "1.0.1",
|
|
41
42
|
"@zhin.js/logger": "1.0.75",
|
|
42
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
43
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "1.1.
|
|
47
|
-
"@zhin.js/agent": "1.1.
|
|
48
|
-
"@zhin.js/core": "1.5.
|
|
49
|
-
"@zhin.js/host-http": "1.0.
|
|
50
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
51
|
-
"zhin.js": "6.0.
|
|
47
|
+
"@zhin.js/adapter": "1.1.5",
|
|
48
|
+
"@zhin.js/agent": "1.1.3",
|
|
49
|
+
"@zhin.js/core": "1.5.2",
|
|
50
|
+
"@zhin.js/host-http": "1.0.6",
|
|
51
|
+
"@zhin.js/plugin-runtime": "1.1.3",
|
|
52
|
+
"zhin.js": "6.0.2"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
55
|
"zhin.js": {
|
|
@@ -66,8 +67,8 @@
|
|
|
66
67
|
"typescript": "^6.0.3",
|
|
67
68
|
"vitest": "^4.1.10",
|
|
68
69
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/agent": "1.1.
|
|
70
|
-
"zhin.js": "6.0.
|
|
70
|
+
"@zhin.js/agent": "1.1.3",
|
|
71
|
+
"zhin.js": "6.0.2"
|
|
71
72
|
},
|
|
72
73
|
"files": [
|
|
73
74
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
EndpointGroup,
|
|
9
9
|
EndpointInstance,
|
|
10
10
|
EndpointManagement,
|
|
11
|
+
EndpointSendRequest,
|
|
11
12
|
} from '@zhin.js/adapter';
|
|
12
13
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
13
14
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
@@ -17,8 +18,8 @@ import {
|
|
|
17
18
|
formatInboundContent,
|
|
18
19
|
formatInteractionContent,
|
|
19
20
|
formatSlashContent,
|
|
20
|
-
inboundMessageId,
|
|
21
21
|
resolveSlackChannelType,
|
|
22
|
+
slackInboundConversation,
|
|
22
23
|
type ResolvedSlackConfig,
|
|
23
24
|
type SlackEvent,
|
|
24
25
|
type SlackEventEnvelope,
|
|
@@ -202,9 +203,10 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
202
203
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
async send({
|
|
206
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
206
207
|
if (!this.#client) throw new Error('Slack client not connected');
|
|
207
|
-
const
|
|
208
|
+
const channel = conversation.id;
|
|
209
|
+
const threadTs = conversation.threadId;
|
|
208
210
|
const result = await sendSlackContent(
|
|
209
211
|
this.#client,
|
|
210
212
|
payload,
|
|
@@ -224,17 +226,22 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
224
226
|
if (!msg.channel || !msg.ts) return;
|
|
225
227
|
|
|
226
228
|
this.trackMessageChannel(msg.ts, msg.channel);
|
|
229
|
+
const threadTs = msg.thread_ts && msg.thread_ts !== msg.ts ? msg.thread_ts : undefined;
|
|
230
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
231
|
+
channelId: msg.channel,
|
|
232
|
+
channelType: msg.channel_type,
|
|
233
|
+
threadId: threadTs,
|
|
234
|
+
});
|
|
227
235
|
void this.#options.gateway.receive({
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
conversation,
|
|
237
|
+
message: { conversation, id: msg.ts },
|
|
230
238
|
content: formatInboundContent(msg),
|
|
231
239
|
sender: msg.user ?? msg.channel,
|
|
232
|
-
id: inboundMessageId(msg),
|
|
233
240
|
metadata: Object.freeze({
|
|
234
241
|
endpoint: this.#options.config.name,
|
|
235
242
|
channelType: resolveSlackChannelType(msg),
|
|
236
243
|
userId: msg.user,
|
|
237
|
-
threadTs
|
|
244
|
+
threadTs,
|
|
238
245
|
ts: msg.ts,
|
|
239
246
|
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
240
247
|
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
@@ -242,7 +249,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
242
249
|
}).catch((err) => {
|
|
243
250
|
logger.warn(formatCompact({
|
|
244
251
|
op: 'slack_gateway_receive_failed',
|
|
245
|
-
target:
|
|
252
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
246
253
|
error: err instanceof Error ? err.message : String(err),
|
|
247
254
|
}));
|
|
248
255
|
});
|
|
@@ -257,12 +264,17 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
257
264
|
if (payload.response_url) {
|
|
258
265
|
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
259
266
|
}
|
|
267
|
+
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
268
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
269
|
+
channelId: channelId || userId,
|
|
270
|
+
// block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
|
|
271
|
+
channelType: channelId ? undefined : 'im',
|
|
272
|
+
});
|
|
260
273
|
void this.#options.gateway.receive({
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
conversation,
|
|
275
|
+
message: { conversation, id: actionTs },
|
|
263
276
|
content: formatInteractionContent(payload),
|
|
264
277
|
sender: userId,
|
|
265
|
-
id: payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`,
|
|
266
278
|
metadata: Object.freeze({
|
|
267
279
|
endpoint: this.#options.config.name,
|
|
268
280
|
eventType: 'block_actions',
|
|
@@ -272,7 +284,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
272
284
|
}).catch((err) => {
|
|
273
285
|
logger.warn(formatCompact({
|
|
274
286
|
op: 'slack_gateway_receive_failed',
|
|
275
|
-
target:
|
|
287
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
276
288
|
error: err instanceof Error ? err.message : String(err),
|
|
277
289
|
}));
|
|
278
290
|
});
|
|
@@ -281,12 +293,14 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
281
293
|
admitSlashCommand(cmd: SlackSlashCommand): void {
|
|
282
294
|
if (!this.#open) return;
|
|
283
295
|
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
296
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
297
|
+
channelId: cmd.channel_id,
|
|
298
|
+
});
|
|
284
299
|
void this.#options.gateway.receive({
|
|
285
|
-
|
|
286
|
-
|
|
300
|
+
conversation,
|
|
301
|
+
message: { conversation, id: cmd.trigger_id },
|
|
287
302
|
content: formatSlashContent(cmd),
|
|
288
303
|
sender: cmd.user_id,
|
|
289
|
-
id: cmd.trigger_id,
|
|
290
304
|
metadata: Object.freeze({
|
|
291
305
|
endpoint: this.#options.config.name,
|
|
292
306
|
eventType: 'slash_command',
|
|
@@ -295,7 +309,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
295
309
|
}).catch((err) => {
|
|
296
310
|
logger.warn(formatCompact({
|
|
297
311
|
op: 'slack_gateway_receive_failed',
|
|
298
|
-
target:
|
|
312
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
299
313
|
error: err instanceof Error ? err.message : String(err),
|
|
300
314
|
}));
|
|
301
315
|
});
|
|
@@ -461,14 +475,6 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
461
475
|
}
|
|
462
476
|
}
|
|
463
477
|
|
|
464
|
-
function parseSendTarget(target: string): { channel: string; threadTs?: string } {
|
|
465
|
-
const parsed = parseSlackMessageRef(target);
|
|
466
|
-
if (parsed && /^\d+\.\d+$/.test(parsed.ts)) {
|
|
467
|
-
return { channel: parsed.channel, threadTs: parsed.ts };
|
|
468
|
-
}
|
|
469
|
-
return { channel: target };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
478
|
/** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
|
|
473
479
|
const SLACK_LIST_PAGE_SIZE = 1000;
|
|
474
480
|
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,12 @@ export {
|
|
|
4
4
|
formatOutboundWire,
|
|
5
5
|
formatSlashContent,
|
|
6
6
|
headerValue,
|
|
7
|
-
inboundMessageId,
|
|
8
7
|
keyboardToBlockKitBlocks,
|
|
9
8
|
normalizeWebhookPath,
|
|
10
9
|
readTextBody,
|
|
11
10
|
resolveSlackChannelType,
|
|
12
11
|
resolveSlackConfig,
|
|
12
|
+
slackInboundConversation,
|
|
13
13
|
verifySlackSignature,
|
|
14
14
|
type ResolvedSlackConfig,
|
|
15
15
|
type SlackAdapterConfig,
|
|
@@ -52,12 +52,6 @@ export {
|
|
|
52
52
|
slackGroupPermitResolver,
|
|
53
53
|
} from './platform-permit.js';
|
|
54
54
|
|
|
55
|
-
export {
|
|
56
|
-
formatSlackMessageRef,
|
|
57
|
-
parseSlackMessageRef,
|
|
58
|
-
slackMessageTs,
|
|
59
|
-
} from './slack-message-ref.js';
|
|
60
|
-
|
|
61
55
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
62
56
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
63
57
|
export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
package/src/protocol.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
6
6
|
import type { IncomingMessage } from 'node:http';
|
|
7
7
|
import { isMediaRef, type MediaRef } from '@zhin.js/core';
|
|
8
|
+
import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
|
|
8
9
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
9
10
|
import { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
|
10
11
|
|
|
@@ -192,6 +193,30 @@ export function resolveSlackChannelType(event: Pick<SlackEvent, 'channel_type'>)
|
|
|
192
193
|
return event.channel_type === 'im' ? 'private' : 'group';
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
/**
|
|
197
|
+
* 入站归一化 → ConversationRef:channel_type `im` → private,其余(channel/group/mpim)
|
|
198
|
+
* → group;交互/斜杠命令无 channel_type 时按平台 id 前缀推断(D* 为 DM)。Slack 无
|
|
199
|
+
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
200
|
+
*/
|
|
201
|
+
export function slackInboundConversation(
|
|
202
|
+
endpointId: string,
|
|
203
|
+
opts: {
|
|
204
|
+
readonly channelId: string;
|
|
205
|
+
readonly channelType?: string;
|
|
206
|
+
readonly threadId?: string;
|
|
207
|
+
},
|
|
208
|
+
): ConversationRef {
|
|
209
|
+
const kind: ConversationKind = opts.channelType != null
|
|
210
|
+
? (opts.channelType === 'im' ? 'private' : 'group')
|
|
211
|
+
: (opts.channelId.startsWith('D') ? 'private' : 'group');
|
|
212
|
+
return {
|
|
213
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
214
|
+
kind,
|
|
215
|
+
id: opts.channelId,
|
|
216
|
+
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
195
220
|
/** Build inbound text for MessageGateway.receive. */
|
|
196
221
|
export function formatInboundContent(event: SlackMessageEvent | SlackEvent): string {
|
|
197
222
|
const text = typeof event.text === 'string' ? event.text : '';
|
|
@@ -213,10 +238,6 @@ export function formatSlashContent(cmd: SlackSlashCommand): string {
|
|
|
213
238
|
return `${cmd.command} ${cmd.text}`.trim();
|
|
214
239
|
}
|
|
215
240
|
|
|
216
|
-
export function inboundMessageId(event: SlackMessageEvent): string {
|
|
217
|
-
return `${event.channel}:${event.ts}`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
241
|
/**
|
|
221
242
|
* Wire-encode an already-rendered outbound payload into Slack text + Block Kit blocks.
|
|
222
243
|
* Segment canonicalization is intentionally not done here.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|