@zhin.js/adapter-slack 6.0.3 → 6.0.6
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 +90 -0
- package/README.md +1 -1
- 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 +2 -1
- package/lib/endpoint.js +24 -28
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +3 -3
- package/lib/protocol.js +5 -5
- package/lib/slack-agent-deps.d.ts +16 -3
- package/lib/slack-agent-deps.js +7 -7
- package/package.json +17 -16
- package/plugin.js +6 -4
- package/schema.json +41 -5
- package/src/endpoint.ts +30 -32
- package/src/index.ts +0 -1
- package/src/platform-permit.ts +4 -8
- package/src/protocol.ts +7 -7
- package/src/slack-agent-deps.ts +23 -9
- /package/commands/endpoint/add/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2d0a622]
|
|
8
|
+
- @zhin.js/command@1.0.10
|
|
9
|
+
- @zhin.js/adapter@1.1.7
|
|
10
|
+
- @zhin.js/core@1.5.5
|
|
11
|
+
- @zhin.js/agent@1.1.6
|
|
12
|
+
- zhin.js@6.0.5
|
|
13
|
+
|
|
14
|
+
## 6.0.5
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
19
|
+
|
|
20
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
21
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
22
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
23
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
24
|
+
PermissionHost integration.
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [6f9c366]
|
|
27
|
+
- Updated dependencies [373a56b]
|
|
28
|
+
- Updated dependencies [0de46a8]
|
|
29
|
+
- Updated dependencies [c106ecc]
|
|
30
|
+
- Updated dependencies [ca92e03]
|
|
31
|
+
- Updated dependencies [b0f37ae]
|
|
32
|
+
- Updated dependencies [ba08a2f]
|
|
33
|
+
- Updated dependencies [b08f7fe]
|
|
34
|
+
- Updated dependencies [daffd4c]
|
|
35
|
+
- Updated dependencies [36c7400]
|
|
36
|
+
- Updated dependencies [a9fa72e]
|
|
37
|
+
- Updated dependencies [c8de3ef]
|
|
38
|
+
- Updated dependencies [60f0fc8]
|
|
39
|
+
- Updated dependencies [574c990]
|
|
40
|
+
- Updated dependencies [d096f16]
|
|
41
|
+
- Updated dependencies [3f29623]
|
|
42
|
+
- Updated dependencies [2916852]
|
|
43
|
+
- Updated dependencies [d047869]
|
|
44
|
+
- Updated dependencies [162fa34]
|
|
45
|
+
- Updated dependencies [e62561e]
|
|
46
|
+
- Updated dependencies [3eeeb46]
|
|
47
|
+
- Updated dependencies [85d0f82]
|
|
48
|
+
- Updated dependencies [61bfc1c]
|
|
49
|
+
- Updated dependencies [04bad47]
|
|
50
|
+
- Updated dependencies [e40b048]
|
|
51
|
+
- Updated dependencies [5a5b1bb]
|
|
52
|
+
- Updated dependencies [f1708c3]
|
|
53
|
+
- Updated dependencies [d254a81]
|
|
54
|
+
- Updated dependencies [7123c47]
|
|
55
|
+
- Updated dependencies [05befc1]
|
|
56
|
+
- Updated dependencies [0663b6a]
|
|
57
|
+
- Updated dependencies [f28f9b3]
|
|
58
|
+
- Updated dependencies [9f340f7]
|
|
59
|
+
- Updated dependencies [e53444f]
|
|
60
|
+
- Updated dependencies [5eedd26]
|
|
61
|
+
- Updated dependencies [92b0dd7]
|
|
62
|
+
- Updated dependencies [f919b6f]
|
|
63
|
+
- Updated dependencies [098e411]
|
|
64
|
+
- Updated dependencies [e1b7c01]
|
|
65
|
+
- Updated dependencies [9b94f87]
|
|
66
|
+
- Updated dependencies [a7df753]
|
|
67
|
+
- @zhin.js/agent@1.1.5
|
|
68
|
+
- @zhin.js/host-http@1.0.8
|
|
69
|
+
- @zhin.js/permission@1.0.1
|
|
70
|
+
- @zhin.js/im-contract@1.0.3
|
|
71
|
+
- @zhin.js/adapter@1.1.7
|
|
72
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
73
|
+
- @zhin.js/command@1.0.9
|
|
74
|
+
- @zhin.js/core@1.5.4
|
|
75
|
+
- zhin.js@6.0.4
|
|
76
|
+
|
|
77
|
+
## 6.0.4
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- f8c7a54: fix: im
|
|
82
|
+
- Updated dependencies [f8c7a54]
|
|
83
|
+
- @zhin.js/logger@1.0.76
|
|
84
|
+
- @zhin.js/host-http@1.0.7
|
|
85
|
+
- @zhin.js/adapter@1.1.6
|
|
86
|
+
- @zhin.js/agent@1.1.4
|
|
87
|
+
- @zhin.js/command@1.0.8
|
|
88
|
+
- @zhin.js/core@1.5.3
|
|
89
|
+
- @zhin.js/im-contract@1.0.2
|
|
90
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
91
|
+
- zhin.js@6.0.3
|
|
92
|
+
|
|
3
93
|
## 6.0.3
|
|
4
94
|
|
|
5
95
|
### 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
|
|
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
|
@@ -3,6 +3,7 @@ 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 {
|
|
@@ -160,7 +161,7 @@ export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHand
|
|
|
160
161
|
renameChannel(channel: string, name: string): Promise<boolean>;
|
|
161
162
|
getChannelMembers(channel: string): Promise<string[]>;
|
|
162
163
|
getChannelInfo(channel: string): Promise<unknown>;
|
|
163
|
-
getUserInfo(user: string): Promise<
|
|
164
|
+
getUserInfo(user: string): Promise<SlackUserInfo | undefined>;
|
|
164
165
|
addReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
165
166
|
removeReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
|
|
166
167
|
pinMessage(channel: string, timestamp: string): Promise<boolean>;
|
package/lib/endpoint.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { SocketModeClient } from '@slack/socket-mode';
|
|
5
5
|
import { WebClient } from '@slack/web-api';
|
|
6
|
-
import { formatCompact,
|
|
6
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
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';
|
|
@@ -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,14 +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
106
|
async send({ conversation, payload }) {
|
|
110
107
|
if (!this.#client)
|
|
111
108
|
throw new Error('Slack client not connected');
|
|
112
109
|
const channel = conversation.id;
|
|
113
110
|
const threadTs = conversation.threadId;
|
|
114
|
-
const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, logger);
|
|
111
|
+
const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, this.#logger);
|
|
115
112
|
if (result.ts)
|
|
116
113
|
this.trackMessageChannel(result.ts, channel);
|
|
117
114
|
return formatSlackMessageRef(channel, result.ts || String(Date.now()));
|
|
@@ -138,18 +135,17 @@ export class SlackEndpoint {
|
|
|
138
135
|
conversation,
|
|
139
136
|
message: { conversation, id: msg.ts },
|
|
140
137
|
content: formatInboundContent(msg),
|
|
141
|
-
sender: msg.user ?? msg.channel,
|
|
138
|
+
sender: { id: msg.user ?? msg.channel ?? '' },
|
|
139
|
+
endpointId: this.#options.config.id,
|
|
140
|
+
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
142
141
|
metadata: Object.freeze({
|
|
143
|
-
endpoint: this.#options.config.name,
|
|
144
142
|
channelType: resolveSlackChannelType(msg),
|
|
145
143
|
userId: msg.user,
|
|
146
144
|
threadTs,
|
|
147
145
|
ts: msg.ts,
|
|
148
|
-
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
149
|
-
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
150
146
|
}),
|
|
151
147
|
}).catch((err) => {
|
|
152
|
-
logger.warn(formatCompact({
|
|
148
|
+
this.#logger.warn(formatCompact({
|
|
153
149
|
op: 'slack_gateway_receive_failed',
|
|
154
150
|
target: `${conversation.kind}:${conversation.id}`,
|
|
155
151
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -165,7 +161,7 @@ export class SlackEndpoint {
|
|
|
165
161
|
const userId = payload.user.id;
|
|
166
162
|
const messageTs = payload.message?.ts ?? '';
|
|
167
163
|
if (payload.response_url) {
|
|
168
|
-
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
164
|
+
postSlackEphemeral(payload.response_url, '已收到', this.#logger);
|
|
169
165
|
}
|
|
170
166
|
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
171
167
|
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
@@ -177,15 +173,15 @@ export class SlackEndpoint {
|
|
|
177
173
|
conversation,
|
|
178
174
|
message: { conversation, id: actionTs },
|
|
179
175
|
content: formatInteractionContent(payload),
|
|
180
|
-
sender: userId,
|
|
176
|
+
sender: { id: userId },
|
|
177
|
+
endpointId: this.#options.config.id,
|
|
181
178
|
metadata: Object.freeze({
|
|
182
|
-
endpoint: this.#options.config.name,
|
|
183
179
|
eventType: 'block_actions',
|
|
184
180
|
actionId: payload.actions[0]?.action_id,
|
|
185
181
|
threadTs: messageTs || undefined,
|
|
186
182
|
}),
|
|
187
183
|
}).catch((err) => {
|
|
188
|
-
logger.warn(formatCompact({
|
|
184
|
+
this.#logger.warn(formatCompact({
|
|
189
185
|
op: 'slack_gateway_receive_failed',
|
|
190
186
|
target: `${conversation.kind}:${conversation.id}`,
|
|
191
187
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -195,7 +191,7 @@ export class SlackEndpoint {
|
|
|
195
191
|
admitSlashCommand(cmd) {
|
|
196
192
|
if (!this.#open)
|
|
197
193
|
return;
|
|
198
|
-
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
194
|
+
postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
|
|
199
195
|
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
200
196
|
channelId: cmd.channel_id,
|
|
201
197
|
});
|
|
@@ -203,14 +199,14 @@ export class SlackEndpoint {
|
|
|
203
199
|
conversation,
|
|
204
200
|
message: { conversation, id: cmd.trigger_id },
|
|
205
201
|
content: formatSlashContent(cmd),
|
|
206
|
-
sender: cmd.user_id,
|
|
202
|
+
sender: { id: cmd.user_id, name: cmd.user_name },
|
|
203
|
+
endpointId: this.#options.config.id,
|
|
207
204
|
metadata: Object.freeze({
|
|
208
|
-
endpoint: this.#options.config.name,
|
|
209
205
|
eventType: 'slash_command',
|
|
210
206
|
command: cmd.command,
|
|
211
207
|
}),
|
|
212
208
|
}).catch((err) => {
|
|
213
|
-
logger.warn(formatCompact({
|
|
209
|
+
this.#logger.warn(formatCompact({
|
|
214
210
|
op: 'slack_gateway_receive_failed',
|
|
215
211
|
target: `${conversation.kind}:${conversation.id}`,
|
|
216
212
|
error: err instanceof Error ? err.message : String(err),
|
package/lib/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { formatInboundContent, formatInteractionContent, formatOutboundWire, for
|
|
|
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,
|
|
5
|
+
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, slackGroupPermitResolver, } from './platform-permit.js';
|
|
6
6
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
7
7
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
8
8
|
export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { formatInboundContent, formatInteractionContent, formatOutboundWire, for
|
|
|
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,
|
|
5
|
+
export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, slackGroupPermitResolver, } from './platform-permit.js';
|
|
6
6
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
7
7
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
|
8
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
|
@@ -2,7 +2,7 @@ import type { IncomingMessage } from 'node:http';
|
|
|
2
2
|
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
3
3
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
4
4
|
export interface SlackAdapterConfig {
|
|
5
|
-
readonly
|
|
5
|
+
readonly id?: string;
|
|
6
6
|
readonly token?: string;
|
|
7
7
|
readonly signingSecret?: string;
|
|
8
8
|
readonly appToken?: string;
|
|
@@ -22,7 +22,7 @@ export interface SlackAdapterConfig {
|
|
|
22
22
|
}
|
|
23
23
|
export interface ResolvedSlackConfig {
|
|
24
24
|
readonly context: 'slack';
|
|
25
|
-
readonly
|
|
25
|
+
readonly id: string;
|
|
26
26
|
readonly token: string;
|
|
27
27
|
readonly mode: 'socket' | 'http';
|
|
28
28
|
readonly signingSecret: string;
|
|
@@ -121,7 +121,7 @@ export declare function resolveSlackChannelType(event: Pick<SlackEvent, 'channel
|
|
|
121
121
|
* → group;交互/斜杠命令无 channel_type 时按平台 id 前缀推断(D* 为 DM)。Slack 无
|
|
122
122
|
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
123
123
|
*/
|
|
124
|
-
export declare function slackInboundConversation(
|
|
124
|
+
export declare function slackInboundConversation(endpointKey: string, opts: {
|
|
125
125
|
readonly channelId: string;
|
|
126
126
|
readonly channelType?: string;
|
|
127
127
|
readonly threadId?: string;
|
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,
|
|
@@ -68,12 +68,12 @@ export function resolveSlackChannelType(event) {
|
|
|
68
68
|
* → group;交互/斜杠命令无 channel_type 时按平台 id 前缀推断(D* 为 DM)。Slack 无
|
|
69
69
|
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
70
70
|
*/
|
|
71
|
-
export function slackInboundConversation(
|
|
71
|
+
export function slackInboundConversation(endpointKey, opts) {
|
|
72
72
|
const kind = opts.channelType != null
|
|
73
73
|
? (opts.channelType === 'im' ? 'private' : 'group')
|
|
74
74
|
: (opts.channelId.startsWith('D') ? 'private' : 'group');
|
|
75
75
|
return {
|
|
76
|
-
endpoint: { id:
|
|
76
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
77
77
|
kind,
|
|
78
78
|
id: opts.channelId,
|
|
79
79
|
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
@@ -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.6",
|
|
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,22 +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/im-contract": "1.0.
|
|
42
|
-
"@zhin.js/logger": "1.0.
|
|
43
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
37
|
+
"@zhin.js/adapter": "1.1.7",
|
|
38
|
+
"@zhin.js/command": "1.0.10",
|
|
39
|
+
"@zhin.js/core": "1.5.5",
|
|
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"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"zod": "^4.0.0",
|
|
47
|
-
"@zhin.js/adapter": "1.1.
|
|
48
|
-
"@zhin.js/agent": "1.1.
|
|
49
|
-
"@zhin.js/core": "1.5.
|
|
50
|
-
"@zhin.js/host-http": "1.0.
|
|
51
|
-
"@zhin.js/
|
|
52
|
-
"zhin.js": "
|
|
47
|
+
"@zhin.js/adapter": "1.1.7",
|
|
48
|
+
"@zhin.js/agent": "1.1.6",
|
|
49
|
+
"@zhin.js/core": "1.5.5",
|
|
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.5"
|
|
53
54
|
},
|
|
54
55
|
"peerDependenciesMeta": {
|
|
55
56
|
"zhin.js": {
|
|
@@ -67,8 +68,8 @@
|
|
|
67
68
|
"typescript": "^6.0.3",
|
|
68
69
|
"vitest": "^4.1.10",
|
|
69
70
|
"zod": "^4.4.3",
|
|
70
|
-
"@zhin.js/agent": "1.1.
|
|
71
|
-
"zhin.js": "6.0.
|
|
71
|
+
"@zhin.js/agent": "1.1.6",
|
|
72
|
+
"zhin.js": "6.0.5"
|
|
72
73
|
},
|
|
73
74
|
"files": [
|
|
74
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
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
} from '@zhin.js/adapter';
|
|
13
13
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
14
14
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
15
|
-
import { formatCompact,
|
|
15
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
16
16
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
17
17
|
import {
|
|
18
18
|
formatInboundContent,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
type SlackMessageEvent,
|
|
28
28
|
type SlackSlashCommand,
|
|
29
29
|
} from './protocol.js';
|
|
30
|
-
import { registerSlackAgentEndpoint } from './slack-agent-deps.js';
|
|
30
|
+
import { registerSlackAgentEndpoint, type SlackUserInfo } from './slack-agent-deps.js';
|
|
31
31
|
import {
|
|
32
32
|
createSlackInboundFilterState,
|
|
33
33
|
shouldDropSlackInboundMessage,
|
|
@@ -38,8 +38,6 @@ import { postSlackEphemeral } from './slack-response-url.js';
|
|
|
38
38
|
import { editSlackContent, sendSlackContent, type SlackChatClient } from './slack-outbound.js';
|
|
39
39
|
import { registerSlackWebhookRoutes, type SlackWebhookHandler } from './webhook.js';
|
|
40
40
|
|
|
41
|
-
const logger = getLogger('slack');
|
|
42
|
-
|
|
43
41
|
export interface SlackSocketLike {
|
|
44
42
|
on(
|
|
45
43
|
event: string,
|
|
@@ -103,6 +101,8 @@ export interface SlackEndpointOptions {
|
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
104
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
105
|
+
|
|
106
106
|
readonly #options: SlackEndpointOptions;
|
|
107
107
|
readonly #inboundFilter = createSlackInboundFilterState();
|
|
108
108
|
readonly #messageChannelMap = new Map<string, string>();
|
|
@@ -116,12 +116,13 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
116
116
|
readonly management: EndpointManagement = createSlackEndpointManagement(this);
|
|
117
117
|
|
|
118
118
|
constructor(options: SlackEndpointOptions) {
|
|
119
|
+
this.#logger = getAdapterLogger('slack', options.config.id);
|
|
119
120
|
this.#options = options;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/** Console 展示 / AdapterIndex live name(多 endpoint 时与 entry name 一致)。 */
|
|
123
124
|
get name(): string {
|
|
124
|
-
return this.#options.config.
|
|
125
|
+
return this.#options.config.id;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
get client(): SlackWebClientLike | undefined {
|
|
@@ -144,7 +145,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
144
145
|
this.#client = this.#options.createClient?.(config.token)
|
|
145
146
|
?? (new WebClient(config.token) as unknown as SlackWebClientLike);
|
|
146
147
|
|
|
147
|
-
this.#unregisterAgent = registerSlackAgentEndpoint(config.
|
|
148
|
+
this.#unregisterAgent = registerSlackAgentEndpoint(config.id, this);
|
|
148
149
|
|
|
149
150
|
if (config.mode === 'socket') {
|
|
150
151
|
await this.#startSocket();
|
|
@@ -154,8 +155,8 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
154
155
|
throw new Error('Slack HTTP Events API requires httpHostToken (Runtime Host)');
|
|
155
156
|
}
|
|
156
157
|
this.#routeReleases.push(...registerSlackWebhookRoutes(http, this));
|
|
157
|
-
logger.debug(formatCompact({
|
|
158
|
-
endpoint: config.
|
|
158
|
+
this.#logger.debug(formatCompact({
|
|
159
|
+
endpoint: config.id,
|
|
159
160
|
op: 'webhook',
|
|
160
161
|
path: config.webhookPath,
|
|
161
162
|
}));
|
|
@@ -164,15 +165,13 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
164
165
|
const authTest = await this.#client.auth.test();
|
|
165
166
|
if (authTest.user_id) this.#botUserId = String(authTest.user_id);
|
|
166
167
|
|
|
167
|
-
logger.info(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
platform_user_id: this.#botUserId,
|
|
172
|
-
}));
|
|
168
|
+
this.#logger.info(
|
|
169
|
+
`connected (${config.mode})`
|
|
170
|
+
+ (this.#botUserId ? ` | user: ${this.#botUserId}` : ''),
|
|
171
|
+
);
|
|
173
172
|
} catch (error) {
|
|
174
173
|
await this.stop();
|
|
175
|
-
logger.error('Failed to connect Slack endpoint:', error);
|
|
174
|
+
this.#logger.error('Failed to connect Slack endpoint:', error);
|
|
176
175
|
throw error;
|
|
177
176
|
}
|
|
178
177
|
}
|
|
@@ -200,7 +199,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
200
199
|
this.#unregisterAgent = undefined;
|
|
201
200
|
this.#client = undefined;
|
|
202
201
|
this.#started = false;
|
|
203
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
202
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
@@ -211,7 +210,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
211
210
|
this.#client,
|
|
212
211
|
payload,
|
|
213
212
|
{ channel, threadTs },
|
|
214
|
-
logger,
|
|
213
|
+
this.#logger,
|
|
215
214
|
);
|
|
216
215
|
if (result.ts) this.trackMessageChannel(result.ts, channel);
|
|
217
216
|
return formatSlackMessageRef(channel, result.ts || String(Date.now()));
|
|
@@ -236,18 +235,17 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
236
235
|
conversation,
|
|
237
236
|
message: { conversation, id: msg.ts },
|
|
238
237
|
content: formatInboundContent(msg),
|
|
239
|
-
sender: msg.user ?? msg.channel,
|
|
238
|
+
sender: { id: msg.user ?? msg.channel ?? '' },
|
|
239
|
+
endpointId: this.#options.config.id,
|
|
240
|
+
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
240
241
|
metadata: Object.freeze({
|
|
241
|
-
endpoint: this.#options.config.name,
|
|
242
242
|
channelType: resolveSlackChannelType(msg),
|
|
243
243
|
userId: msg.user,
|
|
244
244
|
threadTs,
|
|
245
245
|
ts: msg.ts,
|
|
246
|
-
// app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
247
|
-
...(msg.type === 'app_mention' ? { mentioned: true } : {}),
|
|
248
246
|
}),
|
|
249
247
|
}).catch((err) => {
|
|
250
|
-
logger.warn(formatCompact({
|
|
248
|
+
this.#logger.warn(formatCompact({
|
|
251
249
|
op: 'slack_gateway_receive_failed',
|
|
252
250
|
target: `${conversation.kind}:${conversation.id}`,
|
|
253
251
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -262,7 +260,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
262
260
|
const userId = payload.user.id;
|
|
263
261
|
const messageTs = payload.message?.ts ?? '';
|
|
264
262
|
if (payload.response_url) {
|
|
265
|
-
postSlackEphemeral(payload.response_url, '已收到', logger);
|
|
263
|
+
postSlackEphemeral(payload.response_url, '已收到', this.#logger);
|
|
266
264
|
}
|
|
267
265
|
const actionTs = payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`;
|
|
268
266
|
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
@@ -274,15 +272,15 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
274
272
|
conversation,
|
|
275
273
|
message: { conversation, id: actionTs },
|
|
276
274
|
content: formatInteractionContent(payload),
|
|
277
|
-
sender: userId,
|
|
275
|
+
sender: { id: userId },
|
|
276
|
+
endpointId: this.#options.config.id,
|
|
278
277
|
metadata: Object.freeze({
|
|
279
|
-
endpoint: this.#options.config.name,
|
|
280
278
|
eventType: 'block_actions',
|
|
281
279
|
actionId: payload.actions[0]?.action_id,
|
|
282
280
|
threadTs: messageTs || undefined,
|
|
283
281
|
}),
|
|
284
282
|
}).catch((err) => {
|
|
285
|
-
logger.warn(formatCompact({
|
|
283
|
+
this.#logger.warn(formatCompact({
|
|
286
284
|
op: 'slack_gateway_receive_failed',
|
|
287
285
|
target: `${conversation.kind}:${conversation.id}`,
|
|
288
286
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -292,7 +290,7 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
292
290
|
|
|
293
291
|
admitSlashCommand(cmd: SlackSlashCommand): void {
|
|
294
292
|
if (!this.#open) return;
|
|
295
|
-
postSlackEphemeral(cmd.response_url, '处理中…', logger);
|
|
293
|
+
postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
|
|
296
294
|
const conversation = slackInboundConversation(String(this.#options.id), {
|
|
297
295
|
channelId: cmd.channel_id,
|
|
298
296
|
});
|
|
@@ -300,14 +298,14 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
300
298
|
conversation,
|
|
301
299
|
message: { conversation, id: cmd.trigger_id },
|
|
302
300
|
content: formatSlashContent(cmd),
|
|
303
|
-
sender: cmd.user_id,
|
|
301
|
+
sender: { id: cmd.user_id, name: cmd.user_name },
|
|
302
|
+
endpointId: this.#options.config.id,
|
|
304
303
|
metadata: Object.freeze({
|
|
305
|
-
endpoint: this.#options.config.name,
|
|
306
304
|
eventType: 'slash_command',
|
|
307
305
|
command: cmd.command,
|
|
308
306
|
}),
|
|
309
307
|
}).catch((err) => {
|
|
310
|
-
logger.warn(formatCompact({
|
|
308
|
+
this.#logger.warn(formatCompact({
|
|
311
309
|
op: 'slack_gateway_receive_failed',
|
|
312
310
|
target: `${conversation.kind}:${conversation.id}`,
|
|
313
311
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -403,9 +401,9 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
|
|
|
403
401
|
return result.channel;
|
|
404
402
|
}
|
|
405
403
|
|
|
406
|
-
async getUserInfo(user: string): Promise<
|
|
404
|
+
async getUserInfo(user: string): Promise<SlackUserInfo | undefined> {
|
|
407
405
|
const result = await this.#client!.users.info({ user });
|
|
408
|
-
return result.user;
|
|
406
|
+
return result.user as SlackUserInfo | undefined;
|
|
409
407
|
}
|
|
410
408
|
|
|
411
409
|
async addReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
|
package/src/index.ts
CHANGED
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
|
@@ -21,7 +21,7 @@ const MAX_TIMESTAMP_DRIFT_SECONDS = 300;
|
|
|
21
21
|
|
|
22
22
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
23
23
|
export interface SlackAdapterConfig {
|
|
24
|
-
readonly
|
|
24
|
+
readonly id?: string;
|
|
25
25
|
readonly token?: string;
|
|
26
26
|
readonly signingSecret?: string;
|
|
27
27
|
readonly appToken?: string;
|
|
@@ -42,7 +42,7 @@ export interface SlackAdapterConfig {
|
|
|
42
42
|
|
|
43
43
|
export interface ResolvedSlackConfig {
|
|
44
44
|
readonly context: 'slack';
|
|
45
|
-
readonly
|
|
45
|
+
readonly id: string;
|
|
46
46
|
readonly token: string;
|
|
47
47
|
readonly mode: 'socket' | 'http';
|
|
48
48
|
readonly signingSecret: string;
|
|
@@ -139,8 +139,8 @@ export function resolveSlackConfig(config: SlackAdapterConfig = {}): ResolvedSla
|
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
const
|
|
143
|
-
|| (typeof entry?.
|
|
142
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
143
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
144
144
|
|| process.env.SLACK_BOT_NAME
|
|
145
145
|
|| 'slack-bot';
|
|
146
146
|
|
|
@@ -170,7 +170,7 @@ export function resolveSlackConfig(config: SlackAdapterConfig = {}): ResolvedSla
|
|
|
170
170
|
|
|
171
171
|
return {
|
|
172
172
|
context: 'slack',
|
|
173
|
-
|
|
173
|
+
id,
|
|
174
174
|
token,
|
|
175
175
|
mode,
|
|
176
176
|
signingSecret,
|
|
@@ -199,7 +199,7 @@ export function resolveSlackChannelType(event: Pick<SlackEvent, 'channel_type'>)
|
|
|
199
199
|
* guild 容器概念(team 为 workspace 级,不进 parent);线程根 ts 进 `threadId`。
|
|
200
200
|
*/
|
|
201
201
|
export function slackInboundConversation(
|
|
202
|
-
|
|
202
|
+
endpointKey: string,
|
|
203
203
|
opts: {
|
|
204
204
|
readonly channelId: string;
|
|
205
205
|
readonly channelType?: string;
|
|
@@ -210,7 +210,7 @@ export function slackInboundConversation(
|
|
|
210
210
|
? (opts.channelType === 'im' ? 'private' : 'group')
|
|
211
211
|
: (opts.channelId.startsWith('D') ? 'private' : 'group');
|
|
212
212
|
return {
|
|
213
|
-
endpoint: { id:
|
|
213
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
214
214
|
kind,
|
|
215
215
|
id: opts.channelId,
|
|
216
216
|
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
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
|