@zhin.js/adapter-slack 1.0.78 → 2.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.
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Slack Bot 实现
2
+ * Slack Endpoint 实现
3
3
  */
4
4
  import { App as SlackApp, LogLevel } from "@slack/bolt";
5
5
  import { WebClient, type ChatPostMessageArguments } from "@slack/web-api";
6
- import { formatCompact, Bot, Message, MessageSegment, segment, SendContent, SendOptions } from 'zhin.js';
7
- import type { SlackBotConfig, SlackMessageEvent } from "./types.js";
6
+ import { formatCompact, Endpoint, Message, MessageSegment, segment, SendContent, SendOptions } from 'zhin.js';
7
+ import type { SlackEndpointConfig, SlackMessageEvent } from "./types.js";
8
8
  import type { SlackAdapter } from "./adapter.js";
9
+ import { normalizeSlackSenderForPermit } from "./platform-permit.js";
9
10
 
10
11
 
11
- export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
12
+ export class SlackEndpoint implements Endpoint<SlackEndpointConfig, SlackMessageEvent> {
12
13
  $connected: boolean;
13
14
  /** 延迟到 `$connect`,避免子类 mock `$connect` 时仍在构造函数里创建 Bolt/WebClient(会在后台触发 Slack API) */
14
15
  private app?: SlackApp;
@@ -22,7 +23,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
22
23
  return this.$config.name;
23
24
  }
24
25
 
