@zhin.js/adapter-slack 4.1.0 → 4.1.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +58 -250
  3. package/adapters/slack.ts +34 -0
  4. package/agent/tools/add_reaction.ts +2 -2
  5. package/agent/tools/archive_channel.ts +2 -2
  6. package/agent/tools/edit_message.ts +5 -4
  7. package/agent/tools/invite_to_channel.ts +2 -2
  8. package/agent/tools/pin_message.ts +2 -2
  9. package/agent/tools/remove_reaction.ts +2 -2
  10. package/agent/tools/set_purpose.ts +2 -2
  11. package/agent/tools/set_topic.ts +2 -2
  12. package/agent/tools/unarchive.ts +2 -2
  13. package/agent/tools/unpin_message.ts +2 -2
  14. package/agent/tools/user_info.ts +2 -2
  15. package/lib/endpoint.d.ts +147 -0
  16. package/lib/endpoint.js +335 -0
  17. package/lib/index.d.ts +11 -0
  18. package/lib/index.js +11 -0
  19. package/lib/markdown-to-mrkdwn.d.ts +9 -0
  20. package/lib/markdown-to-mrkdwn.js +121 -0
  21. package/lib/mrkdwn-to-markdown.d.ts +4 -0
  22. package/lib/{src/mrkdwn-to-markdown.js → mrkdwn-to-markdown.js} +0 -1
  23. package/lib/platform-permit.d.ts +16 -0
  24. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  25. package/lib/protocol.d.ts +143 -0
  26. package/lib/protocol.js +225 -0
  27. package/lib/slack-agent-deps.d.ts +28 -0
  28. package/lib/slack-agent-deps.js +30 -0
  29. package/lib/slack-inbound-filter.d.ts +12 -0
  30. package/lib/{src/slack-inbound-filter.js → slack-inbound-filter.js} +0 -1
  31. package/lib/slack-message-ref.d.ts +7 -0
  32. package/lib/{src/slack-message-ref.js → slack-message-ref.js} +0 -1
  33. package/lib/slack-outbound.d.ts +24 -0
  34. package/lib/slack-outbound.js +109 -0
  35. package/lib/slack-reaction.d.ts +1 -0
  36. package/lib/{src/slack-reaction.js → slack-reaction.js} +0 -1
  37. package/lib/slack-response-url.d.ts +5 -0
  38. package/lib/{src/slack-response-url.js → slack-response-url.js} +0 -1
  39. package/lib/webhook.d.ts +14 -0
  40. package/lib/webhook.js +69 -0
  41. package/package.json +41 -20
  42. package/plugin.ts +13 -0
  43. package/schema.json +38 -0
  44. package/src/endpoint.ts +317 -227
  45. package/src/index.ts +62 -66
  46. package/src/markdown-to-mrkdwn.ts +63 -4
  47. package/src/platform-permit.ts +1 -1
  48. package/src/protocol.ts +380 -0
  49. package/src/slack-agent-deps.ts +46 -11
  50. package/src/slack-inbound-filter.ts +1 -1
  51. package/src/slack-outbound.ts +32 -134
  52. package/src/webhook.ts +103 -0
  53. package/lib/agent/tools/add_reaction.js +0 -21
  54. package/lib/agent/tools/add_reaction.js.map +0 -1
  55. package/lib/agent/tools/archive_channel.js +0 -21
  56. package/lib/agent/tools/archive_channel.js.map +0 -1
  57. package/lib/agent/tools/edit_message.js +0 -22
  58. package/lib/agent/tools/edit_message.js.map +0 -1
  59. package/lib/agent/tools/invite_to_channel.js +0 -22
  60. package/lib/agent/tools/invite_to_channel.js.map +0 -1
  61. package/lib/agent/tools/pin_message.js +0 -22
  62. package/lib/agent/tools/pin_message.js.map +0 -1
  63. package/lib/agent/tools/remove_reaction.js +0 -21
  64. package/lib/agent/tools/remove_reaction.js.map +0 -1
  65. package/lib/agent/tools/set_purpose.js +0 -22
  66. package/lib/agent/tools/set_purpose.js.map +0 -1
  67. package/lib/agent/tools/set_topic.js +0 -22
  68. package/lib/agent/tools/set_topic.js.map +0 -1
  69. package/lib/agent/tools/unarchive.js +0 -21
  70. package/lib/agent/tools/unarchive.js.map +0 -1
  71. package/lib/agent/tools/unpin_message.js +0 -22
  72. package/lib/agent/tools/unpin_message.js.map +0 -1
  73. package/lib/agent/tools/user_info.js +0 -28
  74. package/lib/agent/tools/user_info.js.map +0 -1
  75. package/lib/src/adapter.js +0 -62
  76. package/lib/src/adapter.js.map +0 -1
  77. package/lib/src/endpoint.js +0 -312
  78. package/lib/src/endpoint.js.map +0 -1
  79. package/lib/src/event-dispatcher.js +0 -233
  80. package/lib/src/event-dispatcher.js.map +0 -1
  81. package/lib/src/index.js +0 -51
  82. package/lib/src/index.js.map +0 -1
  83. package/lib/src/markdown-to-mrkdwn.js +0 -61
  84. package/lib/src/markdown-to-mrkdwn.js.map +0 -1
  85. package/lib/src/mrkdwn-to-markdown.js.map +0 -1
  86. package/lib/src/platform-permit.js.map +0 -1
  87. package/lib/src/segment-mapper.js +0 -2
  88. package/lib/src/segment-mapper.js.map +0 -1
  89. package/lib/src/signing.js +0 -21
  90. package/lib/src/signing.js.map +0 -1
  91. package/lib/src/slack-agent-deps.js +0 -10
  92. package/lib/src/slack-agent-deps.js.map +0 -1
  93. package/lib/src/slack-inbound-filter.js.map +0 -1
  94. package/lib/src/slack-inbound.js +0 -127
  95. package/lib/src/slack-inbound.js.map +0 -1
  96. package/lib/src/slack-message-ref.js.map +0 -1
  97. package/lib/src/slack-outbound.js +0 -223
  98. package/lib/src/slack-outbound.js.map +0 -1
  99. package/lib/src/slack-reaction.js.map +0 -1
  100. package/lib/src/slack-response-url.js.map +0 -1
  101. package/lib/src/slack-side-events.js +0 -64
  102. package/lib/src/slack-side-events.js.map +0 -1
  103. package/lib/src/transport-http.js +0 -83
  104. package/lib/src/transport-http.js.map +0 -1
  105. package/lib/src/transport-socket.js +0 -68
  106. package/lib/src/transport-socket.js.map +0 -1
  107. package/lib/src/types.js +0 -5
  108. package/lib/src/types.js.map +0 -1
  109. package/plugin.yml +0 -3
  110. package/src/adapter.ts +0 -75
  111. package/src/event-dispatcher.ts +0 -255
  112. package/src/segment-mapper.ts +0 -1
  113. package/src/signing.ts +0 -28
  114. package/src/slack-inbound.ts +0 -140
  115. package/src/slack-side-events.ts +0 -74
  116. package/src/transport-http.ts +0 -95
  117. package/src/transport-socket.ts +0 -77
  118. package/src/types.ts +0 -113
