@zhin.js/adapter-slack 1.1.26 → 1.1.27

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 (46) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/LICENSE +21 -0
  3. package/PERMITS.md +10 -17
  4. package/README.md +8 -4
  5. package/agents/slack/conversation.js +15 -0
  6. package/agents/slack/conversation.ts +17 -0
  7. package/agents/slack/messages.js +4 -0
  8. package/agents/slack/messages.ts +4 -0
  9. package/agents/slack/skills/slack-channels/tools/archive_channel/index.js +6 -7
  10. package/agents/slack/skills/slack-channels/tools/archive_channel/index.ts +7 -10
  11. package/agents/slack/skills/slack-channels/tools/invite_to_channel/index.js +5 -5
  12. package/agents/slack/skills/slack-channels/tools/invite_to_channel/index.ts +5 -6
  13. package/agents/slack/skills/slack-channels/tools/set_purpose/index.js +5 -5
  14. package/agents/slack/skills/slack-channels/tools/set_purpose/index.ts +5 -6
  15. package/agents/slack/skills/slack-channels/tools/set_topic/index.js +5 -5
  16. package/agents/slack/skills/slack-channels/tools/set_topic/index.ts +5 -6
  17. package/agents/slack/skills/slack-channels/tools/unarchive/index.js +2 -3
  18. package/agents/slack/skills/slack-channels/tools/unarchive/index.ts +2 -3
  19. package/agents/slack/skills/slack-messages/tools/add_reaction/index.js +5 -4
  20. package/agents/slack/skills/slack-messages/tools/add_reaction/index.ts +5 -5
  21. package/agents/slack/skills/slack-messages/tools/edit_message/index.js +6 -5
  22. package/agents/slack/skills/slack-messages/tools/edit_message/index.ts +6 -6
  23. package/agents/slack/skills/slack-messages/tools/pin_message/index.js +5 -5
  24. package/agents/slack/skills/slack-messages/tools/pin_message/index.ts +5 -6
  25. package/agents/slack/skills/slack-messages/tools/remove_reaction/index.js +5 -4
  26. package/agents/slack/skills/slack-messages/tools/remove_reaction/index.ts +5 -5
  27. package/agents/slack/skills/slack-messages/tools/unpin_message/index.js +5 -5
  28. package/agents/slack/skills/slack-messages/tools/unpin_message/index.ts +5 -6
  29. package/lib/index.d.ts +2 -2
  30. package/lib/index.js +2 -2
  31. package/lib/platform-permit.d.ts +4 -12
  32. package/lib/platform-permit.js +7 -41
  33. package/lib/protocol.d.ts +1 -5
  34. package/lib/protocol.js +1 -19
  35. package/lib/slack-message-ref.d.ts +0 -1
  36. package/lib/slack-message-ref.js +0 -3
  37. package/lib/webhook.d.ts +1 -2
  38. package/lib/webhook.js +12 -29
  39. package/package.json +24 -26
  40. package/plugin.js +0 -6
  41. package/schema.json +0 -56
  42. package/src/index.ts +1 -7
  43. package/src/platform-permit.ts +5 -54
  44. package/src/protocol.ts +2 -23
  45. package/src/slack-message-ref.ts +0 -4
  46. package/src/webhook.ts +12 -32
package/schema.json CHANGED
@@ -17,34 +17,6 @@
17
17
  "default": 15000,
18
18
  "description": "Socket Mode client ping timeout (ms)"
19
19
  },