25
- constructor(public adapter: SlackAdapter, public $config: SlackBotConfig) {
26
+ constructor(public adapter: SlackAdapter, public $config: SlackEndpointConfig) {
26
27
  this.$connected = false;
27
28
  }
28
29
 
@@ -73,7 +74,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
73
74
 
74
75
  // Get bot info
75
76
  const authTest = await this.client!.auth.test();
76
- this.logger.info(formatCompact({ bot: this.$config.name, user: authTest.user }));
77
+ this.logger.info(formatCompact({ endpoint: this.$config.name, user: authTest.user }));
77
78
 
78
79
  if (!this.$config.socketMode) {
79
80
  this.logger.info(formatCompact( { op: "listen", port }));
@@ -93,13 +94,57 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
93
94
  try {
94
95
  await this.app!.stop();
95
96
  this.$connected = false;
96
- this.logger.info(formatCompact( { op: "disconnect", bot: this.$config.name }));
97
+ this.logger.info(formatCompact( { op: "disconnect", endpoint: this.$config.name }));
97
98
  } catch (error) {
98
99
  this.logger.error("Error disconnecting Slack bot:", error);
99
100
  throw error;
100
101
  }
101
102
  }
102
103
 
104
+ private senderPermitCache = new Map<string, { at: number; role?: string; permissions: string[] }>();
105
+
106
+ private async enrichChannelSender(
107
+ message: Message<SlackMessageEvent>,
108
+ msg: SlackMessageEvent,
109
+ ): Promise<void> {
110
+ if (message.$channel.type !== "group" || !("user" in msg) || !msg.user) return;
111
+ const channelId = msg.channel;
112
+ const userId = msg.user;
113
+ const key = `${channelId}:${userId}`;
114
+ const now = Date.now();
115
+ const cached = this.senderPermitCache.get(key);
116
+ if (cached && now - cached.at < 60_000) {
117
+ message.$sender.role = cached.role;
118
+ message.$sender.permissions = cached.permissions;
119
+ return;
120
+ }
121
+ try {
122
+ const user = await this.getUserInfo(userId);
123
+ let isChannelManager = false;
124
+ try {
125
+ const channel = await this.getChannelInfo(channelId);
126
+ if (channel?.creator === userId) isChannelManager = true;
127
+ } catch {
128
+ // ignore
129
+ }
130
+ const normalized = normalizeSlackSenderForPermit({
131
+ isWorkspaceOwner: user.is_owner === true,
132
+ isWorkspaceAdmin: user.is_admin === true,
133
+ isChannelManager,
134
+ });
135
+ const entry = {
136
+ at: now,
137
+ role: normalized.role,
138
+ permissions: normalized.permissions ?? [],
139
+ };
140
+ this.senderPermitCache.set(key, entry);
141
+ message.$sender.role = entry.role;
142
+ message.$sender.permissions = entry.permissions;
143
+ } catch {
144
+ // 保守拒绝
145
+ }
146
+ }
147
+
103
148
  private async handleSlackMessage(msg: SlackMessageEvent): Promise<void> {
104
149
  // Ignore bot messages and message changes
105
150
  if ("subtype" in msg && (msg.subtype === "bot_message" || msg.subtype === "message_changed")) {
@@ -107,6 +152,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
107
152
  }
108
153
 
109
154
  const message = this.$formatMessage(msg);
155
+ await this.enrichChannelSender(message, msg);
110
156
  this.adapter.emit("message.receive", message);
111
157
  this.logger.debug(
112
158
  `${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}): ${segment.raw(message.$content)}`
@@ -129,7 +175,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
129
175
  const result = Message.from(msg, {
130
176
  $id: msg.ts,
131
177
  $adapter: "slack",
132
- $bot: this.$config.name,
178
+ $endpoint: this.$config.name,
133
179
  $sender: {
134
180
  id: userId,
135
181
  name: userName,
@@ -170,7 +216,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
170
216
 
171
217
  return await this.adapter.sendMessage({
172
218
  context: "slack",
173
- bot: this.$config.name,
219
+ endpoint: this.$config.name,
174
220
  id: channelId,
175
221
  type: "channel",
176
222
  content: content,
@@ -458,10 +504,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
458
504
 
459
505
  async $recallMessage(id: string): Promise<void> {
460
506
  // Slack requires both channel and ts (timestamp) to delete a message
461
- // The Bot interface only provides message ID (ts), making recall impossible
507
+ // The Endpoint interface only provides message ID (ts), making recall impossible
462
508
  // Users should use message.$recall() instead, which has the full context
463
509
  throw new Error(
464
- "SlackBot.$recallMessage: Message recall not supported without channel information. " +
510
+ "SlackEndpoint.$recallMessage: Message recall not supported without channel information. " +
465
511
  "Use message.$recall() method instead, which contains the required context."
466
512
  );
467
513
  }
@@ -476,10 +522,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
476
522
  async inviteToChannel(channel: string, users: string[]): Promise<boolean> {
477
523
  try {
478
524
  await this.client!.conversations.invite({ channel, users: users.join(',') });
479
- this.logger.debug(formatCompact( { op: "invite", bot: this.$id, channel, users: users.join(",") }));
525
+ this.logger.debug(formatCompact( { op: "invite", endpoint: this.$id, channel, users: users.join(",") }));
480
526
  return true;
481
527
  } catch (error) {
482
- this.logger.error(`Slack Bot ${this.$id} 邀请用户失败:`, error);
528
+ this.logger.error(`Slack Endpoint ${this.$id} 邀请用户失败:`, error);
483
529
  throw error;
484
530
  }
485
531
  }
@@ -492,10 +538,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
492
538
  async kickFromChannel(channel: string, user: string): Promise<boolean> {
493
539
  try {
494
540
  await this.client!.conversations.kick({ channel, user });
495
- this.logger.debug(formatCompact( { op: "kick", bot: this.$id, channel, user }));
541
+ this.logger.debug(formatCompact( { op: "kick", endpoint: this.$id, channel, user }));
496
542
  return true;
497
543
  } catch (error) {
498
- this.logger.error(`Slack Bot ${this.$id} 踢出用户失败:`, error);
544
+ this.logger.error(`Slack Endpoint ${this.$id} 踢出用户失败:`, error);
499
545
  throw error;
500
546
  }
501
547
  }
@@ -508,10 +554,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
508
554
  async setChannelTopic(channel: string, topic: string): Promise<boolean> {
509
555
  try {
510
556
  await this.client!.conversations.setTopic({ channel, topic });
511
- this.logger.debug(formatCompact( { op: "set_topic", bot: this.$id, channel }));
557
+ this.logger.debug(formatCompact( { op: "set_topic", endpoint: this.$id, channel }));
512
558
  return true;
513
559
  } catch (error) {
514
- this.logger.error(`Slack Bot ${this.$id} 设置话题失败:`, error);
560
+ this.logger.error(`Slack Endpoint ${this.$id} 设置话题失败:`, error);
515
561
  throw error;
516
562
  }
517
563
  }
@@ -524,10 +570,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
524
570
  async setChannelPurpose(channel: string, purpose: string): Promise<boolean> {
525
571
  try {
526
572
  await this.client!.conversations.setPurpose({ channel, purpose });
527
- this.logger.debug(formatCompact( { op: "set_purpose", bot: this.$id, channel }));
573
+ this.logger.debug(formatCompact( { op: "set_purpose", endpoint: this.$id, channel }));
528
574
  return true;
529
575
  } catch (error) {
530
- this.logger.error(`Slack Bot ${this.$id} 设置目的失败:`, error);
576
+ this.logger.error(`Slack Endpoint ${this.$id} 设置目的失败:`, error);
531
577
  throw error;
532
578
  }
533
579
  }
@@ -539,10 +585,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
539
585
  async archiveChannel(channel: string): Promise<boolean> {
540
586
  try {
541
587
  await this.client!.conversations.archive({ channel });
542
- this.logger.debug(formatCompact( { op: "archive", bot: this.$id, channel }));
588
+ this.logger.debug(formatCompact( { op: "archive", endpoint: this.$id, channel }));
543
589
  return true;
544
590
  } catch (error) {
545
- this.logger.error(`Slack Bot ${this.$id} 归档频道失败:`, error);
591
+ this.logger.error(`Slack Endpoint ${this.$id} 归档频道失败:`, error);
546
592
  throw error;
547
593
  }
548
594
  }
@@ -554,10 +600,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
554
600
  async unarchiveChannel(channel: string): Promise<boolean> {
555
601
  try {
556
602
  await this.client!.conversations.unarchive({ channel });
557
- this.logger.debug(formatCompact( { op: "unarchive", bot: this.$id, channel }));
603
+ this.logger.debug(formatCompact( { op: "unarchive", endpoint: this.$id, channel }));
558
604
  return true;
559
605
  } catch (error) {
560
- this.logger.error(`Slack Bot ${this.$id} 取消归档失败:`, error);
606
+ this.logger.error(`Slack Endpoint ${this.$id} 取消归档失败:`, error);
561
607
  throw error;
562
608
  }
563
609
  }
@@ -570,10 +616,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
570
616
  async renameChannel(channel: string, name: string): Promise<boolean> {
571
617
  try {
572
618
  await this.client!.conversations.rename({ channel, name });
573
- this.logger.debug(formatCompact( { op: "rename", bot: this.$id, channel, name }));
619
+ this.logger.debug(formatCompact( { op: "rename", endpoint: this.$id, channel, name }));
574
620
  return true;
575
621
  } catch (error) {
576
- this.logger.error(`Slack Bot ${this.$id} 重命名频道失败:`, error);
622
+ this.logger.error(`Slack Endpoint ${this.$id} 重命名频道失败:`, error);
577
623
  throw error;
578
624
  }
579
625
  }
@@ -587,7 +633,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
587
633
  const result = await this.client!.conversations.members({ channel });
588
634
  return result.members || [];
589
635
  } catch (error) {
590
- this.logger.error(`Slack Bot ${this.$id} 获取成员列表失败:`, error);
636
+ this.logger.error(`Slack Endpoint ${this.$id} 获取成员列表失败:`, error);
591
637
  throw error;
592
638
  }
593
639
  }
@@ -601,7 +647,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
601
647
  const result = await this.client!.conversations.info({ channel });
602
648
  return result.channel;
603
649
  } catch (error) {
604
- this.logger.error(`Slack Bot ${this.$id} 获取频道信息失败:`, error);
650
+ this.logger.error(`Slack Endpoint ${this.$id} 获取频道信息失败:`, error);
605
651
  throw error;
606
652
  }
607
653
  }
@@ -615,7 +661,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
615
661
  const result = await this.client!.users.info({ user });
616
662
  return result.user;
617
663
  } catch (error) {
618
- this.logger.error(`Slack Bot ${this.$id} 获取用户信息失败:`, error);
664
+ this.logger.error(`Slack Endpoint ${this.$id} 获取用户信息失败:`, error);
619
665
  throw error;
620
666
  }
621
667
  }
@@ -629,10 +675,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
629
675
  async addReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
630
676
  try {
631
677
  await this.client!.reactions.add({ channel, timestamp, name });
632
- this.logger.debug(formatCompact( { op: "reaction_add", bot: this.$id, name }));
678
+ this.logger.debug(formatCompact( { op: "reaction_add", endpoint: this.$id, name }));
633
679
  return true;
634
680
  } catch (error) {
635
- this.logger.error(`Slack Bot ${this.$id} 添加反应失败:`, error);
681
+ this.logger.error(`Slack Endpoint ${this.$id} 添加反应失败:`, error);
636
682
  throw error;
637
683
  }
638
684
  }
@@ -646,10 +692,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
646
692
  async removeReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
647
693
  try {
648
694
  await this.client!.reactions.remove({ channel, timestamp, name });
649
- this.logger.debug(formatCompact( { op: "reaction_remove", bot: this.$id, name }));
695
+ this.logger.debug(formatCompact( { op: "reaction_remove", endpoint: this.$id, name }));
650
696
  return true;
651
697
  } catch (error) {
652
- this.logger.error(`Slack Bot ${this.$id} 移除反应失败:`, error);
698
+ this.logger.error(`Slack Endpoint ${this.$id} 移除反应失败:`, error);
653
699
  throw error;
654
700
  }
655
701
  }
