@zhin.js/adapter-slack 6.0.13 → 7.0.0

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 CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f2c532f: Expose exact per-Endpoint message operations through one validated Adapter capability model, route Core control calls through declared active capabilities, and connect existing recall, edit, reaction, and typing implementations across platform adapters.
8
+ - Updated dependencies [b10d058]
9
+ - Updated dependencies [f2c532f]
10
+ - Updated dependencies [3dbf990]
11
+ - @zhin.js/host-http@1.0.12
12
+ - @zhin.js/adapter@1.2.0
13
+ - @zhin.js/core@1.5.13
14
+ - @zhin.js/agent@1.1.15
15
+ - zhin.js@6.0.13
16
+
17
+ ## 6.0.14
18
+
19
+ ### Patch Changes
20
+
21
+ - 5969c5b: Add SideEventGateway so adapters forward notice/request/system into HandlerIndex. HandlerContext now exposes only generation-safe capabilities and prompt ports; live Endpoint escape hatches are removed.
22
+ - Updated dependencies [5969c5b]
23
+ - Updated dependencies [d336a3f]
24
+ - Updated dependencies [0c82a7e]
25
+ - Updated dependencies [b9217e4]
26
+ - Updated dependencies [5969c5b]
27
+ - Updated dependencies [5969c5b]
28
+ - Updated dependencies [974772e]
29
+ - Updated dependencies [5969c5b]
30
+ - Updated dependencies [5969c5b]
31
+ - Updated dependencies [2f786bd]
32
+ - Updated dependencies [63d89f9]
33
+ - Updated dependencies [71c7cdd]
34
+ - Updated dependencies [3cca0ea]
35
+ - Updated dependencies [1312ca0]
36
+ - Updated dependencies [985fa22]
37
+ - Updated dependencies [04b861d]
38
+ - Updated dependencies [a23d544]
39
+ - Updated dependencies [8cddabf]
40
+ - Updated dependencies [dbe5081]
41
+ - @zhin.js/im-contract@1.0.4
42
+ - @zhin.js/core@1.5.12
43
+ - @zhin.js/adapter@1.1.11
44
+ - @zhin.js/agent@1.1.14
45
+ - @zhin.js/host-http@1.0.11
46
+ - @zhin.js/command@1.0.15
47
+ - zhin.js@6.0.12
48
+ - @zhin.js/permission@1.0.3
49
+
3
50
  ## 6.0.13
4
51
 
5
52
  ### Patch Changes
package/adapters/slack.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Convention entry: discover `adapters/slack.ts` → defineAdapter.
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
7
7
  import { httpHostToken } from '@zhin.js/host-http';
8
8
  import { SlackEndpoint } from "../lib/endpoint.js";
9
9
  import { resolveSlackConfig, } from "../lib/protocol.js";
@@ -11,6 +11,7 @@ import { slackRuntimeStateToken } from "../lib/slack-runtime-state.js";
11
11
  export { SlackEndpoint } from "../lib/endpoint.js";