@@ -0,0 +1,147 @@
1
+ import type { EndpointInstance } from '@zhin.js/adapter';
2
+ import type { MessageGateway } from '@zhin.js/core/runtime';
3
+ import type { HttpHost } from '@zhin.js/host-http';
4
+ import type { CapabilityId } from '@zhin.js/plugin-runtime';
5
+ import { type ResolvedSlackConfig, type SlackEvent, type SlackInteractionPayload, type SlackMessageEvent, type SlackSlashCommand } from './protocol.js';
6
+ import { type SlackChatClient } from './slack-outbound.js';
7
+ import { type SlackWebhookHandler } from './webhook.js';
8
+ export interface SlackSocketLike {
9
+ on(event: string, handler: (args: {
10
+ ack: () => Promise<void>;
11
+ body: unknown;
12
+ }) => void | Promise<void>): void;
13
+ start(): Promise<void>;
14
+ disconnect(): Promise<void>;
15
+ }
16
+ export interface SlackWebClientLike extends SlackChatClient {
17
+ auth: {
18
+ test(): Promise<{
19
+ user_id?: string;
20
+ user?: string;
21
+ }>;
22
+ };
23
+ conversations: {
24
+ invite(opts: {
25
+ channel: string;
26
+ users: string;
27
+ }): Promise<unknown>;
28
+ kick(opts: {
29
+ channel: string;
30
+ user: string;
31
+ }): Promise<unknown>;
32
+ setTopic(opts: {
33
+ channel: string;
34
+ topic: string;
35
+ }): Promise<unknown>;
36
+ setPurpose(opts: {
37
+ channel: string;
38
+ purpose: string;
39
+ }): Promise<unknown>;
40
+ archive(opts: {
41
+ channel: string;
42
+ }): Promise<unknown>;
43
+ unarchive(opts: {
44
+ channel: string;
45
+ }): Promise<unknown>;
46
+ rename(opts: {
47
+ channel: string;
48
+ name: string;
49
+ }): Promise<unknown>;
50
+ members(opts: {
51
+ channel: string;
52
+ }): Promise<{
53
+ members?: string[];
54
+ }>;
55
+ info(opts: {
56
+ channel: string;
57
+ }): Promise<{
58
+ channel?: unknown;
59
+ }>;
60
+ };
61
+ users: {
62
+ info(opts: {
63
+ user: string;
64
+ }): Promise<{
65
+ user?: unknown;
66
+ }>;
67
+ };
68
+ reactions: {
69
+ add(opts: {
70
+ channel: string;
71
+ timestamp: string;
72
+ name: string;
73
+ }): Promise<unknown>;
74
+ remove(opts: {
75
+ channel: string;
76
+ timestamp: string;
77
+ name: string;
78
+ }): Promise<unknown>;
79
+ };
80
+ pins: {
81
+ add(opts: {
82
+ channel: string;
83
+ timestamp: string;
84
+ }): Promise<unknown>;
85
+ remove(opts: {
86
+ channel: string;
87
+ timestamp: string;
88
+ }): Promise<unknown>;
89
+ };
90
+ chat: SlackChatClient['chat'] & {
91
+ delete(opts: {
92
+ channel: string;
93
+ ts: string;
94
+ }): Promise<unknown>;
95
+ };
96
+ }
97
+ export interface SlackEndpointOptions {
98
+ readonly id: CapabilityId;
99
+ readonly gateway: MessageGateway;
100
+ readonly config: ResolvedSlackConfig;
101
+ readonly http?: HttpHost;
102
+ readonly createClient?: (token: string) => SlackWebClientLike;
103
+ readonly createSocket?: (opts: {
104
+ readonly appToken: string;
105
+ readonly clientPingTimeout: number;
106
+ }) => SlackSocketLike;
107
+ }
108
+ export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
109
+ #private;
110
+ constructor(options: SlackEndpointOptions);
111
+ get client(): SlackWebClientLike | undefined;
112
+ get platformUserId(): string | undefined;
113
+ get config(): ResolvedSlackConfig;
114
+ start(): Promise<void>;
115
+ open(): void;
116
+ close(): void;
117
+ stop(): Promise<void>;
118
+ send({ target, payload }: {
119
+ readonly target: string;
120
+ readonly payload: unknown;
121
+ }): Promise<string>;
122
+ /** Test / internal: admit a message event when open. */
123
+ admit(event: SlackMessageEvent | SlackEvent): void;
124
+ admitInteraction(payload: SlackInteractionPayload): void;
125
+ admitSlashCommand(cmd: SlackSlashCommand): void;
126
+ handleEnvelope(body: unknown): void;
127
+ trackMessageChannel(ts: string, channel: string): void;
128
+ resolveMessageRef(messageId: string, channelHint?: string): {
129
+ channel: string;
130
+ ts: string;
131
+ } | null;
132
+ editMessage(channel: string, messageTs: string, content: unknown): Promise<void>;
133
+ inviteToChannel(channel: string, users: string[]): Promise<boolean>;
134
+ kickFromChannel(channel: string, user: string): Promise<boolean>;
135
+ setChannelTopic(channel: string, topic: string): Promise<boolean>;
136
+ setChannelPurpose(channel: string, purpose: string): Promise<boolean>;
137
+ archiveChannel(channel: string): Promise<boolean>;
138
+ unarchiveChannel(channel: string): Promise<boolean>;
139
+ renameChannel(channel: string, name: string): Promise<boolean>;
140
+ getChannelMembers(channel: string): Promise<string[]>;
141
+ getChannelInfo(channel: string): Promise<unknown>;
142
+ getUserInfo(user: string): Promise<unknown>;
143
+ addReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
144
+ removeReaction(channel: string, timestamp: string, name: string): Promise<boolean>;
145
+ pinMessage(channel: string, timestamp: string): Promise<boolean>;
146
+ unpinMessage(channel: string, timestamp: string): Promise<boolean>;
147
+ }
@@ -0,0 +1,335 @@
1
+ /**
2
+ * SlackEndpoint — lifecycle, outbound, admit, Socket Mode, agent tool surface.
3
+ */
4
+ import { SocketModeClient } from '@slack/socket-mode';
5
+ import { WebClient } from '@slack/web-api';
6
+ import { formatCompact, getLogger } from '@zhin.js/logger';
7
+ import { formatInboundContent, formatInteractionContent, formatSlashContent, inboundMessageId, resolveSlackChannelType, } from './protocol.js';
8
+ import { registerSlackAgentEndpoint } from './slack-agent-deps.js';
9
+ import { createSlackInboundFilterState, shouldDropSlackInboundMessage, } from './slack-inbound-filter.js';
10
+ import { formatSlackMessageRef, parseSlackMessageRef } from './slack-message-ref.js';
11
+ import { normalizeSlackReactionName } from './slack-reaction.js';
12
+ import { postSlackEphemeral } from './slack-response-url.js';
13
+ import { editSlackContent, sendSlackContent } from './slack-outbound.js';
14
+ import { registerSlackWebhookRoutes } from './webhook.js';
15
+ const logger = getLogger('slack');
16
+ export class SlackEndpoint {
17
+ #options;
18
+ #inboundFilter = createSlackInboundFilterState();
19
+ #messageChannelMap = new Map();
20
+ #client;
21
+ #socket;
22
+ #routeReleases = [];
23
+ #botUserId;
24
+ #open = false;
25
+ #started = false;
26
+ #unregisterAgent;
27
+ constructor(options) {
28
+ this.#options = options;
29
+ }
30
+ get client() {
31
+ return this.#client;
32
+ }
33
+ get platformUserId() {
34
+ return this.#botUserId;
35
+ }
36
+ get config() {
37
+ return this.#options.config;
38
+ }
39
+ async start() {
40
+ if (this.#started)
41
+ return;
42
+ this.#started = true;
43
+ try {
44
+ const { config } = this.#options;
45
+ this.#client = this.#options.createClient?.(config.token)
46
+ ?? new WebClient(config.token);
47
+ this.#unregisterAgent = registerSlackAgentEndpoint(config.name, this);
48
+ if (config.mode === 'socket') {
49
+ await this.#startSocket();
50
+ }
51
+ else {
52
+ const http = this.#options.http;
53
+ if (!http) {
54
+ throw new Error('Slack HTTP Events API requires httpHostToken (Runtime Host)');
55
+ }
56
+ this.#routeReleases.push(...registerSlackWebhookRoutes(http, this));
57
+ logger.debug(formatCompact({
58
+ endpoint: config.name,
59
+ op: 'webhook',
60
+ path: config.webhookPath,
61
+ }));
62
+ }
63
+ const authTest = await this.#client.auth.test();
64
+ if (authTest.user_id)
65
+ this.#botUserId = String(authTest.user_id);
66
+ logger.info(formatCompact({
67
+ op: 'connect',
68
+ endpoint: config.name,
69
+ mode: config.mode,
70
+ platform_user_id: this.#botUserId,
71
+ }));
72
+ }
73
+ catch (error) {
74
+ await this.stop();
75
+ logger.error('Failed to connect Slack endpoint:', error);
76
+ throw error;
77
+ }
78
+ }
79
+ open() {
80
+ this.#open = true;
81
+ }
82
+ close() {
83
+ this.#open = false;
84
+ }
85
+ async stop() {
86
+ this.#open = false;
87
+ if (this.#socket) {
88
+ try {
89
+ await this.#socket.disconnect();
90
+ }
91
+ catch {
92
+ /* ignore */
93
+ }
94
+ this.#socket = undefined;
95
+ }
96
+ for (const release of this.#routeReleases.splice(0))
97
+ release();
98
+ this.#unregisterAgent?.();
99
+ this.#unregisterAgent = undefined;
100
+ this.#client = undefined;
101
+ this.#started = false;
102
+ logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
103
+ }
104
+ async send({ target, payload }) {
105
+ if (!this.#client)
106
+ throw new Error('Slack client not connected');
107
+ const { channel, threadTs } = parseSendTarget(target);
108
+ const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, logger);
109
+ if (result.ts)
110
+ this.trackMessageChannel(result.ts, channel);
111
+ return formatSlackMessageRef(channel, result.ts || String(Date.now()));
112
+ }
113
+ /** Test / internal: admit a message event when open. */
114
+ admit(event) {
115
+ if (!this.#open)
116
+ return;
117
+ if (event.type !== 'message' && event.type !== 'app_mention')
118
+ return;
119
+ const msg = event;
120
+ if (shouldDropSlackInboundMessage(msg, this.#inboundFilter, this.#botUserId))
121
+ return;
122
+ if (!msg.channel || !msg.ts)
123
+ return;
124
+ this.trackMessageChannel(msg.ts, msg.channel);
125
+ void this.#options.gateway.receive({
126
+ adapter: this.#options.id,
127
+ target: msg.channel,
128
+ content: formatInboundContent(msg),
129
+ sender: msg.user ?? msg.channel,
130
+ id: inboundMessageId(msg),
131
+ metadata: Object.freeze({
132
+ endpoint: this.#options.config.name,
133
+ channelType: resolveSlackChannelType(msg),
134
+ userId: msg.user,
135
+ threadTs: msg.thread_ts && msg.thread_ts !== msg.ts ? msg.thread_ts : undefined,
136
+ ts: msg.ts,
137
+ // app_mention 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
138
+ ...(msg.type === 'app_mention' ? { mentioned: true } : {}),
139
+ }),
140
+ }).catch((err) => {
141
+ logger.warn(formatCompact({
142
+ op: 'slack_gateway_receive_failed',
143
+ target: msg.channel,
144
+ error: err instanceof Error ? err.message : String(err),
145
+ }));
146
+ });
147
+ }
148
+ admitInteraction(payload) {
149
+ if (!this.#open)
150
+ return;
151
+ if (payload.type !== 'block_actions' || !payload.actions?.length)
152
+ return;
153
+ const channelId = payload.channel?.id ?? '';
154
+ const userId = payload.user.id;
155
+ const messageTs = payload.message?.ts ?? '';
156
+ if (payload.response_url) {
157
+ postSlackEphemeral(payload.response_url, '已收到', logger);
158
+ }
159
+ void this.#options.gateway.receive({
160
+ adapter: this.#options.id,
161
+ target: channelId || userId,
162
+ content: formatInteractionContent(payload),
163
+ sender: userId,
164
+ id: payload.actions[0]?.action_ts ?? messageTs ?? `action-${Date.now()}`,
165
+ metadata: Object.freeze({
166
+ endpoint: this.#options.config.name,
167
+ eventType: 'block_actions',
168
+ actionId: payload.actions[0]?.action_id,
169
+ threadTs: messageTs || undefined,
170
+ }),
171
+ }).catch((err) => {
172
+ logger.warn(formatCompact({
173
+ op: 'slack_gateway_receive_failed',
174
+ target: channelId,
175
+ error: err instanceof Error ? err.message : String(err),
176
+ }));
177
+ });
178
+ }
179
+ admitSlashCommand(cmd) {
180
+ if (!this.#open)
181
+ return;
182
+ postSlackEphemeral(cmd.response_url, '处理中…', logger);
183
+ void this.#options.gateway.receive({
184
+ adapter: this.#options.id,
185
+ target: cmd.channel_id,
186
+ content: formatSlashContent(cmd),
187
+ sender: cmd.user_id,
188
+ id: cmd.trigger_id,
189
+ metadata: Object.freeze({
190
+ endpoint: this.#options.config.name,
191
+ eventType: 'slash_command',
192
+ command: cmd.command,
193
+ }),
194
+ }).catch((err) => {
195
+ logger.warn(formatCompact({
196
+ op: 'slack_gateway_receive_failed',
197
+ target: cmd.channel_id,
198
+ error: err instanceof Error ? err.message : String(err),
199
+ }));
200
+ });
201
+ }
202
+ handleEnvelope(body) {
203
+ const envelope = body;
204
+ if (envelope?.type === 'event_callback' && envelope.event) {
205
+ this.admit(envelope.event);
206
+ }
207
+ }
208
+ trackMessageChannel(ts, channel) {
209
+ if (ts && channel)
210
+ this.#messageChannelMap.set(ts, channel);
211
+ }
212
+ resolveMessageRef(messageId, channelHint) {
213
+ const parsed = parseSlackMessageRef(messageId);
214
+ if (parsed) {
215
+ this.trackMessageChannel(parsed.ts, parsed.channel);
216
+ return parsed;
217
+ }
218
+ if (channelHint)
219
+ return { channel: channelHint, ts: messageId };
220
+ const channel = this.#messageChannelMap.get(messageId);
221
+ return channel ? { channel, ts: messageId } : null;
222
+ }
223
+ async editMessage(channel, messageTs, content) {
224
+ if (!this.#client)
225
+ throw new Error('Slack client not connected');
226
+ await editSlackContent(this.#client, channel, messageTs, content);
227
+ }
228
+ // ── Agent tool surface ──────────────────────────────────────────────
229
+ async inviteToChannel(channel, users) {
230
+ await this.#client.conversations.invite({ channel, users: users.join(',') });
231
+ return true;
232
+ }
233
+ async kickFromChannel(channel, user) {
234
+ await this.#client.conversations.kick({ channel, user });
235
+ return true;
236
+ }
237
+ async setChannelTopic(channel, topic) {
238
+ await this.#client.conversations.setTopic({ channel, topic });
239
+ return true;
240
+ }
241
+ async setChannelPurpose(channel, purpose) {
242
+ await this.#client.conversations.setPurpose({ channel, purpose });
243
+ return true;
244
+ }
245
+ async archiveChannel(channel) {
246
+ await this.#client.conversations.archive({ channel });
247
+ return true;
248
+ }
249
+ async unarchiveChannel(channel) {
250
+ await this.#client.conversations.unarchive({ channel });
251
+ return true;
252
+ }
253
+ async renameChannel(channel, name) {
254
+ await this.#client.conversations.rename({ channel, name });
255
+ return true;
256
+ }
257
+ async getChannelMembers(channel) {
258
+ const result = await this.#client.conversations.members({ channel });
259
+ return result.members || [];
260
+ }
261
+ async getChannelInfo(channel) {
262
+ const result = await this.#client.conversations.info({ channel });
263
+ return result.channel;
264
+ }
265
+ async getUserInfo(user) {
266
+ const result = await this.#client.users.info({ user });
267
+ return result.user;
268
+ }
269
+ async addReaction(channel, timestamp, name) {
270
+ const reaction = normalizeSlackReactionName(name);
271
+ try {
272
+ await this.#client.reactions.add({ channel, timestamp, name: reaction });
273
+ return true;
274
+ }
275
+ catch (error) {
276
+ const code = error?.data?.error;
277
+ if (code === 'already_reacted')
278
+ return true;
279
+ throw error;
280
+ }
281
+ }
282
+ async removeReaction(channel, timestamp, name) {
283
+ const reaction = normalizeSlackReactionName(name);
284
+ try {
285
+ await this.#client.reactions.remove({ channel, timestamp, name: reaction });
286
+ return true;
287
+ }
288
+ catch (error) {
289
+ const code = error?.data?.error;
290
+ if (code === 'no_reaction')
291
+ return true;
292
+ throw error;
293
+ }
294
+ }
295
+ async pinMessage(channel, timestamp) {
296
+ await this.#client.pins.add({ channel, timestamp });
297
+ return true;
298
+ }
299
+ async unpinMessage(channel, timestamp) {
300
+ await this.#client.pins.remove({ channel, timestamp });
301
+ return true;
302
+ }
303
+ async #startSocket() {
304
+ const { config } = this.#options;
305
+ if (!config.appToken)
306
+ throw new Error('Socket Mode requires appToken');
307
+ this.#socket = this.#options.createSocket?.({
308
+ appToken: config.appToken,
309
+ clientPingTimeout: config.clientPingTimeout,
310
+ }) ?? new SocketModeClient({
311
+ appToken: config.appToken,
312
+ clientPingTimeout: config.clientPingTimeout,
313
+ });
314
+ this.#socket.on('slack_event', async ({ ack, body }) => {
315
+ await ack();
316
+ this.handleEnvelope(body);
317
+ });
318
+ this.#socket.on('interactive', async ({ ack, body }) => {
319
+ await ack();
320
+ this.admitInteraction(body);
321
+ });
322
+ this.#socket.on('slash_commands', async ({ ack, body }) => {
323
+ await ack();
324
+ this.admitSlashCommand(body);
325
+ });
326
+ await this.#socket.start();
327
+ }
328
+ }
329
+ function parseSendTarget(target) {
330
+ const parsed = parseSlackMessageRef(target);
331
+ if (parsed && /^\d+\.\d+$/.test(parsed.ts)) {
332
+ return { channel: parsed.channel, threadTs: parsed.ts };
333
+ }
334
+ return { channel: target };
335
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue, inboundMessageId, keyboardToBlockKitBlocks, normalizeWebhookPath, readTextBody, resolveSlackChannelType, resolveSlackConfig, 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
+ export { SlackEndpoint, type SlackEndpointOptions, type SlackSocketLike, type SlackWebClientLike, } from './endpoint.js';
3
+ export { registerSlackWebhookRoutes, handleSlackWebhookRequest, type SlackWebhookHandler, } from './webhook.js';
4
+ export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, type SlackAgentDeps, type SlackAgentEndpoint, } from './slack-agent-deps.js';
5
+ export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, registerSlackPlatformPermitChecker, slackGroupPermitResolver, } from './platform-permit.js';
6
+ export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
7
+ export { normalizeSlackReactionName } from './slack-reaction.js';
8
+ export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
9
+ export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
10
+ export { createSlackInboundFilterState, shouldDropSlackInboundMessage, } from './slack-inbound-filter.js';
11
+ export { editSlackContent, sendSlackContent } from './slack-outbound.js';
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { formatInboundContent, formatInteractionContent, formatOutboundWire, formatSlashContent, headerValue, inboundMessageId, keyboardToBlockKitBlocks, normalizeWebhookPath, readTextBody, resolveSlackChannelType, resolveSlackConfig, verifySlackSignature, } from './protocol.js';
2
+ export { SlackEndpoint, } from './endpoint.js';
3
+ export { registerSlackWebhookRoutes, handleSlackWebhookRequest, } from './webhook.js';
4
+ export { getSlackAgentDeps, registerSlackAgentEndpoint, setSlackAgentDeps, } from './slack-agent-deps.js';
5
+ export { checkSlackPlatformPermit, normalizeSlackSenderForPermit, platformPermit, registerSlackPlatformPermitChecker, slackGroupPermitResolver, } from './platform-permit.js';
6
+ export { formatSlackMessageRef, parseSlackMessageRef, slackMessageTs, } from './slack-message-ref.js';
7
+ export { normalizeSlackReactionName } from './slack-reaction.js';
8
+ export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
9
+ export { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
10
+ export { createSlackInboundFilterState, shouldDropSlackInboundMessage, } from './slack-inbound-filter.js';
11
+ export { editSlackContent, sendSlackContent } from './slack-outbound.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 常见 Markdown → Slack mrkdwn(Slack 粗体为 *text*,链接为 <url|text>)
3
+ */
4
+ export declare const SLACK_MRKDWN_TEXT_MAX = 2900;
5
+ export declare function markdownToMrkdwn(text: string): string;
6
+ /** 按 Slack section mrkdwn 上限切分(尽量在换行/空格处断开) */
7
+ export declare function splitMrkdwnText(text: string, maxLen?: number): string[];
8
+ /** 通知栏 / 无障碍 fallback 用的纯文本 */
9
+ export declare function mrkdwnToPlainFallback(text: string): string;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * 常见 Markdown → Slack mrkdwn(Slack 粗体为 *text*,链接为 <url|text>)
3
+ */
4
+ export const SLACK_MRKDWN_TEXT_MAX = 2900;
5
+ export function markdownToMrkdwn(text) {
6
+ let result = replaceMarkdownLinks(text);
7
+ result = result.replace(/\*\*([^*]+)\*\*/g, '*$1*');
8
+ result = result.replace(/__([^_]+)__/g, '*$1*');
9
+ result = result.replace(/~~([^~]+)~~/g, '~$1~');
10
+ result = result.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
11
+ return result;
12
+ }
13
+ function replaceMarkdownLinks(text) {
14
+ let out = '';
15
+ let cursor = 0;
16
+ while (cursor < text.length) {
17
+ const labelStart = text.indexOf('[', cursor);
18
+ if (labelStart < 0)
19
+ break;
20
+ const labelEnd = text.indexOf(']', labelStart + 1);
21
+ if (labelEnd < 0 || text[labelEnd + 1] !== '(') {
22
+ out += text.slice(cursor, labelStart + 1);
23
+ cursor = labelStart + 1;
24
+ continue;
25
+ }
26
+ const urlEnd = text.indexOf(')', labelEnd + 2);
27
+ if (urlEnd < 0)
28
+ break;
29
+ out += text.slice(cursor, labelStart);
30
+ out += `<${text.slice(labelEnd + 2, urlEnd)}|${text.slice(labelStart + 1, labelEnd)}>`;
31
+ cursor = urlEnd + 1;
32
+ }
33
+ return out + text.slice(cursor);
34
+ }
35
+ /** 按 Slack section mrkdwn 上限切分(尽量在换行/空格处断开) */
36
+ export function splitMrkdwnText(text, maxLen = SLACK_MRKDWN_TEXT_MAX) {
37
+ if (text.length <= maxLen)
38
+ return [text];
39
+ const chunks = [];
40
+ let rest = text;
41
+ while (rest.length > maxLen) {
42
+ let cut = rest.lastIndexOf('\n', maxLen);
43
+ if (cut < maxLen * 0.4)
44
+ cut = rest.lastIndexOf(' ', maxLen);
45
+ if (cut <= 0)
46
+ cut = maxLen;
47
+ chunks.push(rest.slice(0, cut).trimEnd());
48
+ rest = rest.slice(cut).trimStart();
49
+ }
50
+ if (rest)
51
+ chunks.push(rest);
52
+ return chunks.length > 0 ? chunks : [text];
53
+ }
54
+ /** 通知栏 / 无障碍 fallback 用的纯文本 */
55
+ export function mrkdwnToPlainFallback(text) {
56
+ return stripMrkdwnMarkers(stripAngleBrackets(rewriteAngleLinks(text)));
57
+ }
58
+ /**
59
+ * `<url|text>` → `text (url)`。
60
+ * 线性扫描,语义等价于 `/<([^|>]+)\|([^>]+)>/g`(分组一不含 `|`,
61
+ * 因此切分点恒为首个 `|`),但避免量词重叠在超长 `<...` 输入上的
62
+ * 二次方回溯(js/polynomial-redos)。
63
+ */
64
+ function rewriteAngleLinks(text) {
65
+ let out = '';
66
+ let i = 0;
67
+ while (i < text.length) {
68
+ const open = text.indexOf('<', i);
69
+ if (open < 0)
70
+ break;
71
+ const close = text.indexOf('>', open + 1);
72
+ if (close < 0)
73
+ break;
74
+ const inner = text.slice(open + 1, close);
75
+ const pipe = inner.indexOf('|');
76
+ if (pipe > 0 && pipe < inner.length - 1) {
77
+ out += text.slice(i, open);
78
+ out += `${inner.slice(pipe + 1)} (${inner.slice(0, pipe)})`;
79
+ i = close + 1;
80
+ continue;
81
+ }
82
+ // 非链接形态:本趟不匹配,原样保留 `<` 并继续(与原正则行为一致)。
83
+ out += text.slice(i, open + 1);
84
+ i = open + 1;
85
+ }
86
+ return out + text.slice(i);
87
+ }
88
+ /**
89
+ * 剥掉剩余 `<...>` 的尖括号。线性扫描,等价于 `/<([^>]+)>/g`
90
+ * (空内容 `<>` 不匹配、原样保留)。
91
+ */
92
+ function stripAngleBrackets(text) {
93
+ let out = '';
94
+ let i = 0;
95
+ while (i < text.length) {
96
+ const open = text.indexOf('<', i);
97
+ if (open < 0)
98
+ break;
99
+ const close = text.indexOf('>', open + 1);
100
+ if (close < 0)
101
+ break;
102
+ if (close === open + 1) {
103
+ out += text.slice(i, open + 1);
104
+ i = open + 1;
105
+ continue;
106
+ }
107
+ out += text.slice(i, open);
108
+ out += text.slice(open + 1, close);
109
+ i = close + 1;
110
+ }
111
+ return out + text.slice(i);
112
+ }
113
+ /** 去掉 `*` `_` `~` `` ` `` 样式符号(等价于 `/[*_~`]/g`,逐字符线性)。 */
114
+ function stripMrkdwnMarkers(text) {
115
+ let out = '';
116
+ for (const ch of text) {
117
+ if (ch !== '*' && ch !== '_' && ch !== '~' && ch !== '`')
118
+ out += ch;
119
+ }
120
+ return out;
121
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Slack mrkdwn → 通用 Markdown(入站文本段)
3
+ */
4
+ export declare function mrkdwnToMarkdown(text: string): string;
@@ -28,4 +28,3 @@ function replaceSlackLinks(text) {
28
28
  }
29
29
  return out + text.slice(cursor);
30
30
  }
31
- //# sourceMappingURL=mrkdwn-to-markdown.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Slack platform permit — workspace + channel 权限
3
+ */
4
+ import { type Message } from '@zhin.js/core';
5
+ export declare function platformPermit(perm: string): string;
6
+ export declare function slackGroupPermitResolver(logicalPerm: string): string;
7
+ export declare function normalizeSlackSenderForPermit(input: {
8
+ isWorkspaceOwner?: boolean;
9
+ isWorkspaceAdmin?: boolean;
10
+ isChannelManager?: boolean;
11
+ }): {
12
+ role?: string;
13
+ permissions?: string[];
14
+ };
15
+ export declare function checkSlackPlatformPermit(perm: string, message: Message<any>): boolean;
16
+ export declare function registerSlackPlatformPermitChecker(): () => void;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Slack platform permit — workspace + channel 权限
3
3
  */
4
- import { registerPlatformPermitChecker } from 'zhin.js';
4
+ import { registerPlatformPermitChecker } from '@zhin.js/core';
5
5
  const ADAPTER = 'slack';
6
6
  export function platformPermit(perm) {
7
7
  return `platform(${ADAPTER},${perm})`;
@@ -49,4 +49,3 @@ export function checkSlackPlatformPermit(perm, message) {
49
49
  export function registerSlackPlatformPermitChecker() {
50
50
  return registerPlatformPermitChecker(ADAPTER, checkSlackPlatformPermit);
51
51
  }
52
- //# sourceMappingURL=platform-permit.js.map