@@ -662,10 +708,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
662
708
  async pinMessage(channel: string, timestamp: string): Promise<boolean> {
663
709
  try {
664
710
  await this.client!.pins.add({ channel, timestamp });
665
- this.logger.debug(formatCompact( { op: "pin", bot: this.$id, channel }));
711
+ this.logger.debug(formatCompact( { op: "pin", endpoint: this.$id, channel }));
666
712
  return true;
667
713
  } catch (error) {
668
- this.logger.error(`Slack Bot ${this.$id} 置顶消息失败:`, error);
714
+ this.logger.error(`Slack Endpoint ${this.$id} 置顶消息失败:`, error);
669
715
  throw error;
670
716
  }
671
717
  }
@@ -678,10 +724,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
678
724
  async unpinMessage(channel: string, timestamp: string): Promise<boolean> {
679
725
  try {
680
726
  await this.client!.pins.remove({ channel, timestamp });
681
- this.logger.debug(formatCompact( { op: "unpin", bot: this.$id, channel }));
727
+ this.logger.debug(formatCompact( { op: "unpin", endpoint: this.$id, channel }));
682
728
  return true;
683
729
  } catch (error) {
684
- this.logger.error(`Slack Bot ${this.$id} 取消置顶失败:`, error);
730
+ this.logger.error(`Slack Endpoint ${this.$id} 取消置顶失败:`, error);
685
731
  throw error;
686
732
  }
687
733
  }