20
- "master": {
21
- "type": [
22
- "string",
23
- "number"
24
- ],
25
- "description": "框架 master(Slack user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖",
26
- "x-i18n": {
27
- "en": {
28
- "description": "Framework masters (Slack user id; AI/tool permissions and endpoint management). endpoints[i].master can override this value."
29
- }
30
- }
31
- },
32
- "trusted": {
33
- "type": "array",
34
- "items": {
35
- "type": [
36
- "string",
37
- "number"
38
- ],
39
- "description": "Trusted Slack user id"
40
- },
41
- "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加",
42
- "x-i18n": {
43
- "en": {
44
- "description": "Framework trusted users, below master authority. endpoints[i].trusted can add users."
45
- }
46
- }
47
- },
48
20
  "endpoints": {
49
21
  "type": "array",
50
22
  "minItems": 1,
@@ -53,34 +25,6 @@
53
25
  "type": "object",
54
26
  "additionalProperties": false,
55
27
  "properties": {
56
- "master": {
57
- "type": [
58
- "string",
59
- "number"
60
- ],
61
- "description": "本 endpoint 的框架 master(Slack user id);覆盖顶层 master",
62
- "x-i18n": {
63
- "en": {
64
- "description": "Framework masters for this endpoint (Slack user id), overriding the top-level master list."
65
- }
66
- }
67
- },
68
- "trusted": {
69
- "type": "array",
70
- "items": {
71
- "type": [
72
- "string",
73
- "number"
74
- ],
75
- "description": "Trusted Slack user id"
76
- },
77
- "description": "本 endpoint 的 trusted 列表",
78
- "x-i18n": {
79
- "en": {
80
- "description": "Trusted users for this endpoint."
81
- }
82
- }
83
- },
84
28
  "token": {
85
29
  "type": "string",
86
30
  "description": "Bot User OAuth Token (xoxb-...)"
package/src/index.ts CHANGED
@@ -6,7 +6,6 @@ export {
6
6
  headerValue,
7
7
  keyboardToBlockKitBlocks,
8
8
  normalizeWebhookPath,
9
- readTextBody,
10
9
  resolveSlackChannelType,
11
10
  resolveSlackConfig,
12
11
  slackInboundConversation,
@@ -41,12 +40,7 @@ export {
41
40
  type SlackUserInfo,
42
41
  } from './client.js';
43
42
 
44
- export {
45
- checkSlackPlatformPermit,
46
- normalizeSlackSenderForPermit,
47
- platformPermit,
48
- slackGroupPermitResolver,
49
- } from './platform-permit.js';
43
+ export { platformPermit } from './platform-permit.js';
50
44
 
51
45
  export { normalizeSlackReactionName } from './slack-reaction.js';
52
46
  export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
@@ -1,59 +1,10 @@
1
1
  /**
2
- * Slack platform permit — workspace + channel 权限
2
+ * Slack platform permit helper(ADR 0056 §5)。
3
+ *
4
+ * 平台身份由适配器上报到 `sender.platform`,permit 由内置评估器(`@zhin.js/permission`)判断;
5
+ * 适配器不再导出自有的 permit 解析器或检查函数。
3
6
  */
4
- import type { PermissionSubject } from '@zhin.js/permission';
5
-
6
- const ADAPTER = 'slack';
7
7
 
8
8
  export function platformPermit(perm: string): string {
9
- return `platform(${ADAPTER},${perm})`;
10
- }
11
-
12
- const FACTORY_PERM_MAP: Record<string, string> = {
13
- scene_admin: 'channel_manager',
14
- scene_owner: 'workspace_owner',
15
- };
16
-
17
- export function slackGroupPermitResolver(logicalPerm: string): string {
18
- return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
19
- }
20
-
21
- export function normalizeSlackSenderForPermit(input: {
22
- isWorkspaceOwner?: boolean;
23
- isWorkspaceAdmin?: boolean;
24
- isChannelManager?: boolean;
25
- }): { role?: string; permissions?: string[] } {
26
- const permissions: string[] = [];
27
- if (input.isWorkspaceOwner) {
28
- permissions.push('workspace_owner', 'workspace_admin', 'channel_manager');
29
- return { role: 'owner', permissions };
30
- }
31
- if (input.isWorkspaceAdmin) {
32
- permissions.push('workspace_admin', 'channel_manager');
33
- return { role: 'admin', permissions };
34
- }
35
- if (input.isChannelManager) {
36
- permissions.push('channel_manager');
37
- return { role: 'channel_admin', permissions };
38
- }
39
- return { role: 'member', permissions };
9
+ return `platform-permission:${perm}`;
40
10
  }
41
-
42
- export function checkSlackPlatformPermit(perm: string, subject: PermissionSubject): boolean {
43
- const role = subject.sender?.role?.[0];
44
- const permissions = subject.sender?.permissions ?? [];
45
- const has = (t: string) => permissions.includes(t);
46
-
47
- switch (perm) {
48
- case 'workspace_owner':
49
- return role === 'owner' || has('workspace_owner');
50
- case 'workspace_admin':
51
- return role === 'owner' || role === 'admin' || has('workspace_admin') || has('workspace_owner');
52
- case 'channel_manager':
53
- return role === 'owner' || role === 'admin' || role === 'channel_admin'
54
- || has('channel_manager') || has('workspace_admin') || has('workspace_owner');
55
- default:
56
- return false;
57
- }
58
- }
59
-
package/src/protocol.ts CHANGED
@@ -4,7 +4,6 @@ import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
4
4
  * Canonicalization is owned by gateway/core before endpoint.send.
5
5
  */
6
6
  import { createHmac, timingSafeEqual } from 'node:crypto';
7
- import type { IncomingMessage } from 'node:http';
8
7
  import { isMediaRef, type MediaRef, type ConversationKind, type ConversationRef } from '@zhin.js/im-contract';
9
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
10
9
  import { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
@@ -417,30 +416,10 @@ export function verifySlackSignature(
417
416
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
418
417
  }
419
418
 
420
- export async function readTextBody(
421
- request: IncomingMessage,
422
- options: { readonly limit?: number } = {},
423
- ): Promise<string> {
424
- const limit = options.limit ?? 1_048_576;
425
- const chunks: Buffer[] = [];
426
- let size = 0;
427
- for await (const chunk of request) {
428
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
429
- size += buffer.length;
430
- if (size > limit) {
431
- request.destroy();
432
- throw new Error(`Request body exceeds ${limit} bytes`);
433
- }
434
- chunks.push(buffer);
435
- }
436
- return Buffer.concat(chunks).toString('utf8');
437
- }
438
419
 
439
420
  export function headerValue(
440
- headers: IncomingMessage['headers'],
421
+ headers: Headers,
441
422
  name: string,
442
423
  ): string {
443
- const raw = headers[name.toLowerCase()];
444
- if (Array.isArray(raw)) return raw[0] ?? '';
445
- return raw ?? '';
424
+ return headers.get(name) ?? '';
446
425
  }
@@ -12,7 +12,3 @@ export function parseSlackMessageRef(ref: string): { channel: string; ts: string
12
12
  if (!channel || !ts) return null;
13
13
  return { channel, ts };
14
14
  }
15
-
16
- export function slackMessageTs(ref: string): string {
17
- return parseSlackMessageRef(ref)?.ts ?? ref;
18
- }
package/src/webhook.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Slack webhook HTTP: signature → parse → admit.
3
3
  */
4
- import type { IncomingMessage, ServerResponse } from 'node:http';
5
4
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
5
+ import { readTextBody } from '@zhin.js/host-http-contract';
6
6
  import { formatCompact, getLogger } from '@zhin.js/logger';
7
7
  import {
8
8
  headerValue,
9
- readTextBody,
10
9
  verifySlackSignature,
11
10
  type ResolvedSlackConfig,
12
11
  type SlackInteractionPayload,
@@ -29,26 +28,21 @@ export function registerSlackWebhookRoutes(
29
28
  ): HttpRouteRegistration[] {
30
29
  const path = handler.config.webhookPath;
31
30
  return [
32
- http.route('POST', path, async (request, response) => {
33
- await handleSlackWebhookRequest(request, response, handler);
34
- }, { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
31
+ http.route('POST', path, (request) => handleSlackWebhookRequest(request, handler), { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
35
32
  ];
36
33
  }
37
34
 
38
35
  export async function handleSlackWebhookRequest(
39
- request: IncomingMessage,
40
- response: ServerResponse,
36
+ request: Request,
41
37
  handler: SlackWebhookHandler,
42
- ): Promise<void> {
38
+ ): Promise<Response> {
43
39
  try {
44
40
  const rawBody = await readTextBody(request);
45
41
  const timestamp = headerValue(request.headers, 'x-slack-request-timestamp');
46
42
  const signature = headerValue(request.headers, 'x-slack-signature');
47
43
 
48
44
  if (!verifySlackSignature(handler.config.signingSecret, rawBody, timestamp, signature)) {
49
- response.writeHead(401, { 'Content-Type': 'text/plain' });
50
- response.end('Invalid signature');
51
- return;
45
+ return new Response('Invalid signature', { status: 401, headers: { 'Content-Type': 'text/plain' } });
52
46
  }
53
47
 
54
48
  const contentType = headerValue(request.headers, 'content-type');
@@ -57,17 +51,13 @@ export async function handleSlackWebhookRequest(
57
51
  const payloadStr = params.get('payload');
58
52
  if (payloadStr) {
59
53
  const payload = JSON.parse(payloadStr) as SlackInteractionPayload;
60
- response.writeHead(200);
61
- response.end('');
62
54
  handler.admitInteraction(payload);
63
- return;
55
+ return new Response('', { status: 200 });
64
56
  }
65
57
  const body = Object.fromEntries(params) as unknown as SlackSlashCommand;
66
58
  if (body.command) {
67
- response.writeHead(200);
68
- response.end('');
69
59
  handler.admitSlashCommand(body);
70
- return;
60
+ return new Response('', { status: 200 });
71
61
  }
72
62
  }
73
63
 
@@ -75,32 +65,22 @@ export async function handleSlackWebhookRequest(
75
65
  try {
76
66
  envelope = JSON.parse(rawBody) as Record<string, unknown>;
77
67
  } catch {
78
- response.writeHead(200);
79
- response.end('');
80
- return;
68
+ return new Response('', { status: 200 });
81
69
  }
82
70
 
83
71
  if (envelope.type === 'url_verification') {
84
72
  const challenge = (envelope as unknown as SlackUrlVerification).challenge;
85
- response.writeHead(200, { 'Content-Type': 'application/json' });
86
- response.end(JSON.stringify({ challenge }));
87
- return;
73
+ return new Response(JSON.stringify({ challenge }), { status: 200, headers: { 'Content-Type': 'application/json' } });
88
74
  }
89
75
 
90
76
  if (envelope.type === 'event_callback' && envelope.event) {
91
- response.writeHead(200);
92
- response.end('');
93
77
  handler.handleEnvelope(envelope);
94
- return;
78
+ return new Response('', { status: 200 });
95
79
  }
96
80
 
97
- response.writeHead(200);
98
- response.end('');
81
+ return new Response('', { status: 200 });
99
82
  } catch (error) {
100
83
  logger.error('Slack webhook error:', error);
101
- if (!response.headersSent) {
102
- response.writeHead(200);
103
- response.end('');
104
- }
84
+ return new Response('', { status: 200 });
105
85
  }
106
86
  }