@zhin.js/adapter-slack 6.0.2 → 6.0.5
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 +95 -0
- package/README.md +3 -3
- package/adapters/slack.js +2 -2
- package/adapters/slack.ts +2 -2
- package/agent/tools/user_info.ts +1 -0
- package/lib/endpoint.d.ts +4 -6
- package/lib/endpoint.js +53 -51
- package/lib/index.d.ts +2 -3
- package/lib/index.js +2 -3
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +13 -3
- package/lib/protocol.js +19 -6
- package/lib/slack-agent-deps.d.ts +16 -3
- package/lib/slack-agent-deps.js +7 -7
- package/package.json +17 -15
- package/plugin.js +6 -4
- package/schema.json +41 -5
- package/src/endpoint.ts +60 -56
- package/src/index.ts +1 -8
- package/src/platform-permit.ts +4 -8
- package/src/protocol.ts +30 -9
- package/src/slack-agent-deps.ts +23 -9
- /package/commands/endpoint/add/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
|
|
66
|
+
## 6.0.4
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- f8c7a54: fix: im
|
|
71
|
+
- Updated dependencies [f8c7a54]
|
|
72
|
+
- @zhin.js/logger@1.0.76
|
|
73
|
+
- @zhin.js/host-http@1.0.7
|
|
74
|
+
- @zhin.js/adapter@1.1.6
|
|
75
|
+
- @zhin.js/agent@1.1.4
|
|
76
|
+
- @zhin.js/command@1.0.8
|
|
77
|
+
- @zhin.js/core@1.5.3
|
|
78
|
+
- @zhin.js/im-contract@1.0.2
|
|
79
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
80
|
+
- zhin.js@6.0.3
|
|
81
|
+
|
|
82
|
+
## 6.0.3
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [afc0e66]
|
|
87
|
+
- Updated dependencies [2e41ad5]
|
|
88
|
+
- Updated dependencies [9f57124]
|
|
89
|
+
- @zhin.js/core@1.5.2
|
|
90
|
+
- @zhin.js/adapter@1.1.5
|
|
91
|
+
- @zhin.js/im-contract@1.0.1
|
|
92
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
93
|
+
- @zhin.js/command@1.0.7
|
|
94
|
+
- @zhin.js/host-http@1.0.6
|
|
95
|
+
- @zhin.js/agent@1.1.3
|
|
96
|
+
- zhin.js@6.0.2
|
|
97
|
+
|
|
3
98
|
## 6.0.2
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Zhin.js Slack 适配器(Plugin Runtime),优先 Socket Mode,也可经 Run
|
|
|
6
6
|
|
|
7
7
|
- **Socket Mode**(默认):WebSocket 长连接,无需公网 URL
|
|
8
8
|
- **HTTP Events API**:`httpHostToken` POST(签名验证),**非** legacy host-router/Koa
|
|
9
|
-
- 入站经 `messageGatewayToken`;出站 `send({
|
|
9
|
+
- 入站经 `messageGatewayToken`;出站 `send({ conversation, payload })` → `chat.postMessage` / Block Kit
|
|
10
10
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
11
11
|
- Block Kit 按钮、斜杠命令、消息编辑、表情反应等(见 `agent/tools/`)
|
|
12
12
|
|
|
@@ -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/adapters/slack.js
CHANGED
|
@@ -21,8 +21,8 @@ export default defineAdapter({
|
|
|
21
21
|
create(context) {
|
|
22
22
|
const config = resolveSlackConfig(context.config);
|
|
23
23
|
// 注册到插件运行时状态(slack.endpoint list 的"运行中"数据源)
|
|
24
|
-
context.use(slackRuntimeStateToken).endpoints.set(config.
|
|
25
|
-
|
|
24
|
+
context.use(slackRuntimeStateToken).endpoints.set(config.id, {
|
|
25
|
+
id: config.id,
|
|
26
26
|
mode: config.mode,
|
|
27
27
|
});
|
|
28
28
|
if (config.mode === 'http') {
|
package/adapters/slack.ts
CHANGED
|
@@ -26,8 +26,8 @@ export default defineAdapter<SlackAdapterConfig>({
|
|
|
26
26
|
create(context) {
|
|
27
27
|
const config = resolveSlackConfig(context.config);
|
|
28
28
|
// 注册到插件运行时状态(slack.endpoint list 的"运行中"数据源)
|
|
29
|
-
context.use(slackRuntimeStateToken).endpoints.set(config.
|
|
30
|
-
|
|
29
|
+
context.use(slackRuntimeStateToken).endpoints.set(config.id, {
|
|
30
|
+
id: config.id,
|
|
31
31
|
mode: config.mode,
|
|
32
32
|
});
|
|
33
33
|
if (config.mode === 'http') {
|
package/agent/tools/user_info.ts
CHANGED
|
@@ -17,6 +17,7 @@ export default defineAgentTool<{
|
|
|
17
17
|
const { getEndpoint } = getSlackAgentDeps();
|
|
18
18
|
const endpoint = getEndpoint(endpoint_id);
|
|
19
19
|
const user = await endpoint.getUserInfo(user_id);
|
|
20
|
+
if (!user) throw new Error(`Slack 用户不存在: ${user_id}`);
|
|
20
21
|
return {
|
|
21
22
|
id: user.id,
|
|
22
23
|
name: user.name,
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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';
|
|
5
5
|
import { type ResolvedSlackConfig, type SlackEvent, type SlackInteractionPayload, type SlackMessageEvent, type SlackSlashCommand } from './protocol.js';
|
|
6
|
+
import { type SlackUserInfo } from './slack-agent-deps.js';
|
|
6
7
|
import { type SlackChatClient } from './slack-outbound.js';
|
|
7
8
|
import { type SlackWebhookHandler } from './webhook.js';
|
|
8
9
|
export interface SlackSocketLike {
|
|
@@ -138,10 +139,7 @@ export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHand
|
|
|
138
139
|
open(): void;
|
|
139
140
|
close(): void;
|
|
140
141
|
stop(): Promise<void>;
|
|
141
|
-
send({
|
|
142
|
-
readonly target: string;
|
|
143
|
-
readonly payload: unknown;
|
|
144
|
-
}): Promise<string>;
|
|
142
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
145
143
|
/** Test / internal: admit a message event when open. */
|
|
146
144
|
admit(event: SlackMessageEvent | SlackEvent): void;
|
|
147
145
|
admitInteraction(payload: SlackInteractionPayload): void;
|
|
@@ -163,7 +161,7 @@ export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHand
|
|
|
163
161
|
renameChannel(channel: string, name: string): Promise<boolean>;
|
|
164
162
|
getChannelMembers(channel: string): Promise<string[]>;
|
|
165
163
|
getChannelInfo(channel: string): Promise<unknown>;
|
|
166
|
-
getUserInfo(user: string): Promise<
|
|
164
|
+
getUserInfo(user: string): Promise<SlackUserInfo | undefined>;
|
|
167
165
|
addReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
168
166
|
removeReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
169
167
|
pinMessage(channel: string, timestamp: string): Promise<boolean>;
|
package/lib/endpoint.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { SocketModeClient } from '@slack/socket-mode';
|
|
5
5
|
import { WebClient } from '@slack/web-api';
|
|
6
|
-
import { formatCompact,
|
|
7
|
-
import { formatInboundContent, formatInteractionContent, formatSlashContent,
|
|
6
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
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';
|
|
@@ -12,8 +12,8 @@ import { normalizeSlackReactionName } from './slack-reaction.js';
|
|
|
12
12
|
import { postSlackEphemeral } from './slack-response-url.js';
|
|
13
13
|
import { editSlackContent, sendSlackContent } from './slack-outbound.js';
|
|
14
14
|
import { registerSlackWebhookRoutes } from './webhook.js';
|
|
15
|
-
const logger = getLogger('slack');
|
|
16
15
|
export class SlackEndpoint {
|
|
16
|
+
#logger;
|
|
17
17
|
#options;
|
|
18
18
|
#inboundFilter = createSlackInboundFilterState();
|
|
19
19
|
#messageChannelMap = new Map();
|
|
@@ -26,11 +26,12 @@ export class SlackEndpoint {
|
|
|
26
26
|
#unregisterAgent;
|
|
27
27
|
management = createSlackEndpointManagement(this);
|
|
28
28
|
constructor(options) {
|
|
29
|
+
this.#logger = getAdapterLogger('slack', options.config.id);
|
|
29
30
|
this.#options = options;
|
|
30
31
|
}
|
|
31
32
|
/** Console 展示 / AdapterIndex live name(多 endpoint 时与 entry name 一致)。 */
|
|
32
33
|
get name() {
|
|
33
|
-
return this.#options.config.
|
|
34
|
+
return this.#options.config.id;
|
|
34
35
|
}
|
|
35
36
|
get client() {
|
|
36
37
|
return this.#client;
|
|
@@ -49,7 +50,7 @@ export class SlackEndpoint {
|
|
|
49
50
|
const { config } = this.#options;
|
|
50
51
|
this.#client = this.#options.createClient?.(config.token)
|
|
51
52
|
?? new WebClient(config.token);
|
|
52
|
-
this.#unregisterAgent = registerSlackAgentEndpoint(config.
|
|
53
|
+
this.#unregisterAgent = registerSlackAgentEndpoint(config.id, this);
|
|
53
54
|
if (config.mode === 'socket') {
|
|
54
55
|
await this.#startSocket();
|
|
55
56
|
}
|
|
@@ -59,8 +60,8 @@ export class SlackEndpoint {
|
|
|
59
60
|
throw new Error('Slack HTTP Events API requires httpHostToken (Runtime Host)');
|
|
60
61
|
}
|
|
61
62
|
this.#routeReleases.push(...registerSlackWebhookRoutes(http, this));
|
|
62
|
-
logger.debug(formatCompact({
|
|
63
|
-
endpoint: config.
|
|
63
|
+
this.#logger.debug(formatCompact({
|
|
64
|
+
endpoint: config.id,
|
|
64
65
|
op: 'webhook',
|
|
65
66
|
path: config.webhookPath,
|
|
66
67
|
}));
|
|
@@ -68,16 +69,12 @@ export class SlackEndpoint {
|
|
|
68
69
|
const authTest = await this.#client.auth.test();
|
|
69
70
|
if (authTest.user_id)
|
|
70
71
|
this.#botUserId = String(authTest.user_id);
|
|
71
|
-
logger.info(
|
|
72
|
-
|
|
73
|
-
endpoint: config.name,
|
|
74
|
-
mode: config.mode,
|
|
75
|
-
platform_user_id: this.#botUserId,
|
|
76
|
-
}));
|
|
72
|
+
this.#logger.info(`connected (${config.mode})`
|
|
73
|
+
+ (this.#botUserId ? ` | user: ${this.#botUserId}` : ''));
|
|
77
74
|
}
|
|
78
75
|
catch (error) {
|
|
79
76
|
await this.stop();
|
|
80
|
-
logger.error('Failed to connect Slack endpoint:', error);
|
|
77
|
+
this.#logger.error('Failed to connect Slack endpoint:', error);
|
|
81
78
|
throw error;
|
|
82
79
|
}
|
|
83
80
|
}
|
|
@@ -104,13 +101,14 @@ export class SlackEndpoint {
|
|
|
104
101
|
this.#unregisterAgent = undefined;
|
|
105
102
|
this.#client = undefined;
|
|
106
103
|
this.#started = false;
|
|
107
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
104
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
108
105
|
}
|
|
109
|
-
async send({
|
|
106
|
+
async send({ conversation, payload }) {
|
|
110
107
|
if (!this.#client)
|
|
111
108
|
throw new Error('Slack client not connected');
|
|
112
|
-
const
|
|
113
|
-
const
|
|
109
|
+
const channel = conversation.id;
|
|
110
|
+
const threadTs = conversation.threadId;
|
|
111
|
+
const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, this.#logger);
|
|
114
112
|
if (result.ts)
|
|
115
113
|
this.trackMessageChannel(result.ts, channel);
|
|
116
114
|
return formatSlackMessageRef(channel, result.ts || String(Date.now()));
|
|
@@ -127,25 +125,29 @@ export class SlackEndpoint {
|
|
|
127
125
|
if (!msg.channel || !msg.ts)
|
|
128
126
|
return;
|
|
129
127
|
this.trackMessageChannel(msg.ts, msg.channel);
|
|
128
|
+
const threadTs = msg.thread_ts && msg.thread_ts !== msg.ts ? msg.thread_ts : undefined;
|
|
129
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
130
|
+
channelId: msg.channel,
|
|
131
|
+
channelType: msg.channel_type,
|
|
132
|
+
threadId: threadTs,
|
|
133
|
+
});
|
|
130
134
|
void this.#options.gateway.receive({
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
conversation,
|
|
136
|
+
message: { conversation, id: msg.ts },
|
|
133
137
|
content: formatInboundContent(msg),
|
|
134
|
-
sender: msg.user ?? msg.channel,
|
|
135
|
-
|
|
138
|
+
sender: { id: msg.user ?? msg.channel ?? '' },
|
|
139
|
+
endpointId: this.#options.config.id,
|
|
140
|
+
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
136
141
|
metadata: Object.freeze({
|
|
137
|
-
endpoint: this.#options.config.name,
|
|
138
142
|
channelType: resolveSlackChannelType(msg),
|
|
139
143
|
userId: msg.user,
|
|
140
|
-
threadTs
|
|
144
|
+
threadTs,
|
|
141
145
|
ts: msg.ts,
|
|
142
|
-
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
143
|
-
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
144
146
|
}),
|
|
145
147
|
}).catch((err) => {
|
|
146
|
-
logger.warn(formatCompact({
|
|
148
|
+
this.#logger.warn(formatCompact({
|
|
147
149
|
op: 'slack_gateway_receive_failed',
|
|
148
|
-
target:
|
|
150
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
149
151
|
error: err instanceof Error ? err.message : String(err),
|
|
150
152
|
}));
|
|
151
153
|
});
|
|
@@ -159,24 +161,29 @@ export class SlackEndpoint {
|
|
|
159
161
|
const userId = payload.user.id;
|
|
160
162
|
const messageTs = payload.message?.ts ?? '';
|
|
161
163
|
if (payload.response_url) {
|
|
162
|
-
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
164
|
+
postSlackEphemeral(payload.response_url, '已收到', this.#logger);
|
|
163
165
|
}
|
|
166
|
+
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
167
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
168
|
+
channelId: channelId || userId,
|
|
169
|
+
// block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
|
|
170
|
+
channelType: channelId ? undefined : 'im',
|
|
171
|
+
});
|
|
164
172
|
void this.#options.gateway.receive({
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
conversation,
|
|
174
|
+
message: { conversation, id: actionTs },
|
|
167
175
|
content: formatInteractionContent(payload),
|
|
168
|
-
sender: userId,
|
|
169
|
-
|
|
176
|
+
sender: { id: userId },
|
|
177
|
+
endpointId: this.#options.config.id,
|
|
170
178
|
metadata: Object.freeze({
|
|
171
|
-
endpoint: this.#options.config.name,
|
|
172
179
|
eventType: 'block_actions',
|
|
173
180
|
actionId: payload.actions[0]?.action_id,
|
|
174
181
|
threadTs: messageTs || undefined,
|
|
175
182
|
}),
|
|
176
183
|
}).catch((err) => {
|
|
177
|
-
logger.warn(formatCompact({
|
|
184
|
+
this.#logger.warn(formatCompact({
|
|
178
185
|
op: 'slack_gateway_receive_failed',
|
|
179
|
-
target:
|
|
186
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
180
187
|
error: err instanceof Error ? err.message : String(err),
|
|
181
188
|
}));
|
|
182
189
|
});
|
|
@@ -184,22 +191,24 @@ export class SlackEndpoint {
|
|
|
184
191
|
admitSlashCommand(cmd) {
|
|
185
192
|
if (!this.#open)
|
|
186
193
|
return;
|
|
187
|
-
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
194
|
+
postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
|
|
195
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
196
|
+
channelId: cmd.channel_id,
|
|
197
|
+
});
|
|
188
198
|
void this.#options.gateway.receive({
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
conversation,
|
|
200
|
+
message: { conversation, id: cmd.trigger_id },
|
|
191
201
|
content: formatSlashContent(cmd),
|
|
192
|
-
sender: cmd.user_id,
|
|
193
|
-
|
|
202
|
+
sender: { id: cmd.user_id, name: cmd.user_name },
|
|
203
|
+
endpointId: this.#options.config.id,
|
|
194
204
|
metadata: Object.freeze({
|
|
195
|
-
endpoint: this.#options.config.name,
|
|
196
205
|
eventType: 'slash_command',
|
|
197
206
|
command: cmd.command,
|
|
198
207
|
}),
|
|
199
208
|
}).catch((err) => {
|
|
200
|
-
logger.warn(formatCompact({
|
|
209
|
+
this.#logger.warn(formatCompact({
|
|
201
210
|
op: 'slack_gateway_receive_failed',
|
|
202
|
-
target:
|
|
211
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
203
212
|
error: err instanceof Error ? err.message : String(err),
|
|
204
213
|
}));
|
|
205
214
|
});
|
|
@@ -352,13 +361,6 @@ export class SlackEndpoint {
|
|
|
352
361
|
await this.#socket.start();
|
|
353
362
|
}
|
|
354
363
|
}
|
|
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
364
|
/** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
|
|
363
365
|
const SLACK_LIST_PAGE_SIZE = 1000;
|
|
364
366
|
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
|
-
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit,
|
|
6
|
-
export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
|
|
5
|
+
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, slackGroupPermitResolver, } from './platform-permit.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
|
-
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit,
|
|
6
|
-
export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
|
|
5
|
+
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, slackGroupPermitResolver, } from './platform-permit.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/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slack platform permit — workspace + channel 权限
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function slackGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeSlackSenderForPermit(input: {
|
|
@@ -12,5 +12,4 @@ export declare function normalizeSlackSenderForPermit(input: {
|
|
|
12
12
|
role?: string;
|
|
13
13
|
permissions?: string[];
|
|
14
14
|
};
|
|
15
|
-
export declare function checkSlackPlatformPermit(perm: string,
|
|
16
|
-
export declare function registerSlackPlatformPermitChecker(): () => void;
|
|
15
|
+
export declare function checkSlackPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slack platform permit — workspace + channel 权限
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'slack';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -29,10 +25,9 @@ export function normalizeSlackSenderForPermit(input) {
|
|
|
29
25
|
}
|
|
30
26
|
return { role: 'member', permissions };
|
|
31
27
|
}
|
|
32
|
-
export function checkSlackPlatformPermit(perm,
|
|
33
|
-
const
|
|
34
|
-
const permissions = sender
|
|
35
|
-
const role = sender.role;
|
|
28
|
+
export function checkSlackPlatformPermit(perm, subject) {
|
|
29
|
+
const role = subject.sender?.role?.[0];
|
|
30
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
36
31
|
const has = (t) => permissions.includes(t);
|
|
37
32
|
switch (perm) {
|
|
38
33
|
case 'workspace_owner':
|
|
@@ -46,6 +41,3 @@ export function checkSlackPlatformPermit(perm, message) {
|
|
|
46
41
|
return false;
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
export function registerSlackPlatformPermitChecker() {
|
|
50
|
-
return registerPlatformPermitChecker(ADAPTER, checkSlackPlatformPermit);
|
|
51
|
-
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
-
readonly
|
|
5
|
+
readonly id?: string;
|
|
5
6
|
readonly token?: string;
|
|
6
7
|
readonly signingSecret?: string;
|
|
7
8
|
readonly appToken?: string;
|
|
@@ -21,7 +22,7 @@ export interface SlackAdapterConfig {
|
|
|
21
22
|
}
|
|
22
23
|
export interface ResolvedSlackConfig {
|
|
23
24
|
readonly context: 'slack';
|
|
24
|
-
readonly
|
|
25
|
+
readonly id: string;
|
|
25
26
|
readonly token: string;
|
|
26
27
|
readonly mode: 'socket' | 'http';
|
|
27
28
|
readonly signingSecret: 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(endpointKey: 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
|
@@ -22,8 +22,8 @@ export function resolveSlackConfig(config = {}) {
|
|
|
22
22
|
if (!token) {
|
|
23
23
|
throw new TypeError('Slack adapter requires token (plugins.<key>.token or endpoints with context: slack)');
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
|| (typeof entry?.
|
|
25
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
26
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
27
27
|
|| process.env.SLACK_BOT_NAME
|
|
28
28
|
|| 'slack-bot';
|
|
29
29
|
const socketMode = config.socketMode ?? entry?.socketMode;
|
|
@@ -45,7 +45,7 @@ export function resolveSlackConfig(config = {}) {
|
|
|
45
45
|
}
|
|
46
46
|
return {
|
|
47
47
|
context: 'slack',
|
|
48
|
-
|
|
48
|
+
id,
|
|
49
49
|
token,
|
|
50
50
|
mode,
|
|
51
51
|
signingSecret,
|
|
@@ -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(endpointKey, 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: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
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.
|
|
@@ -12,17 +12,30 @@ export interface SlackAgentEndpoint {
|
|
|
12
12
|
renameChannel(channel: string, name: string): Promise<boolean>;
|
|
13
13
|
getChannelMembers(channel: string): Promise<string[]>;
|
|
14
14
|
getChannelInfo(channel: string): Promise<unknown>;
|
|
15
|
-
getUserInfo(user: string): Promise<
|
|
15
|
+
getUserInfo(user: string): Promise<SlackUserInfo | undefined>;
|
|
16
16
|
addReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
17
17
|
removeReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
18
18
|
pinMessage(channel: string, timestamp: string): Promise<boolean>;
|
|
19
19
|
unpinMessage(channel: string, timestamp: string): Promise<boolean>;
|
|
20
20
|
editMessage(channel: string, messageTs: string, content: unknown): Promise<void>;
|
|
21
21
|
}
|
|
22
|
+
/** Slack users.info 返回的用户对象(SDK 响应字段对 agent 工具的投影)。 */
|
|
23
|
+
export interface SlackUserInfo {
|
|
24
|
+
id?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
real_name?: string;
|
|
27
|
+
is_admin?: boolean;
|
|
28
|
+
is_bot?: boolean;
|
|
29
|
+
profile?: {
|
|
30
|
+
display_name?: string;
|
|
31
|
+
email?: string;
|
|
32
|
+
status_text?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
22
35
|
export interface SlackAgentDeps {
|
|
23
|
-
getEndpoint: (
|
|
36
|
+
getEndpoint: (endpointKey: string) => SlackAgentEndpoint;
|
|
24
37
|
}
|
|
25
|
-
export declare function registerSlackAgentEndpoint(
|
|
38
|
+
export declare function registerSlackAgentEndpoint(endpointKey: string, endpoint: SlackAgentEndpoint): () => void;
|
|
26
39
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
27
40
|
export declare function setSlackAgentDeps(deps: SlackAgentDeps | null): void;
|
|
28
41
|
export declare function getSlackAgentDeps(): SlackAgentDeps;
|
package/lib/slack-agent-deps.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerSlackAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerSlackAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -20,10 +20,10 @@ export function getSlackAgentDeps() {
|
|
|
20
20
|
if (override)
|
|
21
21
|
return override;
|
|
22
22
|
return {
|
|
23
|
-
getEndpoint(
|
|
24
|
-
const registered = endpoints.get(
|
|
23
|
+
getEndpoint(endpointKey) {
|
|
24
|
+
const registered = endpoints.get(endpointKey);
|
|
25
25
|
if (!registered)
|
|
26
|
-
throw new Error(`Endpoint ${
|
|
26
|
+
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
27
27
|
return registered;
|
|
28
28
|
},
|
|
29
29
|
};
|
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.5",
|
|
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,23 @@
|
|
|
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.
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
37
|
+
"@zhin.js/adapter": "1.1.7",
|
|
38
|
+
"@zhin.js/command": "1.0.9",
|
|
39
|
+
"@zhin.js/core": "1.5.4",
|
|
40
|
+
"@zhin.js/host-http": "1.0.8",
|
|
41
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
42
|
+
"@zhin.js/logger": "1.0.76",
|
|
43
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
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/
|
|
51
|
-
"zhin.js": "
|
|
47
|
+
"@zhin.js/adapter": "1.1.7",
|
|
48
|
+
"@zhin.js/agent": "1.1.5",
|
|
49
|
+
"@zhin.js/core": "1.5.4",
|
|
50
|
+
"@zhin.js/host-http": "1.0.8",
|
|
51
|
+
"@zhin.js/permission": "1.0.1",
|
|
52
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
53
|
+
"zhin.js": "6.0.4"
|
|
52
54
|
},
|
|
53
55
|
"peerDependenciesMeta": {
|
|
54
56
|
"zhin.js": {
|
|
@@ -66,8 +68,8 @@
|
|
|
66
68
|
"typescript": "^6.0.3",
|
|
67
69
|
"vitest": "^4.1.10",
|
|
68
70
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/agent": "1.1.
|
|
70
|
-
"zhin.js": "6.0.
|
|
71
|
+
"@zhin.js/agent": "1.1.5",
|
|
72
|
+
"zhin.js": "6.0.4"
|
|
71
73
|
},
|
|
72
74
|
"files": [
|
|
73
75
|
"adapters",
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
3
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
|
+
import { checkSlackPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { slackRuntimeStateToken } from "./lib/slack-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'slack',
|
|
@@ -9,9 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'Slack Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(slack.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(slackRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('slack', checkSlackPlatformPermit);
|
|
17
|
+
}
|
|
16
18
|
},
|
|
17
19
|
});
|
package/schema.json
CHANGED
|
@@ -17,16 +17,48 @@
|
|
|
17
17
|
"default": 15000,
|
|
18
18
|
"description": "Socket Mode client ping timeout (ms)"
|
|
19
19
|
},
|
|
20
|
+
"master": {
|
|
21
|
+
"type": [
|
|
22
|
+
"string",
|
|
23
|
+
"number"
|
|
24
|
+
],
|
|
25
|
+
"description": "框架 master(Slack user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
26
|
+
},
|
|
27
|
+
"trusted": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": [
|
|
31
|
+
"string",
|
|
32
|
+
"number"
|
|
33
|
+
],
|
|
34
|
+
"description": "Trusted Slack user id"
|
|
35
|
+
},
|
|
36
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
37
|
+
},
|
|
20
38
|
"endpoints": {
|
|
21
39
|
"type": "array",
|
|
22
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
40
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
23
41
|
"items": {
|
|
24
42
|
"type": "object",
|
|
25
43
|
"additionalProperties": true,
|
|
26
44
|
"properties": {
|
|
27
|
-
"
|
|
28
|
-
"type":
|
|
29
|
-
|
|
45
|
+
"master": {
|
|
46
|
+
"type": [
|
|
47
|
+
"string",
|
|
48
|
+
"number"
|
|
49
|
+
],
|
|
50
|
+
"description": "本 endpoint 的框架 master(Slack user id);覆盖顶层 master"
|
|
51
|
+
},
|
|
52
|
+
"trusted": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": [
|
|
56
|
+
"string",
|
|
57
|
+
"number"
|
|
58
|
+
],
|
|
59
|
+
"description": "Trusted Slack user id"
|
|
60
|
+
},
|
|
61
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
30
62
|
},
|
|
31
63
|
"token": {
|
|
32
64
|
"type": "string",
|
|
@@ -39,10 +71,14 @@
|
|
|
39
71
|
"appToken": {
|
|
40
72
|
"type": "string",
|
|
41
73
|
"description": "App-Level Token (xapp-...) for Socket Mode"
|
|
74
|
+
},
|
|
75
|
+
"id": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Slack bot name"
|
|
42
78
|
}
|
|
43
79
|
},
|
|
44
80
|
"required": [
|
|
45
|
-
"
|
|
81
|
+
"id",
|
|
46
82
|
"token"
|
|
47
83
|
]
|
|
48
84
|
}
|
package/src/endpoint.ts
CHANGED
|
@@ -8,17 +8,18 @@ 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';
|
|
14
|
-
import { formatCompact,
|
|
15
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
15
16
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
16
17
|
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,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
type SlackMessageEvent,
|
|
27
28
|
type SlackSlashCommand,
|
|
28
29
|
} from './protocol.js';
|
|
29
|
-
import { registerSlackAgentEndpoint } from './slack-agent-deps.js';
|
|
30
|
+
import { registerSlackAgentEndpoint, type SlackUserInfo } from './slack-agent-deps.js';
|
|
30
31
|
import {
|
|
31
32
|
createSlackInboundFilterState,
|
|
32
33
|
shouldDropSlackInboundMessage,
|
|
@@ -37,8 +38,6 @@ import { postSlackEphemeral } from './slack-response-url.js';
|
|
|
37
38
|
import { editSlackContent, sendSlackContent, type SlackChatClient } from './slack-outbound.js';
|
|
38
39
|
import { registerSlackWebhookRoutes, type SlackWebhookHandler } from './webhook.js';
|
|
39
40
|
|
|
40
|
-
const logger = getLogger('slack');
|
|
41
|
-
|
|
42
41
|
export interface SlackSocketLike {
|
|
43
42
|
on(
|
|
44
43
|
event: string,
|
|
@@ -102,6 +101,8 @@ export interface SlackEndpointOptions {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
104
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
105
|
+
|
|
105
106
|
readonly #options: SlackEndpointOptions;
|
|
106
107
|
readonly #inboundFilter = createSlackInboundFilterState();
|
|
107
108
|
readonly #messageChannelMap = new Map<string, string>();
|
|
@@ -115,12 +116,13 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
115
116
|
readonly management: EndpointManagement = createSlackEndpointManagement(this);
|
|
116
117
|
|
|
117
118
|
constructor(options: SlackEndpointOptions) {
|
|
119
|
+
this.#logger = getAdapterLogger('slack', options.config.id);
|
|
118
120
|
this.#options = options;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
/** Console 展示 / AdapterIndex live name(多 endpoint 时与 entry name 一致)。 */
|
|
122
124
|
get name(): string {
|
|
123
|
-
return this.#options.config.
|
|
125
|
+
return this.#options.config.id;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
get client(): SlackWebClientLike | undefined {
|
|
@@ -143,7 +145,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
143
145
|
this.#client = this.#options.createClient?.(config.token)
|
|
144
146
|
?? (new WebClient(config.token) as unknown as SlackWebClientLike);
|
|
145
147
|
|
|
146
|
-
this.#unregisterAgent = registerSlackAgentEndpoint(config.
|
|
148
|
+
this.#unregisterAgent = registerSlackAgentEndpoint(config.id, this);
|
|
147
149
|
|
|
148
150
|
if (config.mode === 'socket') {
|
|
149
151
|
await this.#startSocket();
|
|
@@ -153,8 +155,8 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
153
155
|
throw new Error('Slack HTTP Events API requires httpHostToken (Runtime Host)');
|
|
154
156
|
}
|
|
155
157
|
this.#routeReleases.push(...registerSlackWebhookRoutes(http, this));
|
|
156
|
-
logger.debug(formatCompact({
|
|
157
|
-
endpoint: config.
|
|
158
|
+
this.#logger.debug(formatCompact({
|
|
159
|
+
endpoint: config.id,
|
|
158
160
|
op: 'webhook',
|
|
159
161
|
path: config.webhookPath,
|
|
160
162
|
}));
|
|
@@ -163,15 +165,13 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
163
165
|
const authTest = await this.#client.auth.test();
|
|
164
166
|
if (authTest.user_id) this.#botUserId = String(authTest.user_id);
|
|
165
167
|
|
|
166
|
-
logger.info(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
platform_user_id: this.#botUserId,
|
|
171
|
-
}));
|
|
168
|
+
this.#logger.info(
|
|
169
|
+
`connected (${config.mode})`
|
|
170
|
+
+ (this.#botUserId ? ` | user: ${this.#botUserId}` : ''),
|
|
171
|
+
);
|
|
172
172
|
} catch (error) {
|
|
173
173
|
await this.stop();
|
|
174
|
-
logger.error('Failed to connect Slack endpoint:', error);
|
|
174
|
+
this.#logger.error('Failed to connect Slack endpoint:', error);
|
|
175
175
|
throw error;
|
|
176
176
|
}
|
|
177
177
|
}
|
|
@@ -199,17 +199,18 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
199
199
|
this.#unregisterAgent = undefined;
|
|
200
200
|
this.#client = undefined;
|
|
201
201
|
this.#started = false;
|
|
202
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
202
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
async send({
|
|
205
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
206
206
|
if (!this.#client) throw new Error('Slack client not connected');
|
|
207
|
-
const
|
|
207
|
+
const channel = conversation.id;
|
|
208
|
+
const threadTs = conversation.threadId;
|
|
208
209
|
const result = await sendSlackContent(
|
|
209
210
|
this.#client,
|
|
210
211
|
payload,
|
|
211
212
|
{ channel, threadTs },
|
|
212
|
-
logger,
|
|
213
|
+
this.#logger,
|
|
213
214
|
);
|
|
214
215
|
if (result.ts) this.trackMessageChannel(result.ts, channel);
|
|
215
216
|
return formatSlackMessageRef(channel, result.ts || String(Date.now()));
|
|
@@ -224,25 +225,29 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
224
225
|
if (!msg.channel || !msg.ts) return;
|
|
225
226
|
|
|
226
227
|
this.trackMessageChannel(msg.ts, msg.channel);
|
|
228
|
+
const threadTs = msg.thread_ts && msg.thread_ts !== msg.ts ? msg.thread_ts : undefined;
|
|
229
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
230
|
+
channelId: msg.channel,
|
|
231
|
+
channelType: msg.channel_type,
|
|
232
|
+
threadId: threadTs,
|
|
233
|
+
});
|
|
227
234
|
void this.#options.gateway.receive({
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
conversation,
|
|
236
|
+
message: { conversation, id: msg.ts },
|
|
230
237
|
content: formatInboundContent(msg),
|
|
231
|
-
sender: msg.user ?? msg.channel,
|
|
232
|
-
|
|
238
|
+
sender: { id: msg.user ?? msg.channel ?? '' },
|
|
239
|
+
endpointId: this.#options.config.id,
|
|
240
|
+
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
233
241
|
metadata: Object.freeze({
|
|
234
|
-
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
|
-
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
240
|
-
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
241
246
|
}),
|
|
242
247
|
}).catch((err) => {
|
|
243
|
-
logger.warn(formatCompact({
|
|
248
|
+
this.#logger.warn(formatCompact({
|
|
244
249
|
op: 'slack_gateway_receive_failed',
|
|
245
|
-
target:
|
|
250
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
246
251
|
error: err instanceof Error ? err.message : String(err),
|
|
247
252
|
}));
|
|
248
253
|
});
|
|
@@ -255,24 +260,29 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
255
260
|
const userId = payload.user.id;
|
|
256
261
|
const messageTs = payload.message?.ts ?? '';
|
|
257
262
|
if (payload.response_url) {
|
|
258
|
-
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
263
|
+
postSlackEphemeral(payload.response_url, '已收到', this.#logger);
|
|
259
264
|
}
|
|
265
|
+
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
266
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
267
|
+
channelId: channelId || userId,
|
|
268
|
+
// block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
|
|
269
|
+
channelType: channelId ? undefined : 'im',
|
|
270
|
+
});
|
|
260
271
|
void this.#options.gateway.receive({
|
|
261
|
-
|
|
262
|
-
|
|
272
|
+
conversation,
|
|
273
|
+
message: { conversation, id: actionTs },
|
|
263
274
|
content: formatInteractionContent(payload),
|
|
264
|
-
sender: userId,
|
|
265
|
-
|
|
275
|
+
sender: { id: userId },
|
|
276
|
+
endpointId: this.#options.config.id,
|
|
266
277
|
metadata: Object.freeze({
|
|
267
|
-
endpoint: this.#options.config.name,
|
|
268
278
|
eventType: 'block_actions',
|
|
269
279
|
actionId: payload.actions[0]?.action_id,
|
|
270
280
|
threadTs: messageTs || undefined,
|
|
271
281
|
}),
|
|
272
282
|
}).catch((err) => {
|
|
273
|
-
logger.warn(formatCompact({
|
|
283
|
+
this.#logger.warn(formatCompact({
|
|
274
284
|
op: 'slack_gateway_receive_failed',
|
|
275
|
-
target:
|
|
285
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
276
286
|
error: err instanceof Error ? err.message : String(err),
|
|
277
287
|
}));
|
|
278
288
|
});
|
|
@@ -280,22 +290,24 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
280
290
|
|
|
281
291
|
admitSlashCommand(cmd: SlackSlashCommand): void {
|
|
282
292
|
if (!this.#open) return;
|
|
283
|
-
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
293
|
+
postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
|
|
294
|
+
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
295
|
+
channelId: cmd.channel_id,
|
|
296
|
+
});
|
|
284
297
|
void this.#options.gateway.receive({
|
|
285
|
-
|
|
286
|
-
|
|
298
|
+
conversation,
|
|
299
|
+
message: { conversation, id: cmd.trigger_id },
|
|
287
300
|
content: formatSlashContent(cmd),
|
|
288
|
-
sender: cmd.user_id,
|
|
289
|
-
|
|
301
|
+
sender: { id: cmd.user_id, name: cmd.user_name },
|
|
302
|
+
endpointId: this.#options.config.id,
|
|
290
303
|
metadata: Object.freeze({
|
|
291
|
-
endpoint: this.#options.config.name,
|
|
292
304
|
eventType: 'slash_command',
|
|
293
305
|
command: cmd.command,
|
|
294
306
|
}),
|
|
295
307
|
}).catch((err) => {
|
|
296
|
-
logger.warn(formatCompact({
|
|
308
|
+
this.#logger.warn(formatCompact({
|
|
297
309
|
op: 'slack_gateway_receive_failed',
|
|
298
|
-
target:
|
|
310
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
299
311
|
error: err instanceof Error ? err.message : String(err),
|
|
300
312
|
}));
|
|
301
313
|
});
|
|
@@ -389,9 +401,9 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
389
401
|
return result.channel;
|
|
390
402
|
}
|
|
391
403
|
|
|
392
|
-
async getUserInfo(user: string): Promise<
|
|
404
|
+
async getUserInfo(user: string): Promise<SlackUserInfo | undefined> {
|
|
393
405
|
const result = await this.#client!.users.info({ user });
|
|
394
|
-
return result.user;
|
|
406
|
+
return result.user as SlackUserInfo | undefined;
|
|
395
407
|
}
|
|
396
408
|
|
|
397
409
|
async addReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
|
|
@@ -461,14 +473,6 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
461
473
|
}
|
|
462
474
|
}
|
|
463
475
|
|
|
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
476
|
/** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
|
|
473
477
|
const SLACK_LIST_PAGE_SIZE = 1000;
|
|
474
478
|
|
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,
|
|
@@ -48,16 +48,9 @@ export {
|
|
|
48
48
|
checkSlackPlatformPermit,
|
|
49
49
|
normalizeSlackSenderForPermit,
|
|
50
50
|
platformPermit,
|
|
51
|
-
registerSlackPlatformPermitChecker,
|
|
52
51
|
slackGroupPermitResolver,
|
|
53
52
|
} from './platform-permit.js';
|
|
54
53
|
|
|
55
|
-
export {
|
|
56
|
-
formatSlackMessageRef,
|
|
57
|
-
parseSlackMessageRef,
|
|
58
|
-
slackMessageTs,
|
|
59
|
-
} from './slack-message-ref.js';
|
|
60
|
-
|
|
61
54
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
62
55
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
63
56
|
export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slack platform permit — workspace + channel 权限
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'slack';
|
|
7
7
|
|
|
@@ -39,10 +39,9 @@ export function normalizeSlackSenderForPermit(input: {
|
|
|
39
39
|
return { role: 'member', permissions };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function checkSlackPlatformPermit(perm: string,
|
|
43
|
-
const
|
|
44
|
-
const permissions = sender
|
|
45
|
-
const role = sender.role;
|
|
42
|
+
export function checkSlackPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
43
|
+
const role = subject.sender?.role?.[0];
|
|
44
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
46
45
|
const has = (t: string) => permissions.includes(t);
|
|
47
46
|
|
|
48
47
|
switch (perm) {
|
|
@@ -58,6 +57,3 @@ export function checkSlackPlatformPermit(perm: string, message: Message<any>): b
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
export function registerSlackPlatformPermitChecker(): () => void {
|
|
62
|
-
return registerPlatformPermitChecker(ADAPTER, checkSlackPlatformPermit);
|
|
63
|
-
}
|
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
|
|
|
@@ -20,7 +21,7 @@ const MAX_TIMESTAMP_DRIFT_SECONDS = 300;
|
|
|
20
21
|
|
|
21
22
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
22
23
|
export interface SlackAdapterConfig {
|
|
23
|
-
readonly
|
|
24
|
+
readonly id?: string;
|
|
24
25
|
readonly token?: string;
|
|
25
26
|
readonly signingSecret?: string;
|
|
26
27
|
readonly appToken?: string;
|
|
@@ -41,7 +42,7 @@ export interface SlackAdapterConfig {
|
|
|
41
42
|
|
|
42
43
|
export interface ResolvedSlackConfig {
|
|
43
44
|
readonly context: 'slack';
|
|
44
|
-
readonly
|
|
45
|
+
readonly id: string;
|
|
45
46
|
readonly token: string;
|
|
46
47
|
readonly mode: 'socket' | 'http';
|
|
47
48
|
readonly signingSecret: string;
|
|
@@ -138,8 +139,8 @@ export function resolveSlackConfig(config: SlackAdapterConfig = {}): ResolvedSla
|
|
|
138
139
|
);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
const
|
|
142
|
-
|| (typeof entry?.
|
|
142
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
143
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
143
144
|
|| process.env.SLACK_BOT_NAME
|
|
144
145
|
|| 'slack-bot';
|
|
145
146
|
|
|
@@ -169,7 +170,7 @@ export function resolveSlackConfig(config: SlackAdapterConfig = {}): ResolvedSla
|
|
|
169
170
|
|
|
170
171
|
return {
|
|
171
172
|
context: 'slack',
|
|
172
|
-
|
|
173
|
+
id,
|
|
173
174
|
token,
|
|
174
175
|
mode,
|
|
175
176
|
signingSecret,
|
|
@@ -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
|
+
endpointKey: 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: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
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.
|
package/src/slack-agent-deps.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface SlackAgentEndpoint {
|
|
|
13
13
|
renameChannel(channel: string, name: string): Promise<boolean>;
|
|
14
14
|
getChannelMembers(channel: string): Promise<string[]>;
|
|
15
15
|
getChannelInfo(channel: string): Promise<unknown>;
|
|
16
|
-
getUserInfo(user: string): Promise<
|
|
16
|
+
getUserInfo(user: string): Promise<SlackUserInfo | undefined>;
|
|
17
17
|
addReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
18
18
|
removeReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
19
19
|
pinMessage(channel: string, timestamp: string): Promise<boolean>;
|
|
@@ -21,21 +21,35 @@ export interface SlackAgentEndpoint {
|
|
|
21
21
|
editMessage(channel: string, messageTs: string, content: unknown): Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Slack users.info 返回的用户对象(SDK 响应字段对 agent 工具的投影)。 */
|
|
25
|
+
export interface SlackUserInfo {
|
|
26
|
+
id?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
real_name?: string;
|
|
29
|
+
is_admin?: boolean;
|
|
30
|
+
is_bot?: boolean;
|
|
31
|
+
profile?: {
|
|
32
|
+
display_name?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
status_text?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
export interface SlackAgentDeps {
|
|
25
|
-
getEndpoint: (
|
|
39
|
+
getEndpoint: (endpointKey: string) => SlackAgentEndpoint;
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
const endpoints = new Map<string, SlackAgentEndpoint>();
|
|
29
43
|
let override: SlackAgentDeps | null = null;
|
|
30
44
|
|
|
31
45
|
export function registerSlackAgentEndpoint(
|
|
32
|
-
|
|
46
|
+
endpointKey: string,
|
|
33
47
|
endpoint: SlackAgentEndpoint,
|
|
34
48
|
): () => void {
|
|
35
|
-
endpoints.set(
|
|
49
|
+
endpoints.set(endpointKey, endpoint);
|
|
36
50
|
return () => {
|
|
37
|
-
if (endpoints.get(
|
|
38
|
-
endpoints.delete(
|
|
51
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
52
|
+
endpoints.delete(endpointKey);
|
|
39
53
|
}
|
|
40
54
|
};
|
|
41
55
|
}
|
|
@@ -48,9 +62,9 @@ export function setSlackAgentDeps(deps: SlackAgentDeps | null): void {
|
|
|
48
62
|
export function getSlackAgentDeps(): SlackAgentDeps {
|
|
49
63
|
if (override) return override;
|
|
50
64
|
return {
|
|
51
|
-
getEndpoint(
|
|
52
|
-
const registered = endpoints.get(
|
|
53
|
-
if (!registered) throw new Error(`Endpoint ${
|
|
65
|
+
getEndpoint(endpointKey: string): SlackAgentEndpoint {
|
|
66
|
+
const registered = endpoints.get(endpointKey);
|
|
67
|
+
if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
54
68
|
return registered;
|
|
55
69
|
},
|
|
56
70
|
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|