package/src/index.ts CHANGED
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import { usePlugin, type Plugin, type IGroupManagement, createGroupManagementTools, type ToolFeature } from "zhin.js";
5
5
  import { SlackAdapter } from "./adapter.js";
6
+ import {
7
+ platformPermit,
8
+ registerSlackPlatformPermitChecker,
9
+ slackGroupPermitResolver,
10
+ } from "./platform-permit.js";
6
11
 
7
12
  declare module "zhin.js" {
8
13
  interface Adapters {
@@ -11,7 +16,7 @@ declare module "zhin.js" {
11
16
  }
12
17
 
13
18
  export * from "./types.js";
14
- export { SlackBot } from "./bot.js";
19
+ export { SlackEndpoint } from "./endpoint.js";
15
20
  export { SlackAdapter } from "./adapter.js";
16
21
 
17
22
  const plugin = usePlugin();
@@ -19,7 +24,7 @@ const { provide, useContext } = plugin;
19
24
 
20
25
  provide({
21
26
  name: "slack",
22
- description: "Slack Bot Adapter",
27
+ description: "Slack Endpoint Adapter",
23
28
  mounted: async (p: Plugin) => {
24
29
  const adapter = new SlackAdapter(p);
25
30
  await adapter.start();
@@ -31,16 +36,19 @@ provide({
31
36
  });
32
37
 
33
38
  useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
39
+ const disposers: (() => void)[] = [];
40
+ disposers.push(registerSlackPlatformPermitChecker());
34
41
  const groupTools = createGroupManagementTools(
35
42
  slack as unknown as IGroupManagement,
36
43
  'slack',
44
+ { permitResolver: slackGroupPermitResolver, registerChecker: false },
37
45
  );
38
- const disposers: (() => void)[] = groupTools.map(t => toolService.addTool(t, plugin.name));
46
+ disposers.push(...groupTools.map(t => toolService.addTool(t, plugin.name)));
39
47
 
40
- function getBot(botId: string) {
41
- const bot = slack.bots.get(botId);
42
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
43
- return bot;
48
+ function getEndpoint(endpointId: string) {
49
+ const endpoint = slack.endpoints.get(endpointId);
50
+ if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
51
+ return endpoint;
44
52
  }
45
53
 
46
54
  disposers.push(toolService.addTool({
@@ -49,17 +57,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
49
57
  parameters: {
50
58
  type: 'object',
51
59
  properties: {
52
- bot: { type: 'string', description: 'Bot 名称' },
60
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
53
61
  channel: { type: 'string', description: '频道 ID' },
54
62
  users: { type: 'string', description: '用户 ID 列表(逗号分隔)' },
55
63
  },
56
- required: ['bot', 'channel', 'users'],
64
+ required: ['endpoint_id', 'channel', 'users'],
57
65
  },
58
66
  platforms: ['slack'],
59
67
  tags: ['slack'],
68
+ permissions: [platformPermit('channel_manager')],
60
69
  execute: async (args: Record<string, any>) => {
61
- const bot = getBot(args.bot);
62
- const success = await bot.inviteToChannel(args.channel, args.users.split(','));
70
+ const endpoint = getEndpoint(args.endpoint_id);
71
+ const success = await endpoint.inviteToChannel(args.channel, args.users.split(','));
63
72
  return { success, message: success ? '已邀请用户加入频道' : '操作失败' };
64
73
  },
65
74
  }, plugin.name));
@@ -70,17 +79,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
70
79
  parameters: {
71
80
  type: 'object',
72
81
  properties: {
73
- bot: { type: 'string', description: 'Bot 名称' },
82
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
74
83
  channel: { type: 'string', description: '频道 ID' },
75
84
  topic: { type: 'string', description: '新话题' },
76
85
  },
77
- required: ['bot', 'channel', 'topic'],
86
+ required: ['endpoint_id', 'channel', 'topic'],
78
87
  },
79
88
  platforms: ['slack'],
80
89
  tags: ['slack'],
90
+ permissions: [platformPermit('channel_manager')],
81
91
  execute: async (args: Record<string, any>) => {
82
- const bot = getBot(args.bot);
83
- const success = await bot.setChannelTopic(args.channel, args.topic);
92
+ const endpoint = getEndpoint(args.endpoint_id);
93
+ const success = await endpoint.setChannelTopic(args.channel, args.topic);
84
94
  return { success, message: success ? '已设置频道话题' : '操作失败' };
85
95
  },
86
96
  }, plugin.name));
@@ -91,16 +101,17 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
91
101
  parameters: {
92
102
  type: 'object',
93
103
  properties: {
94
- bot: { type: 'string', description: 'Bot 名称' },
104
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
95
105
  channel: { type: 'string', description: '频道 ID' },
96
106
  },
97
- required: ['bot', 'channel'],
107
+ required: ['endpoint_id', 'channel'],
98
108
  },
99
109
  platforms: ['slack'],
100
110
  tags: ['slack'],
111
+ permissions: [platformPermit('workspace_admin')],
101
112
  execute: async (args: Record<string, any>) => {
102
- const bot = getBot(args.bot);
103
- const success = await bot.archiveChannel(args.channel);
113
+ const endpoint = getEndpoint(args.endpoint_id);
114
+ const success = await endpoint.archiveChannel(args.channel);
104
115
  return { success, message: success ? '已归档频道' : '操作失败' };
105
116
  },
106
117
  }, plugin.name));
