@zhin.js/adapter-kook 7.0.2 → 7.0.5

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,100 @@
1
1
  # @zhin.js/adapter-kook
2
2
 
3
+ ## 7.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
8
+
9
+ All adapter platform-permit checkers now accept PermissionSubject
10
+ (duck-typed from Message/CommandSession) instead of Message directly.
11
+ registerPlatformPermitChecker is replaced by host.registerPlatform in
12
+ the adapter plugin setup. activity-feedback service updated for new
13
+ PermissionHost integration.
14
+
15
+ - Updated dependencies [6f9c366]
16
+ - Updated dependencies [373a56b]
17
+ - Updated dependencies [0de46a8]
18
+ - Updated dependencies [c106ecc]
19
+ - Updated dependencies [ca92e03]
20
+ - Updated dependencies [b0f37ae]
21
+ - Updated dependencies [ba08a2f]
22
+ - Updated dependencies [b08f7fe]
23
+ - Updated dependencies [daffd4c]
24
+ - Updated dependencies [36c7400]
25
+ - Updated dependencies [a9fa72e]
26
+ - Updated dependencies [c8de3ef]
27
+ - Updated dependencies [60f0fc8]
28
+ - Updated dependencies [574c990]
29
+ - Updated dependencies [d096f16]
30
+ - Updated dependencies [3f29623]
31
+ - Updated dependencies [2916852]
32
+ - Updated dependencies [d047869]
33
+ - Updated dependencies [162fa34]
34
+ - Updated dependencies [e62561e]
35
+ - Updated dependencies [3eeeb46]
36
+ - Updated dependencies [85d0f82]
37
+ - Updated dependencies [61bfc1c]
38
+ - Updated dependencies [04bad47]
39
+ - Updated dependencies [e40b048]
40
+ - Updated dependencies [5a5b1bb]
41
+ - Updated dependencies [f1708c3]
42
+ - Updated dependencies [d254a81]
43
+ - Updated dependencies [7123c47]
44
+ - Updated dependencies [05befc1]
45
+ - Updated dependencies [0663b6a]
46
+ - Updated dependencies [f28f9b3]
47
+ - Updated dependencies [9f340f7]
48
+ - Updated dependencies [e53444f]
49
+ - Updated dependencies [5eedd26]
50
+ - Updated dependencies [92b0dd7]
51
+ - Updated dependencies [f919b6f]
52
+ - Updated dependencies [098e411]
53
+ - Updated dependencies [e1b7c01]
54
+ - Updated dependencies [9b94f87]
55
+ - Updated dependencies [a7df753]
56
+ - @zhin.js/agent@1.1.5
57
+ - @zhin.js/host-http@1.0.8
58
+ - @zhin.js/permission@1.0.1
59
+ - @zhin.js/im-contract@1.0.3
60
+ - @zhin.js/adapter@1.1.7
61
+ - @zhin.js/plugin-runtime@1.1.5
62
+ - @zhin.js/command@1.0.9
63
+ - @zhin.js/core@1.5.4
64
+ - zhin.js@6.0.4
65
+
66
+ ## 7.0.4
67
+
68
+ ### Patch Changes
69
+
70
+ - f8c7a54: fix: im
71
+ - Updated dependencies [f8c7a54]
72
+ - @zhin.js/logger@1.0.76
73
+ - @zhin.js/host-http@1.0.7
74
+ - @zhin.js/adapter@1.1.6
75
+ - @zhin.js/agent@1.1.4
76
+ - @zhin.js/command@1.0.8
77
+ - @zhin.js/core@1.5.3
78
+ - @zhin.js/im-contract@1.0.2
79
+ - @zhin.js/plugin-runtime@1.1.4
80
+ - zhin.js@6.0.3
81
+
82
+ ## 7.0.3
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies [afc0e66]
87
+ - Updated dependencies [2e41ad5]
88
+ - Updated dependencies [9f57124]
89
+ - @zhin.js/core@1.5.2
90
+ - @zhin.js/adapter@1.1.5
91
+ - @zhin.js/im-contract@1.0.1
92
+ - @zhin.js/plugin-runtime@1.1.3
93
+ - @zhin.js/command@1.0.7
94
+ - @zhin.js/host-http@1.0.6
95
+ - @zhin.js/agent@1.1.3
96
+ - zhin.js@6.0.2
97
+
3
98
  ## 7.0.2
4
99
 
5
100
  ### Patch Changes
package/README.md CHANGED
@@ -7,7 +7,7 @@ Zhin.js KOOK(开黑啦)适配器(Plugin Runtime),默认通过 **WebSoc
7
7
  - WebSocket Gateway 入站(默认;无需公网 HTTPS / host)
8
8
  - Webhook 入站(`connection: webhook` + `httpHostToken` + `verify_token`)
9
9
  - 解析频道与私聊文本消息
10
- - 出站 `send({ target, payload })` → KOOK KMarkdown(`channel:id` / `private:id`)
10
+ - 出站 `send({ conversation, payload })` → KOOK KMarkdown(`kind: 'channel' | 'private'`)
11
11
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
12
 
13
13
  ## 安装
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-kook
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
  - **WebSocket 路径无需** `@zhin.js/host-http` / `@zhin.js/host-router`
27
27
 
28
- 入站:`gateway.receive({ adapter, target: 'channel:…'|'private:…', content, sender, metadata })`
29
- 出站:`send({ target, payload })` → `sendChannelMsg` / `sendPrivateMsg`
28
+ 入站:`gateway.receive({ conversation, message, content, sender, metadata })`(ConversationRef:`channel` 频道消息带 guild `parent`,`private` 私聊)
29
+ 出站:`send({ conversation, payload })` → `sendChannelMsg` / `sendPrivateMsg`
30
30
 
31
31
  ## 前置条件
32
32
 