12
12
  export default defineAdapter({
13
13
  capabilities: ['inbound', 'outbound'],
14
+ operations: ['recall', 'edit', 'reaction'],
14
15
  // 媒体段(canonical MediaRef):kind=url 直发(image 走 attachment image_url,
15
16
  // 其余拉取后上传);kind=base64 解码后经 files.uploadV2 上传;kind=path 读盘上传;
16
17
  // kind=file 无 Slack 对应物,丢弃留痕。Block Kit 原生按钮承载交互段。
@@ -29,6 +30,7 @@ export default defineAdapter({
29
30
  return new SlackEndpoint({
30
31
  id: context.id,
31
32
  gateway: context.use(messageGatewayToken),
33
+ sideEvents: context.use(sideEventGatewayToken),
32
34
  http: context.use(httpHostToken),
33
35
  config,
34
36
  });
@@ -36,6 +38,7 @@ export default defineAdapter({
36
38
  return new SlackEndpoint({
37
39
  id: context.id,
38
40
  gateway: context.use(messageGatewayToken),
41
+ sideEvents: context.use(sideEventGatewayToken),
39
42
  config,
40
43
  });
41
44
  },
package/adapters/slack.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Convention entry: discover `adapters/slack.ts` → defineAdapter.
3
3
  */
4
4
  import { defineAdapter } from 'zhin.js/adapter';
5
- import { messageGatewayToken } from '@zhin.js/core/runtime';
5
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
6
6
  import { httpHostToken } from '@zhin.js/host-http';
7
7
  import { SlackEndpoint } from '../src/endpoint.js';
8
8
  import {
@@ -16,6 +16,7 @@ export type { SlackEndpointOptions, SlackSocketLike, SlackWebClientLike } from '
16
16
 
17
17
  export default defineAdapter<SlackAdapterConfig>({
18
18
  capabilities: ['inbound', 'outbound'],
19
+ operations: ['recall', 'edit', 'reaction'],
19
20
  // 媒体段(canonical MediaRef):kind=url 直发(image 走 attachment image_url,
20
21
  // 其余拉取后上传);kind=base64 解码后经 files.uploadV2 上传;kind=path 读盘上传;
21
22
  // kind=file 无 Slack 对应物,丢弃留痕。Block Kit 原生按钮承载交互段。
@@ -34,6 +35,7 @@ export default defineAdapter<SlackAdapterConfig>({
34
35
  return new SlackEndpoint({
35
36
  id: context.id,
36
37
  gateway: context.use(messageGatewayToken),
38
+ sideEvents: context.use(sideEventGatewayToken),
37
39
  http: context.use(httpHostToken),
38
40
  config,
39
41
  });
@@ -41,6 +43,7 @@ export default defineAdapter<SlackAdapterConfig>({
41
43
  return new SlackEndpoint({
42
44
  id: context.id,
43
45
  gateway: context.use(messageGatewayToken),
46
+ sideEvents: context.use(sideEventGatewayToken),
44
47
  config,
45
48
  });
46
49
  },
package/lib/endpoint.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
1
+ import type { EndpointControl, EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
2
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from 'zhin.js';
5
5
  import { type ResolvedSlackConfig, type SlackEvent, type SlackInteractionPayload, type SlackMessageEvent, type SlackSlashCommand } from './protocol.js';
@@ -118,6 +118,7 @@ export interface SlackWebClientLike extends SlackChatClient {
118
118
  export interface SlackEndpointOptions {
119
119
  readonly id: CapabilityId;
120
120
  readonly gateway: MessageGateway;
121
+ readonly sideEvents?: SideEventGateway;
121
122
  readonly config: ResolvedSlackConfig;
122
123
  readonly http?: HttpHost;
123
124
  readonly createClient?: (token: string) => SlackWebClientLike;
@@ -129,6 +130,7 @@ export interface SlackEndpointOptions {
129
130
  export declare class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
130
131
  #private;
131
132
  readonly management: EndpointManagement;
133
+ readonly control: EndpointControl;
132
134
  constructor(options: SlackEndpointOptions);
133
135
  /** Console 展示 / AdapterIndex live name(多 endpoint 时与 entry name 一致)。 */
134
136
  get name(): string;
package/lib/endpoint.js CHANGED
@@ -12,6 +12,7 @@ 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
+ import { receiveSlackSideEvent } from './side-event-dispatch.js';
15
16
  export class SlackEndpoint {
16
17
  #logger;
17
18
  #options;
@@ -25,6 +26,35 @@ export class SlackEndpoint {
25
26
  #started = false;
26
27
  #unregisterAgent;
27
28
  management = createSlackEndpointManagement(this);
29
+ control = Object.freeze({
30
+ recall: async (message) => {
31
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
32
+ if (!ref || !this.#client)
33
+ return;
34
+ await this.#client.chat.delete(ref);
35
+ },
36
+ edit: async (message, content) => {
37
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
38
+ if (!ref)
39
+ return null;
40
+ await this.editMessage(ref.channel, ref.ts, content);
41
+ return message.id;
42
+ },
43
+ addReaction: async (message, emoji) => {
44
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
45
+ if (!ref)
46
+ return null;
47
+ const reaction = normalizeSlackReactionName(emoji);
48
+ await this.addReaction(ref.channel, ref.ts, reaction);
49
+ return reaction;
50
+ },
51
+ removeReaction: async (message, reactionId) => {
52
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
53
+ if (!ref)
54
+ return;
55
+ await this.removeReaction(ref.channel, ref.ts, reactionId);
56
+ },
57
+ });
28
58
  constructor(options) {
29
59
  this.#logger = getAdapterLogger('slack', options.config.id);
30
60
  this.#options = options;
@@ -216,7 +246,12 @@ export class SlackEndpoint {
216
246
  handleEnvelope(body) {
217
247
  const envelope = body;
218
248
  if (envelope?.type === 'event_callback' && envelope.event) {
219
- this.admit(envelope.event);
249
+ const event = envelope.event;
250
+ if (event.type !== 'message' && event.type !== 'app_mention') {
251
+ receiveSlackSideEvent(this.#options.sideEvents, String(this.#options.id), this.#options.config.id, event, this.#logger);
252
+ return;
253
+ }
254
+ this.admit(event);
220
255
  }
221
256
  }
222
257
  trackMessageChannel(ts, channel) {
@@ -0,0 +1,4 @@
1
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import { type getAdapterLogger } from '@zhin.js/logger';
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;
@@ -0,0 +1,45 @@
1
+ import { buildNotice, mapNoticeParts, senderFromId, SLACK_NOTICE_PARTS_MAP } from '@zhin.js/core';
2
+ import { formatCompact } from '@zhin.js/logger';
3
+ export function receiveSlackSideEvent(sideEvents, endpointKey, configId, event, logger) {
4
+ if (!sideEvents)
5
+ return;
6
+ const eventType = String(event.type ?? '');
7
+ if (!Object.prototype.hasOwnProperty.call(SLACK_NOTICE_PARTS_MAP, eventType))
8
+ return;
9
+ const record = event;
10
+ const parts = mapNoticeParts('slack', eventType);
11
+ const channel = typeof record.channel === 'string' ? record.channel : undefined;
12
+ const user = typeof record.user === 'string' ? record.user : undefined;
13
+ const sceneId = channel ?? user ?? configId;
14
+ const dedupeKey = [
15
+ eventType,
16
+ channel ?? '',
17
+ user ?? '',
18
+ String(record.ts ?? record.event_ts ?? ''),
19
+ ].join(':');
20
+ void sideEvents.receiveNotice(buildNotice(record, {
21
+ $id: `slack:${dedupeKey}`,
22
+ $adapter: 'slack',
23
+ $endpoint: configId,
24
+ $type: 'notice',
25
+ $scene_id: sceneId,
26
+ $scene_type: parts.scene_type,
27
+ $sub_type: parts.sub_type,
28
+ $actor: senderFromId(user),
29
+ $target: senderFromId(typeof record.item_user === 'string' ? record.item_user : undefined),
30
+ $timestamp: toMillis(record.event_ts ?? record.ts),
31
+ })).catch((err) => {
32
+ logger.warn(formatCompact({
33
+ op: 'slack_side_event_failed',
34
+ endpoint: configId,
35
+ event: eventType,
36
+ error: err instanceof Error ? err.message : String(err),
37
+ }));
38
+ });
39
+ }
40
+ function toMillis(time) {
41
+ const value = Number(time);
42
+ if (!Number.isFinite(value) || value <= 0)
43
+ return Date.now();
44
+ return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-slack",
3
- "version": "6.0.13",
3
+ "version": "7.0.0",
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,21 @@
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.10",
38
- "@zhin.js/core": "1.5.11",
39
- "@zhin.js/host-http": "1.0.10",
40
- "@zhin.js/im-contract": "1.0.3",
37
+ "@zhin.js/adapter": "1.2.0",
38
+ "@zhin.js/core": "1.5.13",
39
+ "@zhin.js/host-http": "1.0.12",
40
+ "@zhin.js/im-contract": "1.0.4",
41
41
  "@zhin.js/logger": "1.0.76"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.10",
46
- "@zhin.js/agent": "1.1.13",
47
- "@zhin.js/command": "1.0.14",
48
- "@zhin.js/core": "1.5.11",
49
- "@zhin.js/host-http": "1.0.10",
50
- "@zhin.js/permission": "1.0.2",
51
- "zhin.js": "6.0.11"
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"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@zhin.js/agent": {
@@ -69,8 +69,8 @@
69
69
  "typescript": "^6.0.3",
70
70
  "vitest": "^4.1.10",
71
71
  "zod": "^4.4.3",
72
- "@zhin.js/agent": "1.1.13",
73
- "zhin.js": "6.0.11"
72
+ "@zhin.js/agent": "1.1.15",
73
+ "zhin.js": "6.0.13"
74
74
  },
75
75
  "files": [
76
76
  "adapters",
package/src/endpoint.ts CHANGED
@@ -6,11 +6,12 @@ import { WebClient } from '@slack/web-api';
6
6
  import type {
7
7
  EndpointFriend,
8
8
  EndpointGroup,
9
+ EndpointControl,
9
10
  EndpointInstance,
10
11
  EndpointManagement,
11
12
  EndpointSendRequest,
12
13
  } from 'zhin.js/adapter';
13
- import type { MessageGateway } from '@zhin.js/core/runtime';
14
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
14
15
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
15
16
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
16
17
  import type { CapabilityId } from 'zhin.js';
@@ -37,6 +38,7 @@ import { normalizeSlackReactionName } from './slack-reaction.js';
37
38
  import { postSlackEphemeral } from './slack-response-url.js';
38
39
  import { editSlackContent, sendSlackContent, type SlackChatClient } from './slack-outbound.js';
39
40
  import { registerSlackWebhookRoutes, type SlackWebhookHandler } from './webhook.js';
41
+ import { receiveSlackSideEvent } from './side-event-dispatch.js';
40
42
 
41
43
  export interface SlackSocketLike {
42
44
  on(
@@ -91,6 +93,7 @@ export interface SlackWebClientLike extends SlackChatClient {
91
93
  export interface SlackEndpointOptions {
92
94
  readonly id: CapabilityId;
93
95
  readonly gateway: MessageGateway;
96
+ readonly sideEvents?: SideEventGateway;
94
97
  readonly config: ResolvedSlackConfig;
95
98
  readonly http?: HttpHost;
96
99
  readonly createClient?: (token: string) => SlackWebClientLike;
@@ -114,6 +117,31 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
114
117
  #started = false;
115
118
  #unregisterAgent?: () => void;
116
119
  readonly management: EndpointManagement = createSlackEndpointManagement(this);
120
+ readonly control: EndpointControl = Object.freeze<EndpointControl>({
121
+ recall: async (message) => {
122
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
123
+ if (!ref || !this.#client) return;
124
+ await this.#client.chat.delete(ref);
125
+ },
126
+ edit: async (message, content) => {
127
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
128
+ if (!ref) return null;
129
+ await this.editMessage(ref.channel, ref.ts, content);
130
+ return message.id;
131
+ },
132
+ addReaction: async (message, emoji) => {
133
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
134
+ if (!ref) return null;
135
+ const reaction = normalizeSlackReactionName(emoji);
136
+ await this.addReaction(ref.channel, ref.ts, reaction);
137
+ return reaction;
138
+ },
139
+ removeReaction: async (message, reactionId) => {
140
+ const ref = this.resolveMessageRef(message.id, message.conversation.id);
141
+ if (!ref) return;
142
+ await this.removeReaction(ref.channel, ref.ts, reactionId);
143
+ },
144
+ });
117
145
 
118
146
  constructor(options: SlackEndpointOptions) {
119
147
  this.#logger = getAdapterLogger('slack', options.config.id);
@@ -316,7 +344,18 @@ export class SlackEndpoint implements EndpointInstance, SlackWebhookHandler {
316
344
  handleEnvelope(body: unknown): void {
317
345
  const envelope = body as SlackEventEnvelope;
318
346
  if (envelope?.type === 'event_callback' && envelope.event) {
319
- this.admit(envelope.event);
347
+ const event = envelope.event;
348
+ if (event.type !== 'message' && event.type !== 'app_mention') {
349
+ receiveSlackSideEvent(
350
+ this.#options.sideEvents,
351
+ String(this.#options.id),
352
+ this.#options.config.id,
353
+ event,
354
+ this.#logger,
355
+ );
356
+ return;
357
+ }
358
+ this.admit(event);
320
359
  }
321
360
  }
322
361
 
@@ -0,0 +1,54 @@
1
+ import { buildNotice, mapNoticeParts, senderFromId, SLACK_NOTICE_PARTS_MAP } from '@zhin.js/core';
2
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
3
+ import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
+ import type { SlackEvent } from './protocol.js';
5
+
6
+ export function receiveSlackSideEvent(
7
+ sideEvents: SideEventGateway | undefined,
8
+ endpointKey: string,
9
+ configId: string,
10
+ event: SlackEvent,
11
+ logger: ReturnType<typeof getAdapterLogger>,
12
+ ): void {
13
+ if (!sideEvents) return;
14
+ const eventType = String(event.type ?? '');
15
+ if (!Object.prototype.hasOwnProperty.call(SLACK_NOTICE_PARTS_MAP, eventType)) return;
16
+ const record = event as Record<string, unknown>;
17
+ const parts = mapNoticeParts('slack', eventType);
18
+ const channel = typeof record.channel === 'string' ? record.channel : undefined;
19
+ const user = typeof record.user === 'string' ? record.user : undefined;
20
+ const sceneId = channel ?? user ?? configId;
21
+ const dedupeKey = [
22
+ eventType,
23
+ channel ?? '',
24
+ user ?? '',
25
+ String(record.ts ?? record.event_ts ?? ''),
26
+ ].join(':');
27
+ void sideEvents.receiveNotice(buildNotice(record, {
28
+ $id: `slack:${dedupeKey}`,
29
+ $adapter: 'slack' as never,
30
+ $endpoint: configId,
31
+ $type: 'notice',
32
+ $scene_id: sceneId,
33
+ $scene_type: parts.scene_type,
34
+ $sub_type: parts.sub_type,
35
+ $actor: senderFromId(user),
36
+ $target: senderFromId(
37
+ typeof record.item_user === 'string' ? record.item_user : undefined,
38
+ ),
39
+ $timestamp: toMillis(record.event_ts ?? record.ts),
40
+ })).catch((err) => {
41
+ logger.warn(formatCompact({
42
+ op: 'slack_side_event_failed',
43
+ endpoint: configId,
44
+ event: eventType,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ }));
47
+ });
48
+ }
49
+
50
+ function toMillis(time: unknown): number {
51
+ const value = Number(time);
52
+ if (!Number.isFinite(value) || value <= 0) return Date.now();
53
+ return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
54
+ }