@@ -111,17 +122,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
111
122
  parameters: {
112
123
  type: 'object',
113
124
  properties: {
114
- bot: { type: 'string', description: 'Bot 名称' },
125
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
115
126
  channel: { type: 'string', description: '频道 ID' },
116
127
  timestamp: { type: 'string', description: '消息时间戳' },
117
128
  },
118
- required: ['bot', 'channel', 'timestamp'],
129
+ required: ['endpoint_id', 'channel', 'timestamp'],
119
130
  },
120
131
  platforms: ['slack'],
121
132
  tags: ['slack'],
133
+ permissions: [platformPermit('channel_manager')],
122
134
  execute: async (args: Record<string, any>) => {
123
- const bot = getBot(args.bot);
124
- const success = await bot.pinMessage(args.channel, args.timestamp);
135
+ const endpoint = getEndpoint(args.endpoint_id);
136
+ const success = await endpoint.pinMessage(args.channel, args.timestamp);
125
137
  return { success, message: success ? '已置顶消息' : '操作失败' };
126
138
  },
127
139
  }, plugin.name));
@@ -132,18 +144,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
132
144
  parameters: {
133
145
  type: 'object',
134
146
  properties: {
135
- bot: { type: 'string', description: 'Bot 名称' },
147
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
136
148
  channel: { type: 'string', description: '频道 ID' },
137
149
  timestamp: { type: 'string', description: '消息时间戳' },
138
150
  emoji: { type: 'string', description: '表情名称(不含冒号)' },
139
151
  },
140
- required: ['bot', 'channel', 'timestamp', 'emoji'],
152
+ required: ['endpoint_id', 'channel', 'timestamp', 'emoji'],
141
153
  },
