@zhin.js/adapter-slack 7.0.0 → 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,8 +24,7 @@ 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);
29
28
  control = Object.freeze({
30
29
  recall: async (message) => {
31
30
  const ref = this.resolveMessageRef(message.id, message.conversation.id);
@@ -37,7 +36,7 @@ export class SlackEndpoint {
37
36
  const ref = this.resolveMessageRef(message.id, message.conversation.id);
38
37
  if (!ref)
39
38
  return null;
40
- await this.editMessage(ref.channel, ref.ts, content);
39
+ await this.#editMessage(ref.channel, ref.ts, content);
41
40
  return message.id;
42
41
  },
43
42
  addReaction: async (message, emoji) => {
@@ -45,17 +44,18 @@ export class SlackEndpoint {
45
44
  if (!ref)
46
45
  return null;
47
46
  const reaction = normalizeSlackReactionName(emoji);
48
- await this.addReaction(ref.channel, ref.ts, reaction);
47
+ await this.#addReaction(ref.channel, ref.ts, reaction);
49
48
  return reaction;
50
49
  },
51
50
  removeReaction: async (message, reactionId) => {
52
51
  const ref = this.resolveMessageRef(message.id, message.conversation.id);
53
52
  if (!ref)
54
53
  return;
55
- await this.removeReaction(ref.channel, ref.ts, reactionId);
54
+ await this.#removeReaction(ref.channel, ref.ts, reactionId);
56
55
  },
57
56
  });
58
57
  constructor(options) {
58
+ super();
59
59
  this.#logger = getAdapterLogger('slack', options.config.id);
60
60
  this.#options = options;
61
61
  }
@@ -64,6 +64,8 @@ export class SlackEndpoint {
64
64
  return this.#options.config.id;
65
65
  }
66
66
  get client() {
67
+ if (!this.#client)
68
+ throw new Error('Slack client not connected');
67
69
  return this.#client;
68
70
  }
69
71
  get platformUserId() {
@@ -80,7 +82,6 @@ export class SlackEndpoint {
80
82
  const { config } = this.#options;
81
83
  this.#client = this.#options.createClient?.(config.token)
82
84
  ?? new WebClient(config.token);
83
- this.#unregisterAgent = registerSlackAgentEndpoint(config.id, this);
84
85
  if (config.mode === 'socket') {
85
86
  await this.#startSocket();
86
87
  }
@@ -127,8 +128,6 @@ export class SlackEndpoint {
127
128
  }
128
129
  for (const release of this.#routeReleases.splice(0))
129
130
  release();
130
- this.#unregisterAgent?.();
131
- this.#unregisterAgent = undefined;
132
131
  this.#client = undefined;
133
132
  this.#started = false;
134
133
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
@@ -147,6 +146,7 @@ export class SlackEndpoint {
147
146
  admit(event) {
148
147
  if (!this.#open)
149
148
  return;
149
+ void this.#emitPlatformEvent(event.type || 'event', event);
150
150
  if (event.type !== 'message' && event.type !== 'app_mention')
151
151
  return;
152
152
  const msg = event;
@@ -161,7 +161,7 @@ export class SlackEndpoint {
161
161
  channelType: msg.channel_type,
162
162
  threadId: threadTs,
163
163
  });
164
- void this.#options.gateway.receive({
164
+ void this.emit('message.receive', {
165
165
  conversation,
166
166
  message: { conversation, id: msg.ts },
167
167
  content: formatInboundContent(msg),
@@ -185,6 +185,7 @@ export class SlackEndpoint {
185
185
  admitInteraction(payload) {
186
186
  if (!this.#open)
187
187
  return;
188
+ void this.#emitPlatformEvent(`interaction.${payload.type}`, payload);
188
189
  if (payload.type !== 'block_actions' || !payload.actions?.length)
189
190
  return;
190
191
  const channelId = payload.channel?.id ?? '';
@@ -199,7 +200,7 @@ export class SlackEndpoint {
199
200
  // block_actions 无 channel_type;无 channel 时按与发起用户的 DM 处理
200
201
  channelType: channelId ? undefined : 'im',
201
202
  });
202
- void this.#options.gateway.receive({
203
+ void this.emit('message.receive', {
203
204
  conversation,
204
205
  message: { conversation, id: actionTs },
205
206
  content: formatInteractionContent(payload),
@@ -221,11 +222,12 @@ export class SlackEndpoint {
221
222
  admitSlashCommand(cmd) {
222
223
  if (!this.#open)
223
224
  return;
225
+ void this.#emitPlatformEvent(`slash.${cmd.command}`, cmd);
224
226
  postSlackEphemeral(cmd.response_url, '处理中…', this.#logger);
225
227
  const conversation = slackInboundConversation(String(this.#options.id), {
226
228
  channelId: cmd.channel_id,
227
229
  });
228
- void this.#options.gateway.receive({
230
+ void this.emit('message.receive', {
229
231
  conversation,
230
232
  message: { conversation, id: cmd.trigger_id },
231
233
  content: formatSlashContent(cmd),
@@ -248,7 +250,8 @@ export class SlackEndpoint {
248
250
  if (envelope?.type === 'event_callback' && envelope.event) {
249
251
  const event = envelope.event;
250
252
  if (event.type !== 'message' && event.type !== 'app_mention') {
251
- 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);
252
255
  return;
253
256
  }
254
257
  this.admit(event);
@@ -286,85 +289,43 @@ export class SlackEndpoint {
286
289
  return;
287
290
  await this.#client.chat.delete({ channel: ref.channel, ts: ref.ts });
288
291
  }
289
- async editMessage(channel, messageTs, content) {
292
+ async #editMessage(channel, messageTs, content) {
290
293
  if (!this.#client)
291
294
  throw new Error('Slack client not connected');
292
295
  await editSlackContent(this.#client, channel, messageTs, content);
293
296
  }
294
- // ── Agent tool surface ──────────────────────────────────────────────
295
- async inviteToChannel(channel, users) {
296
- await this.#client.conversations.invite({ channel, users: users.join(',') });
297
- return true;
298
- }
299
- async kickFromChannel(channel, user) {
300
- await this.#client.conversations.kick({ channel, user });
301
- return true;
302
- }
303
- async setChannelTopic(channel, topic) {
304
- await this.#client.conversations.setTopic({ channel, topic });
305
- return true;
306
- }
307
- async setChannelPurpose(channel, purpose) {
308
- await this.#client.conversations.setPurpose({ channel, purpose });
309
- return true;
310
- }
311
- async archiveChannel(channel) {
312
- await this.#client.conversations.archive({ channel });
313
- return true;
314
- }
315
- async unarchiveChannel(channel) {
316
- await this.#client.conversations.unarchive({ channel });
317
- return true;
318
- }
319
- async renameChannel(channel, name) {
320
- await this.#client.conversations.rename({ channel, name });
321
- return true;
322
- }
323
- async getChannelMembers(channel) {
324
- const result = await this.#client.conversations.members({ channel });
325
- return result.members || [];
326
- }
327
- async getChannelInfo(channel) {
328
- const result = await this.#client.conversations.info({ channel });
329
- return result.channel;
330
- }
331
- async getUserInfo(user) {
332
- const result = await this.#client.users.info({ user });
333
- return result.user;
334
- }
335
- async addReaction(channel, timestamp, name) {
297
+ async #addReaction(channel, timestamp, name) {
336
298
  const reaction = normalizeSlackReactionName(name);
337
299
  try {
338
300
  await this.#client.reactions.add({ channel, timestamp, name: reaction });
339
- return true;
340
301
  }
341
302
  catch (error) {
342
303
  const code = error?.data?.error;
343
304
  if (code === 'already_reacted')
344
- return true;
305
+ return;
345
306
  throw error;
346
307
  }
347
308
  }
348
- async removeReaction(channel, timestamp, name) {
309
+ async #removeReaction(channel, timestamp, name) {
349
310
  const reaction = normalizeSlackReactionName(name);
350
311
  try {
351
312
  await this.#client.reactions.remove({ channel, timestamp, name: reaction });
352
- return true;
353
313
  }
354
314
  catch (error) {
355
315
  const code = error?.data?.error;
356
316
  if (code === 'no_reaction')
357
- return true;
317
+ return;
358
318
  throw error;
359
319
  }
360
320
  }
361
- async pinMessage(channel, timestamp) {
362
- await this.#client.pins.add({ channel, timestamp });
363
- return true;
364
- }
365
- async unpinMessage(channel, timestamp) {
366
- await this.#client.pins.remove({ channel, timestamp });
367
- 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
+ });
368
329
  }
369
330
  async #startSocket() {
370
331
  const { config } = this.#options;
@@ -398,13 +359,7 @@ export class SlackEndpoint {
398
359
  }
399
360
  /** Slack Web API 分页上限(每页 1000,cursor 翻页直到 next_cursor 为空)。 */
400
361
  const SLACK_LIST_PAGE_SIZE = 1000;
401
- function createSlackEndpointManagement(endpoint) {
402
- const requireClient = () => {
403
- const client = endpoint.client;
404
- if (!client)
405
- throw new Error('Slack client not connected');
406
- return client;
407
- };
362
+ function createSlackEndpointManagement(requireClient) {
408
363
  return Object.freeze({
409
364
  // Slack 无"群"概念:public channel 归一为 group(channel 语义由 listChannels 之外省略,
410
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": "7.0.0",
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.2.0",
38
- "@zhin.js/core": "1.5.13",
39
- "@zhin.js/host-http": "1.0.12",
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.2.0",
46
- "@zhin.js/agent": "1.1.15",
47
- "@zhin.js/command": "1.0.15",
48
- "@zhin.js/core": "1.5.13",
49
- "@zhin.js/host-http": "1.0.12",
50
- "@zhin.js/permission": "1.0.3",
51
- "zhin.js": "6.0.13"
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.15",
73
- "zhin.js": "6.0.13"
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');