package/adapters/kook.js CHANGED
@@ -21,8 +21,8 @@ export default defineAdapter({
21
21
  const config = resolveKookConfig(context.config);
22
22
  const gateway = context.use(messageGatewayToken);
23
23
  // 注册到插件运行时状态(kook.endpoint list 的"运行中"数据源)
24
- context.use(kookRuntimeStateToken).endpoints.set(config.name, {
25
- name: config.name,
24
+ context.use(kookRuntimeStateToken).endpoints.set(config.id, {
25
+ id: config.id,
26
26
  mode: config.connection,
27
27
  });
28
28
  if (config.connection === 'webhook') {
package/adapters/kook.ts CHANGED
@@ -36,8 +36,8 @@ export default defineAdapter<KookAdapterConfig>({
36
36
  const config = resolveKookConfig(context.config);
37
37
  const gateway = context.use(messageGatewayToken);
38
38
  // 注册到插件运行时状态(kook.endpoint list 的"运行中"数据源)
39
- context.use(kookRuntimeStateToken).endpoints.set(config.name, {
40
- name: config.name,
39
+ context.use(kookRuntimeStateToken).endpoints.set(config.id, {
40
+ id: config.id,
41
41
  mode: config.connection,
42
42
  });
43
43
  if (config.connection === 'webhook') {
package/lib/endpoint.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
1
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -18,10 +18,7 @@ export declare class KookWebsocketEndpoint implements EndpointInstance {
18
18
  open(): void;
19
19
  close(): void;
20
20
  stop(): Promise<void>;
21
- send({ target, payload }: {
22
- readonly target: string;
23
- readonly payload: unknown;
24
- }): Promise<string>;
21
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
25
22
  /** Test / internal: admit a message when open. */
26
23
  admit(msg: KookInboundMessage): void;
27
24
  recallMessage(messageId: string): Promise<void>;
@@ -61,10 +58,7 @@ export declare class KookWebhookEndpoint implements EndpointInstance {
61
58
  open(): void;
62
59
  close(): void;
63
60
  stop(): Promise<void>;
64
- send({ target, payload }: {
65
- readonly target: string;
66
- readonly payload: unknown;
67
- }): Promise<string>;
61
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
68
62
  /** Test / internal: admit a message when open. */
69
63
  admit(msg: KookInboundMessage): void;
70
64
  recallMessage(messageId: string): Promise<void>;
package/lib/endpoint.js CHANGED
@@ -1,10 +1,10 @@
1
- import { formatCompact, getLogger } from '@zhin.js/logger';
1
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
2
  import { registerKookAgentEndpoint } from './kook-agent-deps.js';
3
- import { formatInboundContent, formatInboundTarget, formatOutboundKmarkdown, isKookBotMentioned, parseSendTarget, senderDisplayName, } from './protocol.js';
3
+ import { formatInboundContent, formatOutboundKmarkdown, isKookBotMentioned, kookInboundConversation, senderDisplayName, } from './protocol.js';
4
4
  import { registerKookWebhookRoutes } from './webhook.js';
5
5
  import { defaultCreateClient, defaultCreateWebhookClient, normalizeKookMessage, } from './ws.js';
6
- const logger = getLogger('kook');
7
6
  export class KookWebsocketEndpoint {
7
+ #logger;
8
8
  #options;
9
9
  #createClient;
10
10
  #client = null;
@@ -13,6 +13,7 @@ export class KookWebsocketEndpoint {
13
13
  #unregisterAgent;
14
14
  management = createKookEndpointManagement(() => this.#requireClient());
15
15
  constructor(options) {
16
+ this.#logger = getAdapterLogger('kook', options.config.id);
16
17
  this.#options = options;
17
18
  this.#createClient = options.createClient ?? defaultCreateClient;
18
19
  }
@@ -21,20 +22,20 @@ export class KookWebsocketEndpoint {
21
22
  return;
22
23
  this.#started = true;
23
24
  try {
24
- this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.name, this);
25
+ this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.id, this);
25
26
  this.#client = this.#createClient(this.#options.config);
26
27
  this.#bindClient(this.#client);
27
28
  await this.#client.connect();
28
- logger.info(formatCompact({
29
+ this.#logger.info(formatCompact({
29
30
  op: 'connect',
30
- endpoint: this.#options.config.name,
31
+ endpoint: this.#options.config.id,
31
32
  mode: 'websocket',
32
33
  self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
33
34
  }));
34
35
  }
35
36
  catch (error) {
36
37
  await this.stop();
37
- logger.error('Failed to connect KOOK websocket:', error);
38
+ this.#logger.error('Failed to connect KOOK websocket:', error);
38
39
  throw error;
39
40
  }
40
41
  }
@@ -59,20 +60,19 @@ export class KookWebsocketEndpoint {
59
60
  this.#client = null;
60
61
  }
61
62
  this.#started = false;
62
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
63
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
63
64
  }
64
- async send({ target, payload }) {
65
+ async send({ conversation, payload }) {
65
66
  const body = formatOutboundKmarkdown(payload);
66
- const parsed = parseSendTarget(target);
67
67
  const client = this.#requireClient();
68
- const result = parsed.kind === 'private'
69
- ? await client.sendPrivateMsg(parsed.id, body)
70
- : await client.sendChannelMsg(parsed.id, body);
68
+ const result = conversation.kind === 'private'
69
+ ? await client.sendPrivateMsg(conversation.id, body)
70
+ : await client.sendChannelMsg(conversation.id, body);
71
71
  const messageId = result?.msg_id != null ? String(result.msg_id) : '';
72
- logger.debug(formatCompact({
72
+ this.#logger.debug(formatCompact({
73
73
  op: 'kook_send',
74
- endpoint: this.#options.config.name,
75
- target,
74
+ endpoint: this.#options.config.id,
75
+ target: `${conversation.kind}:${conversation.id}`,
76
76
  messageId,
77
77
  }));
78
78
  return messageId;
@@ -83,26 +83,29 @@ export class KookWebsocketEndpoint {
83
83
  return;
84
84
  if (msg.authorBot)
85
85
  return;
86
- const target = formatInboundTarget(msg);
86
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
87
87
  const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
88
88
  void this.#options.gateway.receive({
89
- adapter: this.#options.id,
90
- target,
89
+ conversation,
90
+ message: { conversation, id: msg.id },
91
91
  content: formatInboundContent(msg),
92
- sender: senderDisplayName(msg),
93
- id: msg.id,
92
+ sender: {
93
+ id: msg.authorId,
94
+ name: senderDisplayName(msg) || undefined,
95
+ ...(msg.authorRoles?.length ? { roles: msg.authorRoles.map(String) } : {}),
96
+ },
97
+ endpointId: this.#options.config.id,
98
+ ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
94
99
  metadata: Object.freeze({
95
- endpoint: this.#options.config.name,
96
100
  channelKind: msg.channelKind,
97
101
  userId: msg.authorId,
98
102
  guildId: msg.guildId,
99
103
  roles: msg.authorRoles,
100
- ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
101
104
  }),
102
105
  }).catch((err) => {
103
- logger.warn(formatCompact({
106
+ this.#logger.warn(formatCompact({
104
107
  op: 'kook_gateway_receive_failed',
105
- target,
108
+ target: `${conversation.kind}:${conversation.id}`,
106
109
  error: err instanceof Error ? err.message : String(err),
107
110
  }));
108
111
  });
@@ -148,6 +151,7 @@ export class KookWebsocketEndpoint {
148
151
  }
149
152
  }
150
153
  export class KookWebhookEndpoint {
154
+ #logger;
151
155
  #options;
152
156
  #createClient;
153
157
  #client = null;
@@ -158,6 +162,7 @@ export class KookWebhookEndpoint {
158
162
  #unregisterAgent;
159
163
  management = createKookEndpointManagement(() => this.#requireClient());
160
164
  constructor(options) {
165
+ this.#logger = getAdapterLogger('kook', options.config.id);
161
166
  this.#options = options;
162
167
  this.#createClient = options.createClient ?? defaultCreateWebhookClient;
163
168
  }
@@ -176,13 +181,13 @@ export class KookWebhookEndpoint {
176
181
  return;
177
182
  this.#started = true;
178
183
  try {
179
- this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.name, this);
184
+ this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.id, this);
180
185
  this.#client = this.#createClient(this.#options.config);
181
186
  await this.#client.init();
182
187
  this.#routeReleases.push(...registerKookWebhookRoutes(this.#options.http, this));
183
- logger.info(formatCompact({
188
+ this.#logger.info(formatCompact({
184
189
  op: 'connect',
185
- endpoint: this.#options.config.name,
190
+ endpoint: this.#options.config.id,
186
191
  mode: 'webhook',
187
192
  path: this.#options.config.webhookPath,
188
193
  self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
@@ -190,7 +195,7 @@ export class KookWebhookEndpoint {
190
195
  }
191
196
  catch (error) {
192
197
  await this.stop();
193
- logger.error('Failed to connect KOOK webhook:', error);
198
+ this.#logger.error('Failed to connect KOOK webhook:', error);
194
199
  throw error;
195
200
  }
196
201
  }
@@ -218,20 +223,19 @@ export class KookWebhookEndpoint {
218
223
  this.#client = null;
219
224
  }
220
225
  this.#started = false;
221
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
226
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
222
227
  }
223
- async send({ target, payload }) {
228
+ async send({ conversation, payload }) {
224
229
  const body = formatOutboundKmarkdown(payload);
225
- const parsed = parseSendTarget(target);
226
230
  const client = this.#requireClient();
227
- const result = parsed.kind === 'private'
228
- ? await client.sendPrivateMsg(parsed.id, body)
229
- : await client.sendChannelMsg(parsed.id, body);
231
+ const result = conversation.kind === 'private'
232
+ ? await client.sendPrivateMsg(conversation.id, body)
233
+ : await client.sendChannelMsg(conversation.id, body);
230
234
  const messageId = result?.msg_id != null ? String(result.msg_id) : '';
231
- logger.debug(formatCompact({
235
+ this.#logger.debug(formatCompact({
232
236
  op: 'kook_send',
233
- endpoint: this.#options.config.name,
234
- target,
237
+ endpoint: this.#options.config.id,
238
+ target: `${conversation.kind}:${conversation.id}`,
235
239
  messageId,
236
240
  }));
237
241
  return messageId;
@@ -242,26 +246,29 @@ export class KookWebhookEndpoint {
242
246
  return;
243
247
  if (msg.authorBot)
244
248
  return;
245
- const target = formatInboundTarget(msg);
249
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
246
250
  const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
247
251
  void this.#options.gateway.receive({
248
- adapter: this.#options.id,
249
- target,
252
+ conversation,
253
+ message: { conversation, id: msg.id },
250
254
  content: formatInboundContent(msg),
251
- sender: senderDisplayName(msg),
252
- id: msg.id,
255
+ sender: {
256
+ id: msg.authorId,
257
+ name: senderDisplayName(msg) || undefined,
258
+ ...(msg.authorRoles?.length ? { roles: msg.authorRoles.map(String) } : {}),
259
+ },
260
+ endpointId: this.#options.config.id,
261
+ ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
253
262
  metadata: Object.freeze({
254
- endpoint: this.#options.config.name,
255
263
  channelKind: msg.channelKind,
256
264
  userId: msg.authorId,
257
265
  guildId: msg.guildId,
258
266
  roles: msg.authorRoles,
259
- ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
260
267
  }),
261
268
  }).catch((err) => {
262
- logger.warn(formatCompact({
269
+ this.#logger.warn(formatCompact({
263
270
  op: 'kook_gateway_receive_failed',
264
- target,
271
+ target: `${conversation.kind}:${conversation.id}`,
265
272
  error: err instanceof Error ? err.message : String(err),
266
273
  }));
267
274
  });
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { formatInboundContent, formatInboundTarget, formatOutboundKmarkdown, parseSendTarget, resolveKookConfig, senderDisplayName, KookPermission, type KookAdapterConfig, type KookInboundMessage, type KookWireSegment, type LogLevel, type ParsedSendTarget, type ResolvedKookConfig, type ResolvedKookWebhookConfig, type ResolvedKookWebsocketConfig, } from './protocol.js';
1
+ export { formatInboundContent, formatOutboundKmarkdown, resolveKookConfig, senderDisplayName, KookPermission, type KookAdapterConfig, type KookInboundMessage, type KookWireSegment, type LogLevel, type ResolvedKookConfig, type ResolvedKookWebhookConfig, type ResolvedKookWebsocketConfig, } from './protocol.js';
2
2
  export { KookWebhookEndpoint, KookWebsocketEndpoint, type KookEndpointOptions, type KookWebhookEndpointOptions, } from './endpoint.js';
3
3
  export { registerKookWebhookRoutes, handleKookWebhookRequest, type KookWebhookHandler, } from './webhook.js';
4
4
  export { defaultCreateClient, defaultCreateWebhookClient, normalizeKookMessage, type CreateKookClient, type KookClientTransport, } from './ws.js';
5
5
  export { getKookAgentDeps, registerKookAgentEndpoint, setKookAgentDeps, type KookAgentDeps, type KookAgentEndpoint, } from './kook-agent-deps.js';
6
- export { checkKookPlatformPermit, kookGroupPermitResolver, normalizeKookSenderForPermit, platformPermit, registerKookPlatformPermitChecker, type KookSenderInfo, } from './platform-permit.js';
6
+ export { checkKookPlatformPermit, kookGroupPermitResolver, normalizeKookSenderForPermit, platformPermit, type KookSenderInfo, } from './platform-permit.js';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { formatInboundContent, formatInboundTarget, formatOutboundKmarkdown, parseSendTarget, resolveKookConfig, senderDisplayName, KookPermission, } from './protocol.js';
1
+ export { formatInboundContent, formatOutboundKmarkdown, resolveKookConfig, senderDisplayName, KookPermission, } from './protocol.js';
2
2
  export { KookWebhookEndpoint, KookWebsocketEndpoint, } from './endpoint.js';
3
3
  export { registerKookWebhookRoutes, handleKookWebhookRequest, } from './webhook.js';
4
4
  export { defaultCreateClient, defaultCreateWebhookClient, normalizeKookMessage, } from './ws.js';
5
5
  export { getKookAgentDeps, registerKookAgentEndpoint, setKookAgentDeps, } from './kook-agent-deps.js';
6
- export { checkKookPlatformPermit, kookGroupPermitResolver, normalizeKookSenderForPermit, platformPermit, registerKookPlatformPermitChecker, } from './platform-permit.js';
6
+ export { checkKookPlatformPermit, kookGroupPermitResolver, normalizeKookSenderForPermit, platformPermit, } from './platform-permit.js';
@@ -21,9 +21,9 @@ export interface KookAgentEndpoint {
21
21
  removeFromBlacklist(guildId: string, userId: string): Promise<boolean>;
22
22
  }
23
23
  export interface KookAgentDeps {
24
- getEndpoint: (endpointId: string) => KookAgentEndpoint;
24
+ getEndpoint: (endpointKey: string) => KookAgentEndpoint;
25
25
  }
26
- export declare function registerKookAgentEndpoint(endpointId: string, endpoint: KookAgentEndpoint): () => void;
26
+ export declare function registerKookAgentEndpoint(endpointKey: string, endpoint: KookAgentEndpoint): () => void;
27
27
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
28
28
  export declare function setKookAgentDeps(deps: KookAgentDeps | null): void;
29
29
  export declare function getKookAgentDeps(): KookAgentDeps;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerKookAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerKookAgentEndpoint(endpointKey, endpoint) {
8
+ endpoints.set(endpointKey, endpoint);
9
9
  return () => {
10
- if (endpoints.get(endpointId) === endpoint) {
11
- endpoints.delete(endpointId);
10
+ if (endpoints.get(endpointKey) === endpoint) {
11
+ endpoints.delete(endpointKey);
12
12
  }
13
13
  };
14
14
  }
@@ -20,10 +20,10 @@ export function getKookAgentDeps() {
20
20
  if (override)
21
21
  return override;
22
22
  return {
23
- getEndpoint(endpointId) {
24
- const registered = endpoints.get(endpointId);
23
+ getEndpoint(endpointKey) {
24
+ const registered = endpoints.get(endpointKey);
25
25
  if (!registered)
26
- throw new Error(`Endpoint ${endpointId} 不存在`);
26
+ throw new Error(`Endpoint ${endpointKey} 不存在`);
27
27
  return registered;
28
28
  },
29
29
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * KOOK platform permit — guild / channel 差异化门禁
3
3
  */
4
- import { type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
  import { KookPermission } from './protocol.js';
6
6
  export declare function platformPermit(perm: string): string;
7
7
  export declare function kookGroupPermitResolver(logicalPerm: string): string;
@@ -19,5 +19,4 @@ export declare function normalizeKookSenderForPermit(info: KookSenderInfo, isCha
19
19
  role?: string;
20
20
  permissions?: string[];
21
21
  };
22
- export declare function checkKookPlatformPermit(perm: string, message: Message<any>): boolean;
23
- export declare function registerKookPlatformPermitChecker(): () => void;
22
+ export declare function checkKookPlatformPermit(perm: string, subject: PermissionSubject): boolean;
@@ -1,7 +1,3 @@
1
- /**
2
- * KOOK platform permit — guild / channel 差异化门禁
3
- */
4
- import { registerPlatformPermitChecker } from '@zhin.js/core';
5
1
  import { KookPermission } from './protocol.js';
6
2
  const ADAPTER = 'kook';
7
3
  export function platformPermit(perm) {
@@ -33,19 +29,9 @@ export function normalizeKookSenderForPermit(info, isChannel) {
33
29
  }
34
30
  return { role: 'member', permissions };
35
31
  }
36
- function senderPermits(message) {
37
- const sender = message.$sender;
38
- const permissions = [...(sender.permissions ?? [])];
39
- if (sender.isGuildOwner)
40
- permissions.push('guild_owner');
41
- if (sender.isAdmin)
42
- permissions.push('guild_admin');
43
- if (sender.permission === KookPermission.ChannelAdmin)
44
- permissions.push('channel_admin');
45
- return { role: sender.role, permissions };
46
- }
47
- export function checkKookPlatformPermit(perm, message) {
48
- const { role, permissions } = senderPermits(message);
32
+ export function checkKookPlatformPermit(perm, subject) {
33
+ const role = subject.sender?.role?.[0];
34
+ const permissions = subject.sender?.permissions ?? [];
49
35
  const has = (t) => permissions.includes(t);
50
36
  switch (perm) {
51
37
  case 'guild_owner':
@@ -60,6 +46,3 @@ export function checkKookPlatformPermit(perm, message) {
60
46
  return false;
61
47
  }
62
48
  }
63
- export function registerKookPlatformPermitChecker() {
64
- return registerPlatformPermitChecker(ADAPTER, checkKookPlatformPermit);
65
- }
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'mark' | 'off';
7
8
  export declare enum KookPermission {
8
9
  Normal = 1,
@@ -12,7 +13,7 @@ export declare enum KookPermission {
12
13
  }
13
14
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
14
15
  export interface KookAdapterConfig {
15
- readonly name?: string;
16
+ readonly id?: string;
16
17
  readonly token?: string;
17
18
  /** Default `websocket`. `webhook` requires httpHostToken + verify_token. */
18
19
  readonly connection?: 'websocket' | 'webhook';
@@ -27,7 +28,7 @@ export interface KookAdapterConfig {
27
28
  /** Transitional: legacy root `endpoints[]` with `context: kook`. */
28
29
  readonly endpoints?: ReadonlyArray<{
29
30
  readonly context?: string;
30
- readonly name?: string;
31
+ readonly id?: string;
31
32
  readonly token?: string;
32
33
  readonly connection?: 'websocket' | 'webhook';
33
34
  readonly webhookPath?: string;
@@ -43,7 +44,7 @@ export interface KookAdapterConfig {
43
44
  export interface ResolvedKookWebsocketConfig {
44
45
  readonly context: 'kook';
45
46
  readonly connection: 'websocket';
46
- readonly name: string;
47
+ readonly id: string;
47
48
  readonly token: string;
48
49
  readonly data_dir?: string;
49
50
  readonly timeout: number;
@@ -54,7 +55,7 @@ export interface ResolvedKookWebsocketConfig {
54
55
  export interface ResolvedKookWebhookConfig {
55
56
  readonly context: 'kook';
56
57
  readonly connection: 'webhook';
57
- readonly name: string;
58
+ readonly id: string;
58
59
  readonly token: string;
59
60
  readonly webhookPath: string;
60
61
  readonly verifyToken: string;
@@ -106,16 +107,12 @@ export interface KookWireSegment {
106
107
  readonly type: string;
107
108
  readonly data?: Record<string, unknown>;
108
109
  }
109
- export interface ParsedSendTarget {
110
- readonly kind: 'private' | 'channel';
111
- readonly id: string;
112
- }
113
110
  export declare function resolveKookConfig(config?: KookAdapterConfig): ResolvedKookConfig;
114
111
  /**
115
- * Gateway reply target:`private:uid` / `channel:cid`,便于 send() 还原 API。
112
+ * 入站归一化 → ConversationRef:频道消息的所属 guild 进 `parent`(guild 容器
113
+ * 映射为 channel 容器);私聊(PERSON)无容器,直接 kind 'private'。
116
114
  */
117
- export declare function formatInboundTarget(msg: KookInboundMessage): string;
118
- export declare function parseSendTarget(target: string): ParsedSendTarget;
115
+ export declare function kookInboundConversation(endpointKey: string, msg: KookInboundMessage): ConversationRef;
119
116
  export declare function senderDisplayName(msg: KookInboundMessage): string;
120
117
  /** Build inbound text for MessageGateway.receive */
121
118
  export declare function formatInboundContent(msg: KookInboundMessage): string;
package/lib/protocol.js CHANGED
@@ -21,8 +21,8 @@ export function resolveKookConfig(config = {}) {
21
21
  if (!token) {
22
22
  throw new TypeError('KOOK adapter requires token (plugins.<key>.token or endpoints with context: kook)');
23
23
  }
24
- const name = (typeof config.name === 'string' && config.name)
25
- || (typeof entry?.name === 'string' && entry.name)
24
+ const id = (typeof config.id === 'string' && config.id)
25
+ || (typeof entry?.id === 'string' && entry.id)
26
26
  || process.env.KOOK_BOT_NAME
27
27
  || 'kook-bot';
28
28
  const connection = config.connection
@@ -40,7 +40,7 @@ export function resolveKookConfig(config = {}) {
40
40
  return {
41
41
  context: 'kook',
42
42
  connection: 'webhook',
43
- name,
43
+ id,
44
44
  token,
45
45
  webhookPath: normalizeWebhookPath(config.webhookPath ?? entry?.webhookPath ?? process.env.KOOK_WEBHOOK_PATH ?? '/kook/webhook'),
46
46
  verifyToken,
@@ -52,7 +52,7 @@ export function resolveKookConfig(config = {}) {
52
52
  return {
53
53
  context: 'kook',
54
54
  connection: 'websocket',
55
- name,
55
+ id,
56
56
  token,
57
57
  data_dir: config.data_dir ?? entry?.data_dir,
58
58
  timeout: config.timeout ?? entry?.timeout ?? 10_000,
@@ -62,23 +62,18 @@ export function resolveKookConfig(config = {}) {
62
62
  };
63
63
  }
64
64
  /**
65
- * Gateway reply target:`private:uid` / `channel:cid`,便于 send() 还原 API。
65
+ * 入站归一化 → ConversationRef:频道消息的所属 guild 进 `parent`(guild 容器
66
+ * 映射为 channel 容器);私聊(PERSON)无容器,直接 kind 'private'。
66
67
  */
67
- export function formatInboundTarget(msg) {
68
- return `${msg.channelKind}:${msg.channelId}`;
69
- }
70
- export function parseSendTarget(target) {
71
- const sep = target.indexOf(':');
72
- if (sep <= 0) {
73
- return { kind: 'channel', id: target };
74
- }
75
- const head = target.slice(0, sep);
76
- const rest = target.slice(sep + 1);
77
- if (head === 'private')
78
- return { kind: 'private', id: rest };
79
- if (head === 'channel' || head === 'group')
80
- return { kind: 'channel', id: rest };
81
- return { kind: 'channel', id: target };
68
+ export function kookInboundConversation(endpointKey, msg) {
69
+ return {
70
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
71
+ kind: msg.channelKind,
72
+ id: msg.channelId,
73
+ ...(msg.guildId && msg.channelKind === 'channel'
74
+ ? { parent: { kind: 'channel', id: msg.guildId } }
75
+ : {}),
76
+ };
82
77
  }
83
78
  export function senderDisplayName(msg) {
84
79
  return msg.authorName || msg.authorId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-kook",
3
- "version": "7.0.2",
3
+ "version": "7.0.5",
4
4
  "description": "Zhin.js KOOK adapter for Plugin Runtime (WebSocket gateway or Webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,20 +34,22 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "kook-client": "^1.0.4",
37
- "@zhin.js/adapter": "1.1.4",
38
- "@zhin.js/command": "1.0.6",
39
- "@zhin.js/core": "1.5.1",
40
- "@zhin.js/host-http": "1.0.5",
41
- "@zhin.js/logger": "1.0.75",
42
- "@zhin.js/plugin-runtime": "1.1.2"
37
+ "@zhin.js/adapter": "1.1.7",
38
+ "@zhin.js/command": "1.0.9",
39
+ "@zhin.js/core": "1.5.4",
40
+ "@zhin.js/host-http": "1.0.8",
41
+ "@zhin.js/im-contract": "1.0.3",
42
+ "@zhin.js/logger": "1.0.76",
43
+ "@zhin.js/permission": "1.0.1",
44
+ "@zhin.js/plugin-runtime": "1.1.5"
43
45
  },
44
46
  "peerDependencies": {
45
47
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "1.1.4",
47
- "@zhin.js/agent": "1.1.2",
48
- "@zhin.js/core": "1.5.1",
49
- "@zhin.js/plugin-runtime": "1.1.2",
50
- "zhin.js": "6.0.1"
48
+ "@zhin.js/adapter": "1.1.7",
49
+ "@zhin.js/agent": "1.1.5",
50
+ "@zhin.js/core": "1.5.4",
51
+ "@zhin.js/plugin-runtime": "1.1.5",
52
+ "zhin.js": "6.0.4"
51
53
  },
52
54
  "peerDependenciesMeta": {
53
55
  "zhin.js": {
@@ -65,9 +67,9 @@
65
67
  "typescript": "^6.0.3",
66
68
  "vitest": "^4.1.10",
67
69
  "zod": "^4.4.3",
68
- "@zhin.js/agent": "1.1.2",
69
- "@zhin.js/host-http": "1.0.5",
70
- "zhin.js": "6.0.1"
70
+ "@zhin.js/agent": "1.1.5",
71
+ "@zhin.js/host-http": "1.0.8",
72
+ "zhin.js": "6.0.4"
71
73
  },
72
74
  "files": [
73
75
  "adapters",
package/plugin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { createEndpointRuntimeState } from '@zhin.js/adapter';
3
3
  import { definePlugin } from '@zhin.js/plugin-runtime';
4
- import { registerKookPlatformPermitChecker } from "./lib/platform-permit.js";
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkKookPlatformPermit } from "./lib/platform-permit.js";
5
6
  import { kookRuntimeStateToken } from "./lib/kook-runtime-state.js";
6
7
  export default definePlugin({
7
8
  name: 'kook',
@@ -9,9 +10,10 @@ export default definePlugin({
9
10
  displayName: 'KOOK WebSocket Adapter',
10
11
  },
11
12
  setup(context) {
12
- // 运行中 endpoint 注册表(kook.endpoint list 的"运行中"数据源)
13
13
  context.resources.provide(kookRuntimeStateToken, createEndpointRuntimeState());
14
- // 平台权限门禁:guild_owner / guild_admin / channel_admin 等(agent 工具 platformPermit)
15
- return registerKookPlatformPermitChecker();
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('kook', checkKookPlatformPermit);
17
+ }
16
18
  },
17
19
  });
package/schema.json CHANGED
@@ -50,16 +50,48 @@
50
50
  ],
51
51
  "default": "info"
52
52
  },
53
+ "master": {
54
+ "type": [
55
+ "string",
56
+ "number"
57
+ ],
58
+ "description": "框架 master(KOOK user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
59
+ },
60
+ "trusted": {
61
+ "type": "array",
62
+ "items": {
63
+ "type": [
64
+ "string",
65
+ "number"
66
+ ],
67
+ "description": "Trusted KOOK user id"
68
+ },
69
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
70
+ },
53
71
  "endpoints": {
54
72
  "type": "array",
55
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
73
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
56
74
  "items": {
57
75
  "type": "object",
58
76
  "additionalProperties": true,
59
77
  "properties": {
60
- "name": {
61
- "type": "string",
62
- "description": "KOOK bot name"
78
+ "master": {
79
+ "type": [
80
+ "string",
81
+ "number"
82
+ ],
83
+ "description": "本 endpoint 的框架 master(KOOK user id);覆盖顶层 master"
84
+ },
85
+ "trusted": {
86
+ "type": "array",
87
+ "items": {
88
+ "type": [
89
+ "string",
90
+ "number"
91
+ ],
92
+ "description": "Trusted KOOK user id"
93
+ },
94
+ "description": "本 endpoint 的 trusted 列表"
63
95
  },
64
96
  "token": {
65
97
  "type": "string",
@@ -72,10 +104,14 @@
72
104
  "encrypt_key": {
73
105
  "type": "string",
74
106
  "description": "Optional Encrypt Key when message encryption is enabled in KOOK console."
107
+ },
108
+ "id": {
109
+ "type": "string",
110
+ "description": "KOOK bot name"
75
111
  }
76
112
  },
77
113
  "required": [
78
- "name",
114
+ "id",
79
115
  "token"
80
116
  ]
81
117
  }
package/src/endpoint.ts CHANGED
@@ -7,18 +7,18 @@ import type {
7
7
  EndpointGroup,
8
8
  EndpointInstance,
9
9
  EndpointManagement,
10
+ EndpointSendRequest,
10
11
  } from '@zhin.js/adapter';
11
12
  import type { MessageGateway } from '@zhin.js/core/runtime';
12
13
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
13
- import { formatCompact, getLogger } from '@zhin.js/logger';
14
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
14
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
15
16
  import { registerKookAgentEndpoint } from './kook-agent-deps.js';
16
17
  import {
17
18
  formatInboundContent,
18
- formatInboundTarget,
19
19
  formatOutboundKmarkdown,
20
20
  isKookBotMentioned,
21
- parseSendTarget,
21
+ kookInboundConversation,
22
22
  senderDisplayName,
23
23
  type KookInboundMessage,
24
24
  type ResolvedKookWebhookConfig,
@@ -33,8 +33,6 @@ import {
33
33
  type KookClientTransport,
34
34
  } from './ws.js';
35
35
 
36
- const logger = getLogger('kook');
37
-
38
36
  export interface KookEndpointOptions {
39
37
  readonly id: CapabilityId;
40
38
  readonly gateway: MessageGateway;
@@ -43,6 +41,8 @@ export interface KookEndpointOptions {
43
41
  }
44
42
 
45
43
  export class KookWebsocketEndpoint implements EndpointInstance {
44
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
45
+
46
46
  readonly #options: KookEndpointOptions;
47
47
  readonly #createClient: CreateKookClient;
48
48
  #client: KookClientTransport | null = null;
@@ -52,6 +52,7 @@ export class KookWebsocketEndpoint implements EndpointInstance {
52
52
  readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
53
53
 
54
54
  constructor(options: KookEndpointOptions) {
55
+ this.#logger = getAdapterLogger('kook', options.config.id);
55
56
  this.#options = options;
56
57
  this.#createClient = options.createClient ?? (defaultCreateClient as CreateKookClient);
57
58
  }
@@ -60,19 +61,19 @@ export class KookWebsocketEndpoint implements EndpointInstance {
60
61
  if (this.#started) return;
61
62
  this.#started = true;
62
63
  try {
63
- this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.name, this);
64
+ this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.id, this);
64
65
  this.#client = this.#createClient(this.#options.config);
65
66
  this.#bindClient(this.#client);
66
67
  await this.#client.connect();
67
- logger.info(formatCompact({
68
+ this.#logger.info(formatCompact({
68
69
  op: 'connect',
69
- endpoint: this.#options.config.name,
70
+ endpoint: this.#options.config.id,
70
71
  mode: 'websocket',
71
72
  self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
72
73
  }));
73
74
  } catch (error) {
74
75
  await this.stop();
75
- logger.error('Failed to connect KOOK websocket:', error);
76
+ this.#logger.error('Failed to connect KOOK websocket:', error);
76
77
  throw error;
77
78
  }
78
79
  }
@@ -99,21 +100,20 @@ export class KookWebsocketEndpoint implements EndpointInstance {
99
100
  this.#client = null;
100
101
  }
101
102
  this.#started = false;
102
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
103
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
103
104
  }
104
105
 
105
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
106
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
106
107
  const body = formatOutboundKmarkdown(payload);
107
- const parsed = parseSendTarget(target);
108
108
  const client = this.#requireClient();
109
- const result = parsed.kind === 'private'
110
- ? await client.sendPrivateMsg(parsed.id, body)
111
- : await client.sendChannelMsg(parsed.id, body);
109
+ const result = conversation.kind === 'private'
110
+ ? await client.sendPrivateMsg(conversation.id, body)
111
+ : await client.sendChannelMsg(conversation.id, body);
112
112
  const messageId = result?.msg_id != null ? String(result.msg_id) : '';
113
- logger.debug(formatCompact({
113
+ this.#logger.debug(formatCompact({
114
114
  op: 'kook_send',
115
- endpoint: this.#options.config.name,
116
- target,
115
+ endpoint: this.#options.config.id,
116
+ target: `${conversation.kind}:${conversation.id}`,
117
117
  messageId,
118
118
  }));
119
119
  return messageId;
@@ -123,26 +123,29 @@ export class KookWebsocketEndpoint implements EndpointInstance {
123
123
  admit(msg: KookInboundMessage): void {
124
124
  if (!this.#open) return;
125
125
  if (msg.authorBot) return;
126
- const target = formatInboundTarget(msg);
126
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
127
127
  const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
128
128
  void this.#options.gateway.receive({
129
- adapter: this.#options.id,
130
- target,
129
+ conversation,
130
+ message: { conversation, id: msg.id },
131
131
  content: formatInboundContent(msg),
132
- sender: senderDisplayName(msg),
133
- id: msg.id,
132
+ sender: {
133
+ id: msg.authorId,
134
+ name: senderDisplayName(msg) || undefined,
135
+ ...(msg.authorRoles?.length ? { roles: msg.authorRoles.map(String) } : {}),
136
+ },
137
+ endpointId: this.#options.config.id,
138
+ ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
134
139
  metadata: Object.freeze({
135
- endpoint: this.#options.config.name,
136
140
  channelKind: msg.channelKind,
137
141
  userId: msg.authorId,
138
142
  guildId: msg.guildId,
139
143
  roles: msg.authorRoles,
140
- ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
141
144
  }),
142
145
  }).catch((err) => {
143
- logger.warn(formatCompact({
146
+ this.#logger.warn(formatCompact({
144
147
  op: 'kook_gateway_receive_failed',
145
- target,
148
+ target: `${conversation.kind}:${conversation.id}`,
146
149
  error: err instanceof Error ? err.message : String(err),
147
150
  }));
148
151
  });
@@ -205,6 +208,8 @@ export interface KookWebhookEndpointOptions {
205
208
  }
206
209
 
207
210
  export class KookWebhookEndpoint implements EndpointInstance {
211
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
212
+
208
213
  readonly #options: KookWebhookEndpointOptions;
209
214
  readonly #createClient: CreateKookClient;
210
215
  #client: KookClientTransport | null = null;
@@ -216,6 +221,7 @@ export class KookWebhookEndpoint implements EndpointInstance {
216
221
  readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
217
222
 
218
223
  constructor(options: KookWebhookEndpointOptions) {
224
+ this.#logger = getAdapterLogger('kook', options.config.id);
219
225
  this.#options = options;
220
226
  this.#createClient = options.createClient ?? (defaultCreateWebhookClient as CreateKookClient);
221
227
  }
@@ -237,20 +243,20 @@ export class KookWebhookEndpoint implements EndpointInstance {
237
243
  if (this.#started) return;
238
244
  this.#started = true;
239
245
  try {
240
- this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.name, this);
246
+ this.#unregisterAgent = registerKookAgentEndpoint(this.#options.config.id, this);
241
247
  this.#client = this.#createClient(this.#options.config);
242
248
  await (this.#client as Client).init();
243
249
  this.#routeReleases.push(...registerKookWebhookRoutes(this.#options.http, this));
244
- logger.info(formatCompact({
250
+ this.#logger.info(formatCompact({
245
251
  op: 'connect',
246
- endpoint: this.#options.config.name,
252
+ endpoint: this.#options.config.id,
247
253
  mode: 'webhook',
248
254
  path: this.#options.config.webhookPath,
249
255
  self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
250
256
  }));
251
257
  } catch (error) {
252
258
  await this.stop();
253
- logger.error('Failed to connect KOOK webhook:', error);
259
+ this.#logger.error('Failed to connect KOOK webhook:', error);
254
260
  throw error;
255
261
  }
256
262
  }
@@ -279,21 +285,20 @@ export class KookWebhookEndpoint implements EndpointInstance {
279
285
  this.#client = null;
280
286
  }
281
287
  this.#started = false;
282
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
288
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
283
289
  }
284
290
 
285
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
291
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
286
292
  const body = formatOutboundKmarkdown(payload);
287
- const parsed = parseSendTarget(target);
288
293
  const client = this.#requireClient();
289
- const result = parsed.kind === 'private'
290
- ? await client.sendPrivateMsg(parsed.id, body)
291
- : await client.sendChannelMsg(parsed.id, body);
294
+ const result = conversation.kind === 'private'
295
+ ? await client.sendPrivateMsg(conversation.id, body)
296
+ : await client.sendChannelMsg(conversation.id, body);
292
297
  const messageId = result?.msg_id != null ? String(result.msg_id) : '';
293
- logger.debug(formatCompact({
298
+ this.#logger.debug(formatCompact({
294
299
  op: 'kook_send',
295
- endpoint: this.#options.config.name,
296
- target,
300
+ endpoint: this.#options.config.id,
301
+ target: `${conversation.kind}:${conversation.id}`,
297
302
  messageId,
298
303
  }));
299
304
  return messageId;
@@ -303,26 +308,29 @@ export class KookWebhookEndpoint implements EndpointInstance {
303
308
  admit(msg: KookInboundMessage): void {
304
309
  if (!this.#open) return;
305
310
  if (msg.authorBot) return;
306
- const target = formatInboundTarget(msg);
311
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
307
312
  const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
308
313
  void this.#options.gateway.receive({
309
- adapter: this.#options.id,
310
- target,
314
+ conversation,
315
+ message: { conversation, id: msg.id },
311
316
  content: formatInboundContent(msg),
312
- sender: senderDisplayName(msg),
313
- id: msg.id,
317
+ sender: {
318
+ id: msg.authorId,
319
+ name: senderDisplayName(msg) || undefined,
320
+ ...(msg.authorRoles?.length ? { roles: msg.authorRoles.map(String) } : {}),
321
+ },
322
+ endpointId: this.#options.config.id,
323
+ ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
314
324
  metadata: Object.freeze({
315
- endpoint: this.#options.config.name,
316
325
  channelKind: msg.channelKind,
317
326
  userId: msg.authorId,
318
327
  guildId: msg.guildId,
319
328
  roles: msg.authorRoles,
320
- ...(isKookBotMentioned(msg, selfId) ? { mentioned: true } : {}),
321
329
  }),
322
330
  }).catch((err) => {
323
- logger.warn(formatCompact({
331
+ this.#logger.warn(formatCompact({
324
332
  op: 'kook_gateway_receive_failed',
325
- target,
333
+ target: `${conversation.kind}:${conversation.id}`,
326
334
  error: err instanceof Error ? err.message : String(err),
327
335
  }));
328
336
  });
package/src/index.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  export {
2
2
  formatInboundContent,
3
- formatInboundTarget,
4
3
  formatOutboundKmarkdown,
5
- parseSendTarget,
6
4
  resolveKookConfig,
7
5
  senderDisplayName,
8
6
  KookPermission,
@@ -10,7 +8,6 @@ export {
10
8
  type KookInboundMessage,
11
9
  type KookWireSegment,
12
10
  type LogLevel,
13
- type ParsedSendTarget,
14
11
  type ResolvedKookConfig,
15
12
  type ResolvedKookWebhookConfig,
16
13
  type ResolvedKookWebsocketConfig,
@@ -50,6 +47,5 @@ export {
50
47
  kookGroupPermitResolver,
51
48
  normalizeKookSenderForPermit,
52
49
  platformPermit,
53
- registerKookPlatformPermitChecker,
54
50
  type KookSenderInfo,
55
51
  } from './platform-permit.js';
@@ -20,20 +20,20 @@ export interface KookAgentEndpoint {
20
20
  }
21
21
 
22
22
  export interface KookAgentDeps {
23
- getEndpoint: (endpointId: string) => KookAgentEndpoint;
23
+ getEndpoint: (endpointKey: string) => KookAgentEndpoint;
24
24
  }
25
25
 
26
26
  const endpoints = new Map<string, KookAgentEndpoint>();
27
27
  let override: KookAgentDeps | null = null;
28
28
 
29
29
  export function registerKookAgentEndpoint(
30
- endpointId: string,
30
+ endpointKey: string,
31
31
  endpoint: KookAgentEndpoint,
32
32
  ): () => void {
33
- endpoints.set(endpointId, endpoint);
33
+ endpoints.set(endpointKey, endpoint);
34
34
  return () => {
35
- if (endpoints.get(endpointId) === endpoint) {
36
- endpoints.delete(endpointId);
35
+ if (endpoints.get(endpointKey) === endpoint) {
36
+ endpoints.delete(endpointKey);
37
37
  }
38
38
  };
39
39
  }
@@ -46,9 +46,9 @@ export function setKookAgentDeps(deps: KookAgentDeps | null): void {
46
46
  export function getKookAgentDeps(): KookAgentDeps {
47
47
  if (override) return override;
48
48
  return {
49
- getEndpoint(endpointId: string): KookAgentEndpoint {
50
- const registered = endpoints.get(endpointId);
51
- if (!registered) throw new Error(`Endpoint ${endpointId} 不存在`);
49
+ getEndpoint(endpointKey: string): KookAgentEndpoint {
50
+ const registered = endpoints.get(endpointKey);
51
+ if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
52
52
  return registered;
53
53
  },
54
54
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * KOOK platform permit — guild / channel 差异化门禁
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
  import { KookPermission } from './protocol.js';
6
6
 
7
7
  const ADAPTER = 'kook';
@@ -54,17 +54,9 @@ export function normalizeKookSenderForPermit(
54
54
  return { role: 'member', permissions };
55
55
  }
56
56
 
57
- function senderPermits(message: Message<any>): { role?: string; permissions: string[] } {
58
- const sender = message.$sender as KookSenderInfo & { role?: string; permissions?: string[] };
59
- const permissions = [...(sender.permissions ?? [])];
60
- if (sender.isGuildOwner) permissions.push('guild_owner');
61
- if (sender.isAdmin) permissions.push('guild_admin');
62
- if (sender.permission === KookPermission.ChannelAdmin) permissions.push('channel_admin');
63
- return { role: sender.role, permissions };
64
- }
65
-
66
- export function checkKookPlatformPermit(perm: string, message: Message<any>): boolean {
67
- const { role, permissions } = senderPermits(message);
57
+ export function checkKookPlatformPermit(perm: string, subject: PermissionSubject): boolean {
58
+ const role = subject.sender?.role?.[0];
59
+ const permissions = subject.sender?.permissions ?? [];
68
60
  const has = (t: string) => permissions.includes(t);
69
61
 
70
62
  switch (perm) {
@@ -81,6 +73,3 @@ export function checkKookPlatformPermit(perm: string, message: Message<any>): bo
81
73
  }
82
74
  }
83
75
 
84
- export function registerKookPlatformPermitChecker(): () => void {
85
- return registerPlatformPermitChecker(ADAPTER, checkKookPlatformPermit);
86
- }
package/src/protocol.ts CHANGED
@@ -8,6 +8,7 @@ import { inflateSync } from 'node:zlib';
8
8
  import type { IncomingMessage } from 'node:http';
9
9
  import { pickCredential } from '@zhin.js/adapter';
10
10
  import { isMediaRef } from '@zhin.js/core';
11
+ import type { ConversationRef } from '@zhin.js/im-contract';
11
12
  import { formatCompact, getLogger } from '@zhin.js/logger';
12
13
 
13
14
  const logger = getLogger('kook');
@@ -31,7 +32,7 @@ export enum KookPermission {
31
32
 
32
33
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
33
34
  export interface KookAdapterConfig {
34
- readonly name?: string;
35
+ readonly id?: string;
35
36
  readonly token?: string;
36
37
  /** Default `websocket`. `webhook` requires httpHostToken + verify_token. */
37
38
  readonly connection?: 'websocket' | 'webhook';
@@ -46,7 +47,7 @@ export interface KookAdapterConfig {
46
47
  /** Transitional: legacy root `endpoints[]` with `context: kook`. */
47
48
  readonly endpoints?: ReadonlyArray<{
48
49
  readonly context?: string;
49
- readonly name?: string;
50
+ readonly id?: string;
50
51
  readonly token?: string;
51
52
  readonly connection?: 'websocket' | 'webhook';
52
53
  readonly webhookPath?: string;
@@ -63,7 +64,7 @@ export interface KookAdapterConfig {
63
64
  export interface ResolvedKookWebsocketConfig {
64
65
  readonly context: 'kook';
65
66
  readonly connection: 'websocket';
66
- readonly name: string;
67
+ readonly id: string;
67
68
  readonly token: string;
68
69
  readonly data_dir?: string;
69
70
  readonly timeout: number;
@@ -75,7 +76,7 @@ export interface ResolvedKookWebsocketConfig {
75
76
  export interface ResolvedKookWebhookConfig {
76
77
  readonly context: 'kook';
77
78
  readonly connection: 'webhook';
78
- readonly name: string;
79
+ readonly id: string;
79
80
  readonly token: string;
80
81
  readonly webhookPath: string;
81
82
  readonly verifyToken: string;
@@ -133,11 +134,6 @@ export interface KookWireSegment {
133
134
  readonly data?: Record<string, unknown>;
134
135
  }
135
136
 
136
- export interface ParsedSendTarget {
137
- readonly kind: 'private' | 'channel';
138
- readonly id: string;
139
- }
140
-
141
137
  export function resolveKookConfig(config: KookAdapterConfig = {}): ResolvedKookConfig {
142
138
  const entry = config.endpoints?.find((item) => item.context === 'kook' || !item.context);
143
139
  const token = pickCredential(config.token, entry?.token, process.env.KOOK_TOKEN, process.env.KOOK_BOT_TOKEN);
@@ -146,8 +142,8 @@ export function resolveKookConfig(config: KookAdapterConfig = {}): ResolvedKookC
146
142
  'KOOK adapter requires token (plugins.<key>.token or endpoints with context: kook)',
147
143
  );
148
144
  }
149
- const name = (typeof config.name === 'string' && config.name)
150
- || (typeof entry?.name === 'string' && entry.name)
145
+ const id = (typeof config.id === 'string' && config.id)
146
+ || (typeof entry?.id === 'string' && entry.id)
151
147
  || process.env.KOOK_BOT_NAME
152
148
  || 'kook-bot';
153
149
  const connection = config.connection
@@ -168,7 +164,7 @@ export function resolveKookConfig(config: KookAdapterConfig = {}): ResolvedKookC
168
164
  return {
169
165
  context: 'kook',
170
166
  connection: 'webhook',
171
- name,
167
+ id,
172
168
  token,
173
169
  webhookPath: normalizeWebhookPath(
174
170
  config.webhookPath ?? entry?.webhookPath ?? process.env.KOOK_WEBHOOK_PATH ?? '/kook/webhook',
@@ -183,7 +179,7 @@ export function resolveKookConfig(config: KookAdapterConfig = {}): ResolvedKookC
183
179
  return {
184
180
  context: 'kook',
185
181
  connection: 'websocket',
186
- name,
182
+ id,
187
183
  token,
188
184
  data_dir: config.data_dir ?? entry?.data_dir,
189
185
  timeout: config.timeout ?? entry?.timeout ?? 10_000,
@@ -194,22 +190,18 @@ export function resolveKookConfig(config: KookAdapterConfig = {}): ResolvedKookC
194
190
  }
195
191
 
196
192
  /**
197
- * Gateway reply target:`private:uid` / `channel:cid`,便于 send() 还原 API。
193
+ * 入站归一化 → ConversationRef:频道消息的所属 guild 进 `parent`(guild 容器
194
+ * 映射为 channel 容器);私聊(PERSON)无容器,直接 kind 'private'。
198
195
  */
199
- export function formatInboundTarget(msg: KookInboundMessage): string {
200
- return `${msg.channelKind}:${msg.channelId}`;
201
- }
202
-
203
- export function parseSendTarget(target: string): ParsedSendTarget {
204
- const sep = target.indexOf(':');
205
- if (sep <= 0) {
206
- return { kind: 'channel', id: target };
207
- }
208
- const head = target.slice(0, sep);
209
- const rest = target.slice(sep + 1);
210
- if (head === 'private') return { kind: 'private', id: rest };
211
- if (head === 'channel' || head === 'group') return { kind: 'channel', id: rest };
212
- return { kind: 'channel', id: target };
196
+ export function kookInboundConversation(endpointKey: string, msg: KookInboundMessage): ConversationRef {
197
+ return {
198
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
199
+ kind: msg.channelKind,
200
+ id: msg.channelId,
201
+ ...(msg.guildId && msg.channelKind === 'channel'
202
+ ? { parent: { kind: 'channel' as const, id: msg.guildId } }
203
+ : {}),
204
+ };
213
205
  }
214
206
 
215
207
  export function senderDisplayName(msg: KookInboundMessage): string {