142
154
  platforms: ['slack'],
143
155
  tags: ['slack'],
144
156
  execute: async (args: Record<string, any>) => {
145
- const bot = getBot(args.bot);
146
- const success = await bot.addReaction(args.channel, args.timestamp, args.emoji);
157
+ const endpoint = getEndpoint(args.endpoint_id);
158
+ const success = await endpoint.addReaction(args.channel, args.timestamp, args.emoji);
147
159
  return { success, message: success ? `已添加反应 :${args.emoji}:` : '操作失败' };
148
160
  },
149
161
  }, plugin.name));
@@ -154,18 +166,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
154
166
  parameters: {
155
167
  type: 'object',
156
168
  properties: {
157
- bot: { type: 'string', description: 'Bot 名称' },
169
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
158
170
  channel_id: { type: 'string', description: '频道 ID' },
159
171
  timestamp: { type: 'string', description: '消息时间戳' },
160
172
  name: { type: 'string', description: '表情名称(如 thumbsup、heart)' },
161
173
  },
162
- required: ['bot', 'channel_id', 'timestamp', 'name'],
174
+ required: ['endpoint_id', 'channel_id', 'timestamp', 'name'],
163
175
  },
164
176
  platforms: ['slack'],
165
177
  tags: ['slack'],
166
178
  execute: async (args: Record<string, any>) => {
167
- const bot = getBot(args.bot);
168
- const success = await bot.removeReaction(args.channel_id, args.timestamp, args.name);
179
+ const endpoint = getEndpoint(args.endpoint_id);
180
+ const success = await endpoint.removeReaction(args.channel_id, args.timestamp, args.name);
169
181
  return { success, message: success ? `已移除反应 :${args.name}:` : '操作失败' };
170
182
  },
171
183
  }, plugin.name));
