@zhin.js/adapter-slack 6.0.14 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/endpoint.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * SlackEndpoint — lifecycle, outbound, admit, Socket Mode, agent tool surface.
3
4
  */
@@ -5,7 +6,6 @@ import { SocketModeClient } from '@slack/socket-mode';
5
6
  import { WebClient } from '@slack/web-api';
6
7
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
8
  import { formatInboundContent, formatInteractionContent, formatSlashContent, resolveSlackChannelType, slackInboundConversation, } from './protocol.js';
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';
11
11
  import { normalizeSlackReactionName } from './slack-reaction.js';
@@ -13,7 +13,7 @@ import { postSlackEphemeral } from './slack-response-url.js';
13
13
  import { editSlackContent, sendSlackContent } from './slack-outbound.js';
14
14
  import { registerSlackWebhookRoutes } from './webhook.js';
15
15
  import { receiveSlackSideEvent } from './side-event-dispatch.js';
16
- export class SlackEndpoint {
16
+ export class SlackEndpoint extends Endpoint {
17
17
  #logger;
18
18
  #options;
19
19
  #inboundFilter = createSlackInboundFilterState();
@@ -24,9 +24,38 @@ export class SlackEndpoint {
24
24
  #botUserId;
25
25
  #open = false;
26
26
  #started = false;
27
- #unregisterAgent;
28
- management = createSlackEndpointManagement(this);
27
+ management = createSlackEndpointManagement(() => this.client);
28
+ control = Object.freeze({
29
+ recall: async (message) => {
30
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
31
+ if (!ref || !this.#client)
32
+ return;
33
+ await this.#client.chat.delete(ref);
34
+ },
35
+ edit: async (message, content) => {
36
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
37
+ if (!ref)
38
+ return null;
39
+ await this.#editMessage(ref.channel, ref.ts, content);
40
+ return message.id;
41
+ },
42
+ addReaction: async (message, emoji) => {
43
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
44
+ if (!ref)
45
+ return null;
46
+ const reaction = normalizeSlackReactionName(emoji);
47
+ await this.#addReaction(ref.channel, ref.ts, reaction);
48
+ return reaction;
49
+ },
50
+ removeReaction: async (message, reactionId) => {
51
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
52
+ if (!ref)
53
+ return;
54
+ await this.#removeReaction(ref.channel, ref.ts, reactionId);
55
+ },
56
+ });
29
57
  constructor(options) {
58
+ super();
30
59
  this.#logger = getAdapterLogger('slack', options.config.id);
31
60
  this.#options = options;
32
61
  }
@@ -35,6 +64,8 @@ export class SlackEndpoint {
35
64
  return this.#options.config.id;
36
65
  }
37
66
  get client() {
67
+ if (!this.#client)
68
+ throw new Error('Slack client not connected');
38
69
  return this.#client;
39
70
  }
40
71
  get platformUserId() {
@@ -51,7 +82,6 @@ export class SlackEndpoint {
51
82
  const { config } = this.#options;
52
83
  this.#client = this.#options.createClient?.(config.token)
53
84
  ?? new WebClient(config.token);
54
- this.#unregisterAgent = registerSlackAgentEndpoint(config.id, this);
55
85
  if (config.mode === 'socket') {
56
86
  await this.#startSocket();
57
87
  }
@@ -98,8 +128,6 @@ export class SlackEndpoint {
98
128
  }
99
129
  for (const release of this.#routeReleases.splice(0))
100
130
  release();
101
- this.#unregisterAgent?.();
102
- this.#unregisterAgent = undefined;
103
131
  this.#client = undefined;
104
132
  this.#started = false;
105
133
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
@@ -118,6 +146,7 @@ export class SlackEndpoint {
118
146
  admit(event) {
119
147
  if (!this.#open)
120
148
  return;
149
+ void this.#emitPlatformEvent(event.type || 'event', event);
121
150
  if (event.type !== 'message' && event.type !== 'app_mention')
122
151
  return;
123
152
  const msg = event;
@@ -132,7 +161,7 @@ export class SlackEndpoint {
132
161
  channelType: msg.channel_type,
133
162
  threadId: threadTs,
134
163
  });
135
- void this.#options.gateway.receive({
164
+ void this.emit('message.receive', {
136
165
  conversation,
137
166
  message: { conversation, id: msg.ts },
138
167
  content: formatInboundContent(msg),
@@ -156,6 +185,7 @@ export class SlackEndpoint {
156
185
  admitInteraction(payload) {
157
186
  if (!this.#open)
158
187
  return;
188
+ void this.#emitPlatformEvent(`interaction.${payload.type}`, payload);
159
189
  if (payload.type !== 'block_actions' || !payload.actions?.length)
160
190
  return;
161
191
  const channelId = payload.channel?.id ?? '';
@@ -170,7 +200,7 @@ export class SlackEndpoint {
170
200
  // block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
171
201
  channelType: channelId ? undefined : 'im',
172
202
  });
173
- void this.#options.gateway.receive({
203
+ void this.emit('message.receive', {
174
204
  conversation,
175
205
  message: { conversation, id: actionTs },
176
206
  content: formatInteractionContent(payload),
@@ -192,11 +222,12 @@ export class SlackEndpoint {
192
222
  admitSlashCommand(cmd) {
193
223
  if (!this.#open)
194
224
  return;
225
+ void this.#emitPlatformEvent(`slash.${cmd.command}`, cmd);
195
226
  postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
196
227
  const conversation = slackInboundConversation(String(this.#options.id), {
197
228
  channelId: cmd.channel_id,
198
229
  });
199
- void this.#options.gateway.receive({
230
+ void this.emit('message.receive', {
200
231
  conversation,
201
232
  message: { conversation, id: cmd.trigger_id },
202
233
  content: formatSlashContent(cmd),
@@ -219,7 +250,8 @@ export class SlackEndpoint {
219
250
  if (envelope?.type === 'event_callback' && envelope.event) {
220
251
  const event = envelope.event;
221
252
  if (event.type !== 'message' && event.type !== 'app_mention') {
222
- receiveSlackSideEvent(this.#options.sideEvents, String(this.#options.id), this.#options.config.id, event, this.#logger);
253
+ void this.#emitPlatformEvent(event.type || 'event', event);
254
+ receiveSlackSideEvent((name, payload) => this.emit(name, payload), String(this.#options.id), this.#options.config.id, event, this.#logger);
223
255
  return;
224
256
  }
225
257
  this.admit(event);
@@ -257,85 +289,43 @@ export class SlackEndpoint {
257
289
  return;
258
290
  await this.#client.chat.delete({ channel: ref.channel, ts: ref.ts });
259
291
  }
260
- async editMessage(channel, messageTs, content) {
292
+ async #editMessage(channel, messageTs, content) {
261
293
  if (!this.#client)
262
294
  throw new Error('Slack client not connected');
263
295
  await editSlackContent(this.#client, channel, messageTs, content);
264
296
  }
265
- // ── Agent tool surface ──────────────────────────────────────────────
266
- async inviteToChannel(channel, users) {
267
- await this.#client.conversations.invite({ channel, users: users.join(',') });
268
- return true;
269
- }
270
- async kickFromChannel(channel, user) {
271
- await this.#client.conversations.kick({ channel, user });
272
- return true;
273
- }
274
- async setChannelTopic(channel, topic) {
275
- await this.#client.conversations.setTopic({ channel, topic });
276
- return true;
277
- }
278
- async setChannelPurpose(channel, purpose) {
279
- await this.#client.conversations.setPurpose({ channel, purpose });
280
- return true;
281
- }
282
- async archiveChannel(channel) {
283
- await this.#client.conversations.archive({ channel });
284
- return true;
285
- }
286
- async unarchiveChannel(channel) {
287
- await this.#client.conversations.unarchive({ channel });
288
- return true;
289
- }
290
- async renameChannel(channel, name) {
291
- await this.#client.conversations.rename({ channel, name });
292
- return true;
293
- }
294
- async getChannelMembers(channel) {
295
- const result = await this.#client.conversations.members({ channel });
296
- return result.members || [];
297
- }
298
- async getChannelInfo(channel) {
299
- const result = await this.#client.conversations.info({ channel });
300
- return result.channel;
301
- }
302
- async getUserInfo(user) {
303
- const result = await this.#client.users.info({ user });
304
- return result.user;
305
- }
306
- async addReaction(channel, timestamp, name) {
297
+ async #addReaction(channel, timestamp, name) {
307
298
  const reaction = normalizeSlackReactionName(name);
308
299
  try {
309
300
  await this.#client.reactions.add({ channel, timestamp, name: reaction });
310
- return true;
311
301
  }
312
302
  catch (error) {
313
303
  const code = error?.data?.error;
314
304
  if (code === 'already_reacted')
315
- return true;
305
+ return;
316
306
  throw error;
317
307
  }
318
308
  }
319
- async removeReaction(channel, timestamp, name) {
309
+ async #removeReaction(channel, timestamp, name) {
320
310
  const reaction = normalizeSlackReactionName(name);
321
311
  try {
322
312
  await this.#client.reactions.remove({ channel, timestamp, name: reaction });
323
- return true;
324
313
  }
325
314
  catch (error) {
326
315
  const code = error?.data?.error;
327
316
  if (code === 'no_reaction')
328
- return true;
317
+ return;
329
318
  throw error;
330
319
  }
331
320
  }
332
- async pinMessage(channel, timestamp) {
333
- await this.#client.pins.add({ channel, timestamp });
334
- return true;
335
- }
336
- async unpinMessage(channel, timestamp) {
337
- await this.#client.pins.remove({ channel, timestamp });
338
- return true;
321
+ async #emitPlatformEvent(name, event) {
322
+ await this.emitPlatform(name, event).catch((error) => {
323
+ this.#logger.warn(formatCompact({
324
+ op: 'slack_platform_event_failed',
325
+ event: name,
326
+ error: error instanceof Error ? error.message : String(error),
327
+ }));
328
+ });
339
329
  }
340
330
  async #startSocket() {
341
331
  const { config } = this.#options;
@@ -369,13 +359,7 @@ export class SlackEndpoint {
369
359
  }
370
360
  /** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
371
361
  const SLACK_LIST_PAGE_SIZE = 1000;
372
- function createSlackEndpointManagement(endpoint) {
373
- const requireClient = () => {
374
- const client = endpoint.client;
375
- if (!client)
376
- throw new Error('Slack client not connected');
377
- return client;
378
- };
362
+ function createSlackEndpointManagement(requireClient) {
379
363
  return Object.freeze({
380
364
  // Slack 无"群"概念:public channel 归一为 group(channel 语义由 listChannels 之外省略,
381
365
  // Slack channel 本身就是会话载体,避免同一批数据在两个列表里重复)。
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
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
- export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, type SlackAgentDeps, type SlackAgentEndpoint, } from './slack-agent-deps.js';
4
+ export { slackClient, type SlackClientEventMap, type SlackUserInfo, } from './client.js';
5
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';
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
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
- export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, } from './slack-agent-deps.js';
4
+ export { slackClient, } from './client.js';
5
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';
package/lib/protocol.d.ts CHANGED
@@ -126,7 +126,7 @@ export declare function slackInboundConversation(endpointKey: string, opts: {
126
126
  readonly channelType?: string;
127
127
  readonly threadId?: string;
128
128
  }): ConversationRef;
129
- /** Build inbound text for MessageGateway.receive. */
129
+ /** Build inbound text for OutboundMessageService.receive. */
130
130
  export declare function formatInboundContent(event: SlackMessageEvent | SlackEvent): string;
131
131
  export declare function formatInteractionContent(payload: SlackInteractionPayload): string;
132
132
  export declare function formatSlashContent(cmd: SlackSlashCommand): string;
package/lib/protocol.js CHANGED
@@ -79,7 +79,7 @@ export function slackInboundConversation(endpointKey, opts) {
79
79
  ...(opts.threadId ? { threadId: opts.threadId } : {}),
80
80
  };
81
81
  }
82
- /** Build inbound text for MessageGateway.receive. */
82
+ /** Build inbound text for OutboundMessageService.receive. */
83
83
  export function formatInboundContent(event) {
84
84
  const text = typeof event.text === 'string' ? event.text : '';
85
85
  if (text)
@@ -1,4 +1,4 @@
1
- import type { SideEventGateway } from '@zhin.js/core/runtime';
1
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
2
2
  import { type getAdapterLogger } from '@zhin.js/logger';
3
3
  import type { SlackEvent } from './protocol.js';
4
- export declare function receiveSlackSideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, configId: string, event: SlackEvent, logger: ReturnType<typeof getAdapterLogger>): void;
4
+ export declare function receiveSlackSideEvent(emit: EndpointEventEmitter, endpointKey: string, configId: string, event: SlackEvent, logger: ReturnType<typeof getAdapterLogger>): void;
@@ -1,7 +1,7 @@
1
1
  import { buildNotice, mapNoticeParts, senderFromId, SLACK_NOTICE_PARTS_MAP } from '@zhin.js/core';
2
2
  import { formatCompact } from '@zhin.js/logger';
3
- export function receiveSlackSideEvent(sideEvents, endpointKey, configId, event, logger) {
4
- if (!sideEvents)
3
+ export function receiveSlackSideEvent(emit, endpointKey, configId, event, logger) {
4
+ if (!emit)
5
5
  return;
6
6
  const eventType = String(event.type ?? '');
7
7
  if (!Object.prototype.hasOwnProperty.call(SLACK_NOTICE_PARTS_MAP, eventType))
@@ -17,7 +17,7 @@ export function receiveSlackSideEvent(sideEvents, endpointKey, configId, event,
17
17
  user ?? '',
18
18
  String(record.ts ?? record.event_ts ?? ''),
19
19
  ].join(':');
20
- void sideEvents.receiveNotice(buildNotice(record, {
20
+ void emit('notice.receive', buildNotice(record, {
21
21
  $id: `slack:${dedupeKey}`,
22
22
  $adapter: 'slack',
23
23
  $endpoint: configId,
@@ -1 +1 @@
1
- export declare const slackEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage>>>;
1
+ export declare const slackEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-slack",
3
- "version": "6.0.14",
3
+ "version": "7.0.1",
4
4
  "description": "Zhin.js Slack adapter for Plugin Runtime (Socket Mode / HTTP Events)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,21 +34,22 @@
34
34
  "dependencies": {
35
35
  "@slack/socket-mode": "^2.0.7",
36
36
  "@slack/web-api": "^7.19.0",
37
- "@zhin.js/adapter": "1.1.11",
38
- "@zhin.js/core": "1.5.12",
39
- "@zhin.js/host-http": "1.0.11",
37
+ "@zhin.js/adapter": "1.2.1",
38
+ "@zhin.js/core": "1.5.14",
39
+ "@zhin.js/feature-kit": "1.0.13",
40
+ "@zhin.js/host-http": "1.0.13",
40
41
  "@zhin.js/im-contract": "1.0.4",
41
- "@zhin.js/logger": "1.0.76"
42
+ "@zhin.js/logger": "1.0.77"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.11",
46
- "@zhin.js/agent": "1.1.14",
47
- "@zhin.js/command": "1.0.15",
48
- "@zhin.js/core": "1.5.12",
49
- "@zhin.js/host-http": "1.0.11",
50
- "@zhin.js/permission": "1.0.3",
51
- "zhin.js": "6.0.12"
46
+ "@zhin.js/adapter": "1.2.1",
47
+ "@zhin.js/agent": "1.1.16",
48
+ "@zhin.js/command": "1.0.16",
49
+ "@zhin.js/core": "1.5.14",
50
+ "@zhin.js/host-http": "1.0.13",
51
+ "@zhin.js/permission": "1.0.4",
52
+ "zhin.js": "6.0.14"
52
53
  },
53
54
  "peerDependenciesMeta": {
54
55
  "@zhin.js/agent": {
@@ -69,8 +70,8 @@
69
70
  "typescript": "^6.0.3",
70
71
  "vitest": "^4.1.10",
71
72
  "zod": "^4.4.3",
72
- "@zhin.js/agent": "1.1.14",
73
- "zhin.js": "6.0.12"
73
+ "@zhin.js/agent": "1.1.16",
74
+ "zhin.js": "6.0.14"
74
75
  },
75
76
  "files": [
76
77
  "adapters",
package/src/client.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ import type { SlackWebClientLike } from './endpoint.js';
3
+ import type { SlackEvent } from './protocol.js';
4
+
5
+ /** Slack users.info response fields projected by the bundled tools. */
6
+ export interface SlackUserInfo {
7
+ id?: string;
8
+ name?: string;
9
+ real_name?: string;
10
+ is_admin?: boolean;
11
+ is_bot?: boolean;
12
+ profile?: {
13
+ display_name?: string;
14
+ email?: string;
15
+ status_text?: string;
16
+ };
17
+ }
18
+
19
+ export type SlackClientEventMap = Record<string, SlackEvent>;
20
+
21
+ declare module '@zhin.js/feature-kit' {
22
+ interface AdapterClientRegistry {
23
+ readonly slack: {
24
+ readonly client: SlackWebClientLike;
25
+ readonly events: SlackClientEventMap;
26
+ };
27
+ }
28
+ }
29
+
30
+ export const slackClient = defineEndpointClient<SlackWebClientLike, SlackClientEventMap>('slack');