@@ -176,17 +188,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
176
188
  parameters: {
177
189
  type: 'object',
178
190
  properties: {
179
- bot: { type: 'string', description: 'Bot 名称' },
191
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
180
192
  channel_id: { type: 'string', description: '频道 ID' },
181
193
  timestamp: { type: 'string', description: '消息时间戳' },
182
194
  },
183
- required: ['bot', 'channel_id', 'timestamp'],
195
+ required: ['endpoint_id', 'channel_id', 'timestamp'],
184
196
  },
185
197
  platforms: ['slack'],
186
198
  tags: ['slack'],
199
+ permissions: [platformPermit('channel_manager')],
187
200
  execute: async (args: Record<string, any>) => {
188
- const bot = getBot(args.bot);
189
- const success = await bot.unpinMessage(args.channel_id, args.timestamp);
201
+ const endpoint = getEndpoint(args.endpoint_id);
202
+ const success = await endpoint.unpinMessage(args.channel_id, args.timestamp);
190
203
  return { success, message: success ? '已取消置顶' : '操作失败' };
191
204
  },
192
205
  }, plugin.name));
@@ -197,16 +210,16 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
197
210
  parameters: {
198
211
  type: 'object',
199
212
  properties: {
200
- bot: { type: 'string', description: 'Bot 名称' },
213
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
201
214
  user_id: { type: 'string', description: '用户 ID' },
202
215
  },
203
- required: ['bot', 'user_id'],
216
+ required: ['endpoint_id', 'user_id'],
204
217
  },
205
218
  platforms: ['slack'],
206
219
  tags: ['slack'],
207
220
  execute: async (args: Record<string, any>) => {
208
- const bot = getBot(args.bot);
209
- const user = await bot.getUserInfo(args.user_id);
221
+ const endpoint = getEndpoint(args.endpoint_id);
222
+ const user = await endpoint.getUserInfo(args.user_id);
210
223
  return {
211
224
  id: user.id,
212
225
  name: user.name,
@@ -226,17 +239,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
226
239
  parameters: {
227
240
  type: 'object',
228
241
  properties: {
229
- bot: { type: 'string', description: 'Bot 名称' },
242
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
230
243
  channel_id: { type: 'string', description: '频道 ID' },
231
244
  purpose: { type: 'string', description: '频道用途描述' },
232
245
  },
233
- required: ['bot', 'channel_id', 'purpose'],
246
+ required: ['endpoint_id', 'channel_id', 'purpose'],
234
247
  },
235
248
  platforms: ['slack'],
236
249
  tags: ['slack'],
250
+ permissions: [platformPermit('channel_manager')],
237
251
  execute: async (args: Record<string, any>) => {
238
- const bot = getBot(args.bot);
239
- const success = await bot.setChannelPurpose(args.channel_id, args.purpose);
252
+ const endpoint = getEndpoint(args.endpoint_id);
253
+ const success = await endpoint.setChannelPurpose(args.channel_id, args.purpose);
240
254
  return { success, message: success ? '频道用途已更新' : '操作失败' };
241
255
  },
242
256
  }, plugin.name));
@@ -247,16 +261,17 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
247
261
  parameters: {
248
262
  type: 'object',
249
263
  properties: {
250
- bot: { type: 'string', description: 'Bot 名称' },
264
+ endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
251
265
  channel_id: { type: 'string', description: '频道 ID' },
252
266
  },
253
- required: ['bot', 'channel_id'],
267
+ required: ['endpoint_id', 'channel_id'],
254
268
  },
255
269
  platforms: ['slack'],
256
270
  tags: ['slack'],
271
+ permissions: [platformPermit('workspace_admin')],
257
272
  execute: async (args: Record<string, any>) => {
258
- const bot = getBot(args.bot);
259
- const success = await bot.unarchiveChannel(args.channel_id);
273
+ const endpoint = getEndpoint(args.endpoint_id);
274
+ const success = await endpoint.unarchiveChannel(args.channel_id);
260
275
  return { success, message: success ? '频道已恢复' : '操作失败' };
261
276
  },
262
277
  }, plugin.name));
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Slack platform permit — workspace + channel 权限
3
+ */
4
+ import type { Message } from 'zhin.js';
5
+ import { registerPlatformPermitChecker } from 'zhin.js';
6
+
7
+ const ADAPTER = 'slack';
8
+
9
+ export function platformPermit(perm: string): string {
10
+ return `platform(${ADAPTER},${perm})`;
11
+ }
12
+
13
+ const FACTORY_PERM_MAP: Record<string, string> = {
14
+ group_admin: 'channel_manager',
15
+ group_owner: 'workspace_owner',
16
+ };
17
+
18
+ export function slackGroupPermitResolver(logicalPerm: string): string {
19
+ return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
20
+ }
21
+
22
+ export function normalizeSlackSenderForPermit(input: {
23
+ isWorkspaceOwner?: boolean;
24
+ isWorkspaceAdmin?: boolean;
25
+ isChannelManager?: boolean;
26
+ }): { role?: string; permissions?: string[] } {
27
+ const permissions: string[] = [];
28
+ if (input.isWorkspaceOwner) {
29
+ permissions.push('workspace_owner', 'workspace_admin', 'channel_manager');
30
+ return { role: 'owner', permissions };
31
+ }
32
+ if (input.isWorkspaceAdmin) {
33
+ permissions.push('workspace_admin', 'channel_manager');
34
+ return { role: 'admin', permissions };
35
+ }
36
+ if (input.isChannelManager) {
37
+ permissions.push('channel_manager');
38
+ return { role: 'channel_admin', permissions };
39
+ }
40
+ return { role: 'member', permissions };
41
+ }
42
+
43
+ export function checkSlackPlatformPermit(perm: string, message: Message<any>): boolean {
44
+ const sender = message.$sender as { role?: string; permissions?: string[] };
45
+ const permissions = sender.permissions ?? [];
46
+ const role = sender.role;
47
+ const has = (t: string) => permissions.includes(t);
48
+
49
+ switch (perm) {
50
+ case 'workspace_owner':
51
+ return role === 'owner' || has('workspace_owner');
52
+ case 'workspace_admin':
53
+ return role === 'owner' || role === 'admin' || has('workspace_admin') || has('workspace_owner');
54
+ case 'channel_manager':
55
+ return role === 'owner' || role === 'admin' || role === 'channel_admin'
56
+ || has('channel_manager') || has('workspace_admin') || has('workspace_owner');
57
+ default:
58
+ return false;
59
+ }
60
+ }
61
+
62
+ export function registerSlackPlatformPermitChecker(): () => void {
63
+ return registerPlatformPermitChecker(ADAPTER, checkSlackPlatformPermit);
64
+ }
package/src/types.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import type { LogLevel } from "@slack/bolt";
5
5
  import type { KnownEventFromType } from "@slack/bolt";
6
6
 
7
- export interface SlackBotConfig {
7
+ export interface SlackEndpointConfig {
8
8
  context: "slack";
9
9
  token: string;
10
10
  name: string;
package/lib/bot.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,GAAG,EAAE,OAAO,EAAwC,WAAW,EAAE,MAAM,SAAS,CAAC;AACzG,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,qBAAa,QAAS,YAAW,GAAG,CAAC,cAAc,EAAE,iBAAiB,CAAC;;IAclD,OAAO,EAAE,YAAY;IAAS,OAAO,EAAE,cAAc;IAbxE,UAAU,EAAE,OAAO,CAAC;IACpB,sFAAsF;IACtF,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B,IAAI,MAAM,6BAET;IAED,IAAI,GAAG,WAEN;gBAEkB,OAAO,EAAE,YAAY,EAAS,OAAO,EAAE,cAAc;IA0BlE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCzB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;YAepB,kBAAkB;IAahC,cAAc,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAoElE,OAAO,CAAC,mBAAmB;IAiF3B,OAAO,CAAC,cAAc;IA+FhB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;YAgB3C,oBAAoB;IAmF5B,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/C;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzE;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvE;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW3E;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvD;;;OAGG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzD;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWpE;;;OAGG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAU3D;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAUnD;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAU7C;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrF;;;;;OAKG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWxF;;;;OAIG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUzE"}