@zhin.js/adapter-napcat 6.0.3 → 6.0.6

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,95 @@
1
1
  # @zhin.js/adapter-napcat
2
2
 
3
+ ## 6.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2d0a622]
8
+ - @zhin.js/command@1.0.10
9
+ - @zhin.js/adapter@1.1.7
10
+ - @zhin.js/core@1.5.5
11
+ - @zhin.js/agent@1.1.6
12
+ - zhin.js@6.0.5
13
+
14
+ ## 6.0.5
15
+
16
+ ### Patch Changes
17
+
18
+ - 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
19
+
20
+ All adapter platform-permit checkers now accept PermissionSubject
21
+ (duck-typed from Message/CommandSession) instead of Message directly.
22
+ registerPlatformPermitChecker is replaced by host.registerPlatform in
23
+ the adapter plugin setup. activity-feedback service updated for new
24
+ PermissionHost integration.
25
+
26
+ - Updated dependencies [6f9c366]
27
+ - Updated dependencies [373a56b]
28
+ - Updated dependencies [0de46a8]
29
+ - Updated dependencies [c106ecc]
30
+ - Updated dependencies [ca92e03]
31
+ - Updated dependencies [b0f37ae]
32
+ - Updated dependencies [ba08a2f]
33
+ - Updated dependencies [b08f7fe]
34
+ - Updated dependencies [daffd4c]
35
+ - Updated dependencies [36c7400]
36
+ - Updated dependencies [a9fa72e]
37
+ - Updated dependencies [c8de3ef]
38
+ - Updated dependencies [60f0fc8]
39
+ - Updated dependencies [574c990]
40
+ - Updated dependencies [d096f16]
41
+ - Updated dependencies [3f29623]
42
+ - Updated dependencies [2916852]
43
+ - Updated dependencies [d047869]
44
+ - Updated dependencies [162fa34]
45
+ - Updated dependencies [e62561e]
46
+ - Updated dependencies [3eeeb46]
47
+ - Updated dependencies [85d0f82]
48
+ - Updated dependencies [61bfc1c]
49
+ - Updated dependencies [04bad47]
50
+ - Updated dependencies [e40b048]
51
+ - Updated dependencies [5a5b1bb]
52
+ - Updated dependencies [f1708c3]
53
+ - Updated dependencies [d254a81]
54
+ - Updated dependencies [7123c47]
55
+ - Updated dependencies [05befc1]
56
+ - Updated dependencies [0663b6a]
57
+ - Updated dependencies [f28f9b3]
58
+ - Updated dependencies [9f340f7]
59
+ - Updated dependencies [e53444f]
60
+ - Updated dependencies [5eedd26]
61
+ - Updated dependencies [92b0dd7]
62
+ - Updated dependencies [f919b6f]
63
+ - Updated dependencies [098e411]
64
+ - Updated dependencies [e1b7c01]
65
+ - Updated dependencies [9b94f87]
66
+ - Updated dependencies [a7df753]
67
+ - @zhin.js/agent@1.1.5
68
+ - @zhin.js/host-http@1.0.8
69
+ - @zhin.js/permission@1.0.1
70
+ - @zhin.js/im-contract@1.0.3
71
+ - @zhin.js/adapter@1.1.7
72
+ - @zhin.js/plugin-runtime@1.1.5
73
+ - @zhin.js/command@1.0.9
74
+ - @zhin.js/core@1.5.4
75
+ - zhin.js@6.0.4
76
+
77
+ ## 6.0.4
78
+
79
+ ### Patch Changes
80
+
81
+ - f8c7a54: fix: im
82
+ - Updated dependencies [f8c7a54]
83
+ - @zhin.js/logger@1.0.76
84
+ - @zhin.js/host-http@1.0.7
85
+ - @zhin.js/adapter@1.1.6
86
+ - @zhin.js/agent@1.1.4
87
+ - @zhin.js/command@1.0.8
88
+ - @zhin.js/core@1.5.3
89
+ - @zhin.js/im-contract@1.0.2
90
+ - @zhin.js/plugin-runtime@1.1.4
91
+ - zhin.js@6.0.3
92
+
3
93
  ## 6.0.3
4
94
 
5
95
  ### Patch Changes
package/README.md CHANGED
@@ -8,7 +8,7 @@ Zhin.js [NapCatQQ](https://github.com/NapNeko/NapCatQQ) 适配器(Plugin Runti
8
8
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
9
9
  - **正向 WebSocket**(`connection: ws`):应用连 NapCat WS
10
10
  - `access_token` 鉴权(Bearer + query)
11
- - 入站经 `messageGatewayToken`(去重 + 自发过滤);出站 `send({ target, payload })`
11
+ - 入站经 `messageGatewayToken`(去重 + 自发过滤);出站 `send({ conversation, payload })`
12
12
  - 41 个 AI 工具(`agent/tools/`)
13
13
 
14
14
  ## 安装
@@ -25,8 +25,8 @@ export default defineAdapter({
25
25
  const config = resolveNapCatConfig(context.config);
26
26
  const gateway = context.use(messageGatewayToken);
27
27
  // 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
28
- context.use(napcatRuntimeStateToken).endpoints.set(config.name, {
29
- name: config.name,
28
+ context.use(napcatRuntimeStateToken).endpoints.set(config.id, {
29
+ id: config.id,
30
30
  mode: config.connection,
31
31
  });
32
32
  if (config.connection === 'wss') {
@@ -36,8 +36,8 @@ export default defineAdapter<NapCatAdapterConfig>({
36
36
  const config = resolveNapCatConfig(context.config);
37
37
  const gateway = context.use(messageGatewayToken);
38
38
  // 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
39
- context.use(napcatRuntimeStateToken).endpoints.set(config.name, {
40
- name: config.name,
39
+ context.use(napcatRuntimeStateToken).endpoints.set(config.id, {
40
+ id: config.id,
41
41
  mode: config.connection,
42
42
  });
43
43
  if (config.connection === 'wss') {
@@ -1,12 +1,12 @@
1
- import { formatCompact, getLogger } from '@zhin.js/logger';
1
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
2
  import { createNapCatEndpointManagement } from './endpoint-management.js';
3
3
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
4
4
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
5
5
  import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
6
6
  import { readRequestBody } from './webhook.js';
7
7
  import { verifyNapCatAccessToken } from './wss-auth.js';
8
- const logger = getLogger('napcat');
9
8
  export class NapCatHttpEndpoint {
9
+ #logger;
10
10
  #options;
11
11
  #inboundDeduper = new InboundMessageDeduper();
12
12
  management = createNapCatEndpointManagement(this);
@@ -16,6 +16,7 @@ export class NapCatHttpEndpoint {
16
16
  #started = false;
17
17
  #unregisterAgent;
18
18
  constructor(options) {
19
+ this.#logger = getAdapterLogger('napcat', options.config.id);
19
20
  this.#options = options;
20
21
  this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
21
22
  }
@@ -23,11 +24,11 @@ export class NapCatHttpEndpoint {
23
24
  if (this.#started)
24
25
  return;
25
26
  this.#started = true;
26
- this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.name, this);
27
+ this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.id, this);
27
28
  this.#setupRoutes();
28
- logger.info(formatCompact({
29
+ this.#logger.info(formatCompact({
29
30
  op: 'listen',
30
- endpoint: this.#options.config.name,
31
+ endpoint: this.#options.config.id,
31
32
  mode: 'http',
32
33
  path: this.#options.config.post_path,
33
34
  }));
@@ -46,7 +47,7 @@ export class NapCatHttpEndpoint {
46
47
  this.#unregisterAgent = undefined;
47
48
  this.#inboundDeduper.clear();
48
49
  this.#started = false;
49
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
50
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
50
51
  }
51
52
  async send({ conversation, payload }) {
52
53
  const message = formatOutboundSegments(payload);
@@ -57,9 +58,9 @@ export class NapCatHttpEndpoint {
57
58
  }, action, params);
58
59
  const data = resp.data;
59
60
  const messageId = data?.message_id != null ? String(data.message_id) : '';
60
- logger.debug(formatCompact({
61
+ this.#logger.debug(formatCompact({
61
62
  op: 'napcat_send',
62
- endpoint: this.#options.config.name,
63
+ endpoint: this.#options.config.id,
63
64
  target: `${conversation.kind}:${conversation.id}`,
64
65
  messageId,
65
66
  }));
@@ -94,20 +95,24 @@ export class NapCatHttpEndpoint {
94
95
  conversation,
95
96
  message: { conversation, id: msgId },
96
97
  content: formatInboundContent(ev),
97
- sender: senderUserId(ev),
98
+ sender: {
99
+ id: senderUserId(ev),
100
+ name: nickname,
101
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
102
+ },
103
+ endpointId: this.#options.config.id,
104
+ ...(mentioned ? { mentioned: true } : {}),
98
105
  metadata: Object.freeze({
99
106
  message_type: ev.message_type,
100
107
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
101
108
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
102
- endpoint: this.#options.config.name,
103
109
  time: ev.time,
104
110
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
105
111
  role: ev.sender?.role,
106
112
  ...(nickname ? { nickname } : {}),
107
- ...(mentioned ? { mentioned: true } : {}),
108
113
  }),
109
114
  }).catch((err) => {
110
- logger.warn(formatCompact({
115
+ this.#logger.warn(formatCompact({
111
116
  op: 'napcat_gateway_receive_failed',
112
117
  target: `${conversation.kind}:${conversation.id}`,
113
118
  error: err instanceof Error ? err.message : String(err),
@@ -143,7 +148,7 @@ export class NapCatHttpEndpoint {
143
148
  response.end(JSON.stringify({ status: 'ok' }));
144
149
  }
145
150
  catch (error) {
146
- logger.error('NapCat HTTP webhook error:', error);
151
+ this.#logger.error('NapCat HTTP webhook error:', error);
147
152
  if (!response.headersSent) {
148
153
  response.writeHead(500, { 'Content-Type': 'application/json' });
149
154
  response.end(JSON.stringify({ message: 'Internal Server Error' }));
@@ -41,11 +41,11 @@ export interface NapcatAgentEndpoint {
41
41
  sendGroupAiRecord(groupId: number, characterId: string, text: string): Promise<unknown>;
42
42
  }
43
43
  export interface NapcatAgentDeps {
44
- getEndpoint: (endpointId: string) => NapcatAgentEndpoint;
44
+ getEndpoint: (endpointKey: string) => NapcatAgentEndpoint;
45
45
  }
46
- export declare function registerNapcatAgentEndpoint(endpointId: string, endpoint: NapcatAgentEndpoint): () => void;
46
+ export declare function registerNapcatAgentEndpoint(endpointKey: string, endpoint: NapcatAgentEndpoint): () => void;
47
47
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
48
48
  export declare function setNapcatAgentDeps(deps: NapcatAgentDeps | null): void;
49
49
  export declare function getNapcatAgentDeps(): NapcatAgentDeps;
50
50
  /** Convenience for agent/tools (unchanged call sites). */
51
- export declare function getEndpoint(endpointId: string): NapcatAgentEndpoint;
51
+ export declare function getEndpoint(endpointKey: string): NapcatAgentEndpoint;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerNapcatAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerNapcatAgentEndpoint(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
  }
@@ -16,10 +16,10 @@ export function registerNapcatAgentEndpoint(endpointId, endpoint) {
16
16
  export function setNapcatAgentDeps(deps) {
17
17
  override = deps;
18
18
  }
19
- function lookup(endpointId) {
20
- const registered = endpoints.get(endpointId);
19
+ function lookup(endpointKey) {
20
+ const registered = endpoints.get(endpointKey);
21
21
  if (!registered)
22
- throw new Error(`Endpoint ${endpointId} not found`);
22
+ throw new Error(`Endpoint ${endpointKey} not found`);
23
23
  return registered;
24
24
  }
25
25
  export function getNapcatAgentDeps() {
@@ -28,6 +28,6 @@ export function getNapcatAgentDeps() {
28
28
  return { getEndpoint: lookup };
29
29
  }
30
30
  /** Convenience for agent/tools (unchanged call sites). */
31
- export function getEndpoint(endpointId) {
32
- return getNapcatAgentDeps().getEndpoint(endpointId);
31
+ export function getEndpoint(endpointKey) {
32
+ return getNapcatAgentDeps().getEndpoint(endpointKey);
33
33
  }
package/lib/protocol.d.ts CHANGED
@@ -9,7 +9,7 @@ import type { ConversationRef } from '@zhin.js/im-contract';
9
9
  export interface NapCatLegacyEndpointRow {
10
10
  readonly context?: string;
11
11
  readonly connection?: 'ws' | 'wss' | 'http';
12
- readonly name?: string;
12
+ readonly id?: string;
13
13
  readonly access_token?: string;
14
14
  readonly url?: string;
15
15
  readonly path?: string;
@@ -22,7 +22,7 @@ export interface NapCatLegacyEndpointRow {
22
22
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
23
23
  export interface NapCatAdapterConfig {
24
24
  readonly connection?: 'ws' | 'wss' | 'http';
25
- readonly name?: string;
25
+ readonly id?: string;
26
26
  readonly access_token?: string;
27
27
  readonly url?: string;
28
28
  readonly path?: string;
@@ -36,7 +36,7 @@ export interface NapCatAdapterConfig {
36
36
  }
37
37
  export interface NapCatConfigBase {
38
38
  readonly context: 'napcat';
39
- readonly name: string;
39
+ readonly id: string;
40
40
  readonly access_token?: string;
41
41
  }
42
42
  /** 正向 WebSocket:应用连 NapCat WS */
@@ -121,7 +121,7 @@ export declare function getChannelId(ev: NapCatEvent): string;
121
121
  * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
122
122
  * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
123
123
  */
124
- export declare function napcatInboundConversation(endpointId: string, ev: NapCatEvent): ConversationRef;
124
+ export declare function napcatInboundConversation(endpointKey: string, ev: NapCatEvent): ConversationRef;
125
125
  /** 出站:ConversationRef → OneBot 私聊/群聊目标。 */
126
126
  export declare function napcatOutboundTarget(conversation: ConversationRef): ParsedSendTarget;
127
127
  export declare function formatInboundContent(ev: NapCatEvent): string;
package/lib/protocol.js CHANGED
@@ -14,8 +14,8 @@ function normalizeConnection(connection) {
14
14
  export function resolveNapCatConfig(config = {}) {
15
15
  const entry = config.endpoints?.find((item) => item.context === 'napcat');
16
16
  const connection = normalizeConnection(config.connection ?? entry?.connection);
17
- const name = (typeof config.name === 'string' && config.name)
18
- || (typeof entry?.name === 'string' && entry.name)
17
+ const id = (typeof config.id === 'string' && config.id)
18
+ || (typeof entry?.id === 'string' && entry.id)
19
19
  || process.env.NAPCAT_BOT_NAME
20
20
  || 'napcat-bot';
21
21
  const access_token = config.access_token ?? entry?.access_token;
@@ -27,7 +27,7 @@ export function resolveNapCatConfig(config = {}) {
27
27
  return {
28
28
  context: 'napcat',
29
29
  connection: 'ws',
30
- name,
30
+ id,
31
31
  access_token,
32
32
  url,
33
33
  reconnect_interval: config.reconnect_interval ?? entry?.reconnect_interval ?? 5000,
@@ -41,7 +41,7 @@ export function resolveNapCatConfig(config = {}) {
41
41
  return {
42
42
  context: 'napcat',
43
43
  connection: 'wss',
44
- name,
44
+ id,
45
45
  access_token,
46
46
  path,
47
47
  heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
@@ -56,7 +56,7 @@ export function resolveNapCatConfig(config = {}) {
56
56
  return {
57
57
  context: 'napcat',
58
58
  connection: 'http',
59
- name,
59
+ id,
60
60
  access_token,
61
61
  http_url,
62
62
  post_path,
@@ -82,8 +82,8 @@ export function getChannelId(ev) {
82
82
  * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
83
83
  * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
84
84
  */
85
- export function napcatInboundConversation(endpointId, ev) {
86
- const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
85
+ export function napcatInboundConversation(endpointKey, ev) {
86
+ const endpoint = { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey };
87
87
  const isGroup = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private');
88
88
  if (isGroup && ev.group_id != null) {
89
89
  return { endpoint, kind: 'group', id: String(ev.group_id) };
@@ -3,14 +3,14 @@
3
3
  */
4
4
  import WebSocket from 'ws';
5
5
  import { createEndpointLifecycle, } from '@zhin.js/adapter';
6
- import { formatCompact, getLogger } from '@zhin.js/logger';
6
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
7
  import { createNapCatEndpointManagement } from './endpoint-management.js';
8
8
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
9
9
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
10
10
  import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
11
11
  import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, } from './ws-transport.js';
12
- const logger = getLogger('napcat');
13
12
  export class NapCatWsEndpoint {
13
+ #logger;
14
14
  #options;
15
15
  #inboundDeduper = new InboundMessageDeduper();
16
16
  management = createNapCatEndpointManagement(this);
@@ -21,9 +21,10 @@ export class NapCatWsEndpoint {
21
21
  #open = false;
22
22
  #unregisterAgent;
23
23
  constructor(options) {
24
+ this.#logger = getAdapterLogger('napcat', options.config.id);
24
25
  this.#options = options;
25
26
  this.#lifecycle = createEndpointLifecycle({
26
- name: options.config.name,
27
+ name: options.config.id,
27
28
  // reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
28
29
  reconnect: {
29
30
  initialIntervalMs: options.config.reconnect_interval,
@@ -36,7 +37,7 @@ export class NapCatWsEndpoint {
36
37
  async start() {
37
38
  if (this.#lifecycle.started)
38
39
  return;
39
- this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.name, this);
40
+ this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.id, this);
40
41
  try {
41
42
  await this.#lifecycle.start((handle) => this.#connect(handle));
42
43
  }
@@ -75,9 +76,9 @@ export class NapCatWsEndpoint {
75
76
  const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
76
77
  const data = await this.callApi(action, params);
77
78
  const messageId = data?.message_id != null ? String(data.message_id) : '';
78
- logger.debug(formatCompact({
79
+ this.#logger.debug(formatCompact({
79
80
  op: 'napcat_send',
80
- endpoint: this.#options.config.name,
81
+ endpoint: this.#options.config.id,
81
82
  target: `${conversation.kind}:${conversation.id}`,
82
83
  messageId,
83
84
  }));
@@ -245,20 +246,24 @@ export class NapCatWsEndpoint {
245
246
  conversation,
246
247
  message: { conversation, id: msgId },
247
248
  content,
248
- sender: senderUserId(ev),
249
+ sender: {
250
+ id: senderUserId(ev),
251
+ name: nickname,
252
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
253
+ },
254
+ endpointId: this.#options.config.id,
255
+ ...(mentioned ? { mentioned: true } : {}),
249
256
  metadata: Object.freeze({
250
257
  message_type: ev.message_type,
251
258
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
252
259
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
253
- endpoint: this.#options.config.name,
254
260
  time: ev.time,
255
261
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
256
262
  role: ev.sender?.role,
257
263
  ...(nickname ? { nickname } : {}),
258
- ...(mentioned ? { mentioned: true } : {}),
259
264
  }),
260
265
  }).catch((err) => {
261
- logger.warn(formatCompact({
266
+ this.#logger.warn(formatCompact({
262
267
  op: 'napcat_gateway_receive_failed',
263
268
  target: `${conversation.kind}:${conversation.id}`,
264
269
  error: err instanceof Error ? err.message : String(err),
@@ -286,14 +291,14 @@ export class NapCatWsEndpoint {
286
291
  return;
287
292
  settled = true;
288
293
  if (!this.#options.config.access_token) {
289
- logger.warn(formatCompact({
290
- endpoint: this.#options.config.name,
294
+ this.#logger.warn(formatCompact({
295
+ endpoint: this.#options.config.id,
291
296
  ok: false,
292
297
  error: 'missing access_token',
293
298
  }));
294
299
  }
295
- logger.debug(formatCompact({
296
- endpoint: this.#options.config.name,
300
+ this.#logger.debug(formatCompact({
301
+ endpoint: this.#options.config.id,
297
302
  mode: 'ws',
298
303
  url: safeUrl,
299
304
  }));
@@ -312,7 +317,7 @@ export class NapCatWsEndpoint {
312
317
  });
313
318
  ws.on('message', (data) => {
314
319
  handleNapCatWsMessage(data, {
315
- endpointName: this.#options.config.name,
320
+ endpointId: this.#options.config.id,
316
321
  pending: this.#pending,
317
322
  admit: (event) => this.admit(event),
318
323
  });
@@ -329,9 +334,8 @@ export class NapCatWsEndpoint {
329
334
  : codeNum === 1006
330
335
  ? ' [abnormal]'
331
336
  : '';
332
- logger.warn(formatCompact({
337
+ this.#logger.warn(formatCompact({
333
338
  op: 'disconnect',
334
- endpoint: this.#options.config.name,
335
339
  code: codeNum,
336
340
  error: `${reasonStr || 'closed'}${codeHint}`,
337
341
  reconnect_ms: this.#options.config.reconnect_interval,
@@ -345,9 +349,9 @@ export class NapCatWsEndpoint {
345
349
  });
346
350
  ws.on('error', (err) => {
347
351
  const error = err instanceof Error ? err : new Error(String(err));
348
- logger.warn(formatCompact({
352
+ this.#logger.warn(formatCompact({
349
353
  op: 'ws_error',
350
- endpoint: this.#options.config.name,
354
+ endpoint: this.#options.config.id,
351
355
  ok: false,
352
356
  error: error.message,
353
357
  }));
@@ -2,7 +2,7 @@ import { type NapCatEvent } from './protocol.js';
2
2
  import { type NapCatPendingAction, type NapCatWsSocket } from './ws-types.js';
3
3
  export declare function decodeWsPayload(data: unknown): string;
4
4
  export declare function handleNapCatWsMessage(data: unknown, options: {
5
- readonly endpointName: string;
5
+ readonly endpointId: string;
6
6
  readonly pending: Map<string, NapCatPendingAction>;
7
7
  readonly admit: (ev: NapCatEvent) => void;
8
8
  }): void;
@@ -34,7 +34,7 @@ export function handleNapCatWsMessage(data, options) {
34
34
  catch (error) {
35
35
  logger.warn(formatCompact({
36
36
  op: 'napcat_parse_failed',
37
- endpoint: options.endpointName,
37
+ endpoint: options.endpointId,
38
38
  error: error instanceof Error ? error.message : String(error),
39
39
  }));
40
40
  }
@@ -2,15 +2,15 @@
2
2
  * NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
- import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
6
  import { createNapCatEndpointManagement } from './endpoint-management.js';
7
7
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
8
8
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
9
9
  import { buildSendAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
10
10
  import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
11
11
  import { verifyNapCatAccessToken } from './wss-auth.js';
12
- const logger = getLogger('napcat');
13
12
  export class NapCatWssEndpoint {
13
+ #logger;
14
14
  #options;
15
15
  #inboundDeduper = new InboundMessageDeduper();
16
16
  management = createNapCatEndpointManagement(this);
@@ -23,20 +23,21 @@ export class NapCatWssEndpoint {
23
23
  #started = false;
24
24
  #unregisterAgent;
25
25
  constructor(options) {
26
+ this.#logger = getAdapterLogger('napcat', options.config.id);
26
27
  this.#options = options;
27
28
  }
28
29
  async start() {
29
30
  if (this.#started)
30
31
  return;
31
32
  this.#started = true;
32
- this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.name, this);
33
+ this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.id, this);
33
34
  const handle = this.#options.http.ws(this.#options.config.path);
34
35
  this.#wsRelease = handle.onConnection((connection) => {
35
36
  this.#acceptConnection(connection);
36
37
  });
37
- logger.info(formatCompact({
38
+ this.#logger.info(formatCompact({
38
39
  op: 'listen',
39
- endpoint: this.#options.config.name,
40
+ endpoint: this.#options.config.id,
40
41
  mode: 'wss',
41
42
  path: this.#options.config.path,
42
43
  }));
@@ -102,20 +103,24 @@ export class NapCatWssEndpoint {
102
103
  conversation,
103
104
  message: { conversation, id: msgId },
104
105
  content: formatInboundContent(ev),
105
- sender: senderUserId(ev),
106
+ sender: {
107
+ id: senderUserId(ev),
108
+ name: nickname,
109
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
110
+ },
111
+ endpointId: this.#options.config.id,
112
+ ...(mentioned ? { mentioned: true } : {}),
106
113
  metadata: Object.freeze({
107
114
  message_type: ev.message_type,
108
115
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
109
116
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
110
- endpoint: this.#options.config.name,
111
117
  time: ev.time,
112
118
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
113
119
  role: ev.sender?.role,
114
120
  ...(nickname ? { nickname } : {}),
115
- ...(mentioned ? { mentioned: true } : {}),
116
121
  }),
117
122
  }).catch((err) => {
118
- logger.warn(formatCompact({
123
+ this.#logger.warn(formatCompact({
119
124
  op: 'napcat_gateway_receive_failed',
120
125
  target: `${conversation.kind}:${conversation.id}`,
121
126
  error: err instanceof Error ? err.message : String(err),
@@ -140,7 +145,7 @@ export class NapCatWssEndpoint {
140
145
  this.#heartbeatTimer = startNapCatHeartbeat(this.#ws, this.#options.config.heartbeat_interval, this.#heartbeatTimer);
141
146
  socket.on('message', (data) => {
142
147
  handleNapCatWsMessage(data, {
143
- endpointName: this.#options.config.name,
148
+ endpointId: this.#options.config.id,
144
149
  pending: this.#pending,
145
150
  admit: (event) => this.admit(event),
146
151
  });
@@ -154,8 +159,8 @@ export class NapCatWssEndpoint {
154
159
  }
155
160
  }
156
161
  });
157
- logger.debug(formatCompact({
158
- endpoint: this.#options.config.name,
162
+ this.#logger.debug(formatCompact({
163
+ endpoint: this.#options.config.id,
159
164
  mode: 'wss',
160
165
  peer: connection.request.socket.remoteAddress,
161
166
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-napcat",
3
- "version": "6.0.3",
3
+ "version": "6.0.6",
4
4
  "description": "Zhin.js NapCat adapter for Plugin Runtime (WebSocket client, OneBot11 + NapCat extensions)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -43,13 +43,14 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "ws": "^8.21.1",
46
- "@zhin.js/adapter": "1.1.5",
47
- "@zhin.js/command": "1.0.7",
48
- "@zhin.js/core": "1.5.2",
49
- "@zhin.js/host-http": "1.0.6",
50
- "@zhin.js/im-contract": "1.0.1",
51
- "@zhin.js/logger": "1.0.75",
52
- "@zhin.js/plugin-runtime": "1.1.3"
46
+ "@zhin.js/adapter": "1.1.7",
47
+ "@zhin.js/command": "1.0.10",
48
+ "@zhin.js/core": "1.5.5",
49
+ "@zhin.js/host-http": "1.0.8",
50
+ "@zhin.js/im-contract": "1.0.3",
51
+ "@zhin.js/logger": "1.0.76",
52
+ "@zhin.js/permission": "1.0.1",
53
+ "@zhin.js/plugin-runtime": "1.1.5"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@types/node": "^26.1.2",
@@ -57,16 +58,16 @@
57
58
  "typescript": "^6.0.3",
58
59
  "vitest": "^4.1.10",
59
60
  "zod": "^4.4.3",
60
- "@zhin.js/agent": "1.1.3",
61
- "@zhin.js/host-http": "1.0.6"
61
+ "@zhin.js/agent": "1.1.6",
62
+ "@zhin.js/host-http": "1.0.8"
62
63
  },
63
64
  "peerDependencies": {
64
65
  "zod": "^4.0.0",
65
- "@zhin.js/adapter": "1.1.5",
66
- "@zhin.js/agent": "1.1.3",
67
- "@zhin.js/core": "1.5.2",
68
- "@zhin.js/plugin-runtime": "1.1.3",
69
- "zhin.js": "6.0.2"
66
+ "@zhin.js/adapter": "1.1.7",
67
+ "@zhin.js/agent": "1.1.6",
68
+ "@zhin.js/core": "1.5.5",
69
+ "@zhin.js/plugin-runtime": "1.1.5",
70
+ "zhin.js": "6.0.5"
70
71
  },
71
72
  "peerDependenciesMeta": {
72
73
  "zhin.js": {
package/plugin.js CHANGED
@@ -1,7 +1,7 @@
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 { registerDefaultScenePlatformPermitChecker } from '@zhin.js/core';
4
+ import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
5
5
  import { napcatRuntimeStateToken } from "./lib/napcat-runtime-state.js";
6
6
  export default definePlugin({
7
7
  name: 'napcat',
@@ -9,9 +9,10 @@ export default definePlugin({
9
9
  displayName: 'NapCat Adapter',
10
10
  },
11
11
  setup(context) {
12
- // 运行中 endpoint 注册表(napcat.endpoint list 的"运行中"数据源)
13
12
  context.resources.provide(napcatRuntimeStateToken, createEndpointRuntimeState());
14
- // 平台权限门禁:scene_admin / scene_owner 由 sender role 判定(见各 endpoint admit metadata)
15
- return registerDefaultScenePlatformPermitChecker('napcat');
13
+ if (context.resources.has(permissionHostToken)) {
14
+ const host = context.resources.use(permissionHostToken);
15
+ return host.registerPlatform('napcat', createSceneRolePlatformChecker());
16
+ }
16
17
  },
17
18
  });
package/schema.json CHANGED
@@ -25,16 +25,48 @@
25
25
  "type": "number",
26
26
  "default": 30000
27
27
  },
28
+ "master": {
29
+ "type": [
30
+ "string",
31
+ "number"
32
+ ],
33
+ "description": "框架 master(QQ uin;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
34
+ },
35
+ "trusted": {
36
+ "type": "array",
37
+ "items": {
38
+ "type": [
39
+ "string",
40
+ "number"
41
+ ],
42
+ "description": "Trusted QQ uin"
43
+ },
44
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
45
+ },
28
46
  "endpoints": {
29
47
  "type": "array",
30
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
48
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
31
49
  "items": {
32
50
  "type": "object",
33
51
  "additionalProperties": true,
34
52
  "properties": {
35
- "name": {
36
- "type": "string",
37
- "description": "NapCat bot name"
53
+ "master": {
54
+ "type": [
55
+ "string",
56
+ "number"
57
+ ],
58
+ "description": "本 endpoint 的框架 master(QQ uin);覆盖顶层 master"
59
+ },
60
+ "trusted": {
61
+ "type": "array",
62
+ "items": {
63
+ "type": [
64
+ "string",
65
+ "number"
66
+ ],
67
+ "description": "Trusted QQ uin"
68
+ },
69
+ "description": "本 endpoint 的 trusted 列表"
38
70
  },
39
71
  "url": {
40
72
  "type": "string",
@@ -55,10 +87,14 @@
55
87
  "access_token": {
56
88
  "type": "string",
57
89
  "description": "NapCat access token"
90
+ },
91
+ "id": {
92
+ "type": "string",
93
+ "description": "NapCat bot name"
58
94
  }
59
95
  },
60
96
  "required": [
61
- "name"
97
+ "id"
62
98
  ]
63
99
  }
64
100
  },
@@ -5,7 +5,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
- import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
9
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
10
10
  import { createNapCatEndpointManagement } from './endpoint-management.js';
11
11
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
@@ -32,8 +32,6 @@ import { readRequestBody } from './webhook.js';
32
32
  import { NapCatWsEndpoint } from './ws-endpoint.js';
33
33
  import { verifyNapCatAccessToken } from './wss-auth.js';
34
34
 
35
- const logger = getLogger('napcat');
36
-
37
35
  export interface NapCatHttpEndpointOptions {
38
36
  readonly id: CapabilityId;
39
37
  readonly gateway: MessageGateway;
@@ -43,6 +41,8 @@ export interface NapCatHttpEndpointOptions {
43
41
  }
44
42
 
45
43
  export class NapCatHttpEndpoint implements EndpointInstance {
44
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
45
+
46
46
  readonly #options: NapCatHttpEndpointOptions;
47
47
  readonly #inboundDeduper = new InboundMessageDeduper();
48
48
  readonly management: EndpointManagement = createNapCatEndpointManagement(this);
@@ -53,6 +53,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
53
53
  #unregisterAgent?: () => void;
54
54
 
55
55
  constructor(options: NapCatHttpEndpointOptions) {
56
+ this.#logger = getAdapterLogger('napcat', options.config.id);
56
57
  this.#options = options;
57
58
  this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
58
59
  }
@@ -61,13 +62,13 @@ export class NapCatHttpEndpoint implements EndpointInstance {
61
62
  if (this.#started) return;
62
63
  this.#started = true;
63
64
  this.#unregisterAgent = registerNapcatAgentEndpoint(
64
- this.#options.config.name,
65
+ this.#options.config.id,
65
66
  this as unknown as NapCatWsEndpoint,
66
67
  );
67
68
  this.#setupRoutes();
68
- logger.info(formatCompact({
69
+ this.#logger.info(formatCompact({
69
70
  op: 'listen',
70
- endpoint: this.#options.config.name,
71
+ endpoint: this.#options.config.id,
71
72
  mode: 'http',
72
73
  path: this.#options.config.post_path,
73
74
  }));
@@ -88,7 +89,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
88
89
  this.#unregisterAgent = undefined;
89
90
  this.#inboundDeduper.clear();
90
91
  this.#started = false;
91
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
92
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
92
93
  }
93
94
 
94
95
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
@@ -104,9 +105,9 @@ export class NapCatHttpEndpoint implements EndpointInstance {
104
105
  );
105
106
  const data = resp.data as { message_id?: number | string } | undefined;
106
107
  const messageId = data?.message_id != null ? String(data.message_id) : '';
107
- logger.debug(formatCompact({
108
+ this.#logger.debug(formatCompact({
108
109
  op: 'napcat_send',
109
- endpoint: this.#options.config.name,
110
+ endpoint: this.#options.config.id,
110
111
  target: `${conversation.kind}:${conversation.id}`,
111
112
  messageId,
112
113
  }));
@@ -144,20 +145,24 @@ export class NapCatHttpEndpoint implements EndpointInstance {
144
145
  conversation,
145
146
  message: { conversation, id: msgId },
146
147
  content: formatInboundContent(ev),
147
- sender: senderUserId(ev),
148
+ sender: {
149
+ id: senderUserId(ev),
150
+ name: nickname,
151
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
152
+ },
153
+ endpointId: this.#options.config.id,
154
+ ...(mentioned ? { mentioned: true } : {}),
148
155
  metadata: Object.freeze({
149
156
  message_type: ev.message_type,
150
157
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
151
158
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
152
- endpoint: this.#options.config.name,
153
159
  time: ev.time,
154
160
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
155
161
  role: ev.sender?.role,
156
162
  ...(nickname ? { nickname } : {}),
157
- ...(mentioned ? { mentioned: true } : {}),
158
163
  }),
159
164
  }).catch((err) => {
160
- logger.warn(formatCompact({
165
+ this.#logger.warn(formatCompact({
161
166
  op: 'napcat_gateway_receive_failed',
162
167
  target: `${conversation.kind}:${conversation.id}`,
163
168
  error: err instanceof Error ? err.message : String(err),
@@ -194,7 +199,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
194
199
  response.writeHead(200, { 'Content-Type': 'application/json' });
195
200
  response.end(JSON.stringify({ status: 'ok' }));
196
201
  } catch (error) {
197
- logger.error('NapCat HTTP webhook error:', error);
202
+ this.#logger.error('NapCat HTTP webhook error:', error);
198
203
  if (!response.headersSent) {
199
204
  response.writeHead(500, { 'Content-Type': 'application/json' });
200
205
  response.end(JSON.stringify({ message: 'Internal Server Error' }));
@@ -55,20 +55,20 @@ export interface NapcatAgentEndpoint {
55
55
  }
56
56
 
57
57
  export interface NapcatAgentDeps {
58
- getEndpoint: (endpointId: string) => NapcatAgentEndpoint;
58
+ getEndpoint: (endpointKey: string) => NapcatAgentEndpoint;
59
59
  }
60
60
 
61
61
  const endpoints = new Map<string, NapcatAgentEndpoint>();
62
62
  let override: NapcatAgentDeps | null = null;
63
63
 
64
64
  export function registerNapcatAgentEndpoint(
65
- endpointId: string,
65
+ endpointKey: string,
66
66
  endpoint: NapcatAgentEndpoint,
67
67
  ): () => void {
68
- endpoints.set(endpointId, endpoint);
68
+ endpoints.set(endpointKey, endpoint);
69
69
  return () => {
70
- if (endpoints.get(endpointId) === endpoint) {
71
- endpoints.delete(endpointId);
70
+ if (endpoints.get(endpointKey) === endpoint) {
71
+ endpoints.delete(endpointKey);
72
72
  }
73
73
  };
74
74
  }
@@ -78,9 +78,9 @@ export function setNapcatAgentDeps(deps: NapcatAgentDeps | null): void {
78
78
  override = deps;
79
79
  }
80
80
 
81
- function lookup(endpointId: string): NapcatAgentEndpoint {
82
- const registered = endpoints.get(endpointId);
83
- if (!registered) throw new Error(`Endpoint ${endpointId} not found`);
81
+ function lookup(endpointKey: string): NapcatAgentEndpoint {
82
+ const registered = endpoints.get(endpointKey);
83
+ if (!registered) throw new Error(`Endpoint ${endpointKey} not found`);
84
84
  return registered;
85
85
  }
86
86
 
@@ -90,6 +90,6 @@ export function getNapcatAgentDeps(): NapcatAgentDeps {
90
90
  }
91
91
 
92
92
  /** Convenience for agent/tools (unchanged call sites). */
93
- export function getEndpoint(endpointId: string): NapcatAgentEndpoint {
94
- return getNapcatAgentDeps().getEndpoint(endpointId);
93
+ export function getEndpoint(endpointKey: string): NapcatAgentEndpoint {
94
+ return getNapcatAgentDeps().getEndpoint(endpointKey);
95
95
  }
package/src/protocol.ts CHANGED
@@ -13,7 +13,7 @@ const logger = getLogger('napcat');
13
13
  export interface NapCatLegacyEndpointRow {
14
14
  readonly context?: string;
15
15
  readonly connection?: 'ws' | 'wss' | 'http';
16
- readonly name?: string;
16
+ readonly id?: string;
17
17
  readonly access_token?: string;
18
18
  readonly url?: string;
19
19
  readonly path?: string;
@@ -27,7 +27,7 @@ export interface NapCatLegacyEndpointRow {
27
27
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
28
28
  export interface NapCatAdapterConfig {
29
29
  readonly connection?: 'ws' | 'wss' | 'http';
30
- readonly name?: string;
30
+ readonly id?: string;
31
31
  readonly access_token?: string;
32
32
  readonly url?: string;
33
33
  readonly path?: string;
@@ -42,7 +42,7 @@ export interface NapCatAdapterConfig {
42
42
 
43
43
  export interface NapCatConfigBase {
44
44
  readonly context: 'napcat';
45
- readonly name: string;
45
+ readonly id: string;
46
46
  readonly access_token?: string;
47
47
  }
48
48
 
@@ -143,8 +143,8 @@ function normalizeConnection(
143
143
  export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedNapCatConfig {
144
144
  const entry = config.endpoints?.find((item) => item.context === 'napcat');
145
145
  const connection = normalizeConnection(config.connection ?? entry?.connection);
146
- const name = (typeof config.name === 'string' && config.name)
147
- || (typeof entry?.name === 'string' && entry.name)
146
+ const id = (typeof config.id === 'string' && config.id)
147
+ || (typeof entry?.id === 'string' && entry.id)
148
148
  || process.env.NAPCAT_BOT_NAME
149
149
  || 'napcat-bot';
150
150
  const access_token = config.access_token ?? entry?.access_token;
@@ -159,7 +159,7 @@ export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedN
159
159
  return {
160
160
  context: 'napcat',
161
161
  connection: 'ws',
162
- name,
162
+ id,
163
163
  access_token,
164
164
  url,
165
165
  reconnect_interval: config.reconnect_interval ?? entry?.reconnect_interval ?? 5000,
@@ -173,7 +173,7 @@ export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedN
173
173
  return {
174
174
  context: 'napcat',
175
175
  connection: 'wss',
176
- name,
176
+ id,
177
177
  access_token,
178
178
  path,
179
179
  heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
@@ -189,7 +189,7 @@ export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedN
189
189
  return {
190
190
  context: 'napcat',
191
191
  connection: 'http',
192
- name,
192
+ id,
193
193
  access_token,
194
194
  http_url,
195
195
  post_path,
@@ -218,8 +218,8 @@ export function getChannelId(ev: NapCatEvent): string {
218
218
  * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
219
219
  * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
220
220
  */
221
- export function napcatInboundConversation(endpointId: string, ev: NapCatEvent): ConversationRef {
222
- const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
221
+ export function napcatInboundConversation(endpointKey: string, ev: NapCatEvent): ConversationRef {
222
+ const endpoint = { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey };
223
223
  const isGroup = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private');
224
224
  if (isGroup && ev.group_id != null) {
225
225
  return { endpoint, kind: 'group', id: String(ev.group_id) };
@@ -11,7 +11,7 @@ import {
11
11
  type EndpointSendRequest,
12
12
  } from '@zhin.js/adapter';
13
13
  import type { MessageGateway } from '@zhin.js/core/runtime';
14
- import { formatCompact, getLogger } from '@zhin.js/logger';
14
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
16
16
  import { createNapCatEndpointManagement } from './endpoint-management.js';
17
17
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
@@ -45,8 +45,6 @@ import {
45
45
  type NapCatWsSocket,
46
46
  } from './ws-types.js';
47
47
 
48
- const logger = getLogger('napcat');
49
-
50
48
  export interface NapCatWsEndpointOptions {
51
49
  readonly id: CapabilityId;
52
50
  readonly gateway: MessageGateway;
@@ -58,6 +56,8 @@ export interface NapCatWsEndpointOptions {
58
56
  }
59
57
 
60
58
  export class NapCatWsEndpoint implements EndpointInstance {
59
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
60
+
61
61
  readonly #options: NapCatWsEndpointOptions;
62
62
  readonly #inboundDeduper = new InboundMessageDeduper();
63
63
  readonly management: EndpointManagement = createNapCatEndpointManagement(this);
@@ -69,9 +69,10 @@ export class NapCatWsEndpoint implements EndpointInstance {
69
69
  #unregisterAgent?: () => void;
70
70
 
71
71
  constructor(options: NapCatWsEndpointOptions) {
72
+ this.#logger = getAdapterLogger('napcat', options.config.id);
72
73
  this.#options = options;
73
74
  this.#lifecycle = createEndpointLifecycle({
74
- name: options.config.name,
75
+ name: options.config.id,
75
76
  // reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
76
77
  reconnect: {
77
78
  initialIntervalMs: options.config.reconnect_interval,
@@ -84,7 +85,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
84
85
 
85
86
  async start(): Promise<void> {
86
87
  if (this.#lifecycle.started) return;
87
- this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.name, this);
88
+ this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.id, this);
88
89
  try {
89
90
  await this.#lifecycle.start((handle) => this.#connect(handle));
90
91
  } catch (err) {
@@ -125,9 +126,9 @@ export class NapCatWsEndpoint implements EndpointInstance {
125
126
  const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
126
127
  const data = await this.callApi(action, params) as { message_id?: number | string } | undefined;
127
128
  const messageId = data?.message_id != null ? String(data.message_id) : '';
128
- logger.debug(formatCompact({
129
+ this.#logger.debug(formatCompact({
129
130
  op: 'napcat_send',
130
- endpoint: this.#options.config.name,
131
+ endpoint: this.#options.config.id,
131
132
  target: `${conversation.kind}:${conversation.id}`,
132
133
  messageId,
133
134
  }));
@@ -336,20 +337,24 @@ export class NapCatWsEndpoint implements EndpointInstance {
336
337
  conversation,
337
338
  message: { conversation, id: msgId },
338
339
  content,
339
- sender: senderUserId(ev),
340
+ sender: {
341
+ id: senderUserId(ev),
342
+ name: nickname,
343
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
344
+ },
345
+ endpointId: this.#options.config.id,
346
+ ...(mentioned ? { mentioned: true } : {}),
340
347
  metadata: Object.freeze({
341
348
  message_type: ev.message_type,
342
349
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
343
350
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
344
- endpoint: this.#options.config.name,
345
351
  time: ev.time,
346
352
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
347
353
  role: ev.sender?.role,
348
354
  ...(nickname ? { nickname } : {}),
349
- ...(mentioned ? { mentioned: true } : {}),
350
355
  }),
351
356
  }).catch((err) => {
352
- logger.warn(formatCompact({
357
+ this.#logger.warn(formatCompact({
353
358
  op: 'napcat_gateway_receive_failed',
354
359
  target: `${conversation.kind}:${conversation.id}`,
355
360
  error: err instanceof Error ? err.message : String(err),
@@ -379,14 +384,14 @@ export class NapCatWsEndpoint implements EndpointInstance {
379
384
  if (settled) return;
380
385
  settled = true;
381
386
  if (!this.#options.config.access_token) {
382
- logger.warn(formatCompact({
383
- endpoint: this.#options.config.name,
387
+ this.#logger.warn(formatCompact({
388
+ endpoint: this.#options.config.id,
384
389
  ok: false,
385
390
  error: 'missing access_token',
386
391
  }));
387
392
  }
388
- logger.debug(formatCompact({
389
- endpoint: this.#options.config.name,
393
+ this.#logger.debug(formatCompact({
394
+ endpoint: this.#options.config.id,
390
395
  mode: 'ws',
391
396
  url: safeUrl,
392
397
  }));
@@ -405,7 +410,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
405
410
 
406
411
  ws.on('message', (data) => {
407
412
  handleNapCatWsMessage(data, {
408
- endpointName: this.#options.config.name,
413
+ endpointId: this.#options.config.id,
409
414
  pending: this.#pending,
410
415
  admit: (event) => this.admit(event),
411
416
  });
@@ -423,9 +428,8 @@ export class NapCatWsEndpoint implements EndpointInstance {
423
428
  : codeNum === 1006
424
429
  ? ' [abnormal]'
425
430
  : '';
426
- logger.warn(formatCompact({
431
+ this.#logger.warn(formatCompact({
427
432
  op: 'disconnect',
428
- endpoint: this.#options.config.name,
429
433
  code: codeNum,
430
434
  error: `${reasonStr || 'closed'}${codeHint}`,
431
435
  reconnect_ms: this.#options.config.reconnect_interval,
@@ -440,9 +444,9 @@ export class NapCatWsEndpoint implements EndpointInstance {
440
444
 
441
445
  ws.on('error', (err) => {
442
446
  const error = err instanceof Error ? err : new Error(String(err));
443
- logger.warn(formatCompact({
447
+ this.#logger.warn(formatCompact({
444
448
  op: 'ws_error',
445
- endpoint: this.#options.config.name,
449
+ endpoint: this.#options.config.id,
446
450
  ok: false,
447
451
  error: error.message,
448
452
  }));
@@ -23,7 +23,7 @@ export function decodeWsPayload(data: unknown): string {
23
23
  export function handleNapCatWsMessage(
24
24
  data: unknown,
25
25
  options: {
26
- readonly endpointName: string;
26
+ readonly endpointId: string;
27
27
  readonly pending: Map<string, NapCatPendingAction>;
28
28
  readonly admit: (ev: NapCatEvent) => void;
29
29
  },
@@ -50,7 +50,7 @@ export function handleNapCatWsMessage(
50
50
  } catch (error) {
51
51
  logger.warn(formatCompact({
52
52
  op: 'napcat_parse_failed',
53
- endpoint: options.endpointName,
53
+ endpoint: options.endpointId,
54
54
  error: error instanceof Error ? error.message : String(error),
55
55
  }));
56
56
  }
@@ -5,7 +5,7 @@ import { clearInterval } from 'node:timers';
5
5
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
8
- import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
9
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
10
10
  import { createNapCatEndpointManagement } from './endpoint-management.js';
11
11
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
@@ -40,8 +40,6 @@ import {
40
40
  } from './ws-types.js';
41
41
  import { verifyNapCatAccessToken } from './wss-auth.js';
42
42
 
43
- const logger = getLogger('napcat');
44
-
45
43
  export interface NapCatWssEndpointOptions {
46
44
  readonly id: CapabilityId;
47
45
  readonly gateway: MessageGateway;
@@ -50,6 +48,8 @@ export interface NapCatWssEndpointOptions {
50
48
  }
51
49
 
52
50
  export class NapCatWssEndpoint implements EndpointInstance {
51
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
52
+
53
53
  readonly #options: NapCatWssEndpointOptions;
54
54
  readonly #inboundDeduper = new InboundMessageDeduper();
55
55
  readonly management: EndpointManagement = createNapCatEndpointManagement(this);
@@ -63,6 +63,7 @@ export class NapCatWssEndpoint implements EndpointInstance {
63
63
  #unregisterAgent?: () => void;
64
64
 
65
65
  constructor(options: NapCatWssEndpointOptions) {
66
+ this.#logger = getAdapterLogger('napcat', options.config.id);
66
67
  this.#options = options;
67
68
  }
68
69
 
@@ -70,16 +71,16 @@ export class NapCatWssEndpoint implements EndpointInstance {
70
71
  if (this.#started) return;
71
72
  this.#started = true;
72
73
  this.#unregisterAgent = registerNapcatAgentEndpoint(
73
- this.#options.config.name,
74
+ this.#options.config.id,
74
75
  this as unknown as NapCatWsEndpoint,
75
76
  );
76
77
  const handle = this.#options.http.ws(this.#options.config.path);
77
78
  this.#wsRelease = handle.onConnection((connection) => {
78
79
  this.#acceptConnection(connection);
79
80
  });
80
- logger.info(formatCompact({
81
+ this.#logger.info(formatCompact({
81
82
  op: 'listen',
82
- endpoint: this.#options.config.name,
83
+ endpoint: this.#options.config.id,
83
84
  mode: 'wss',
84
85
  path: this.#options.config.path,
85
86
  }));
@@ -147,20 +148,24 @@ export class NapCatWssEndpoint implements EndpointInstance {
147
148
  conversation,
148
149
  message: { conversation, id: msgId },
149
150
  content: formatInboundContent(ev),
150
- sender: senderUserId(ev),
151
+ sender: {
152
+ id: senderUserId(ev),
153
+ name: nickname,
154
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
155
+ },
156
+ endpointId: this.#options.config.id,
157
+ ...(mentioned ? { mentioned: true } : {}),
151
158
  metadata: Object.freeze({
152
159
  message_type: ev.message_type,
153
160
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
154
161
  group_id: ev.group_id != null ? String(ev.group_id) : undefined,
155
- endpoint: this.#options.config.name,
156
162
  time: ev.time,
157
163
  self_id: ev.self_id != null ? String(ev.self_id) : undefined,
158
164
  role: ev.sender?.role,
159
165
  ...(nickname ? { nickname } : {}),
160
- ...(mentioned ? { mentioned: true } : {}),
161
166
  }),
162
167
  }).catch((err) => {
163
- logger.warn(formatCompact({
168
+ this.#logger.warn(formatCompact({
164
169
  op: 'napcat_gateway_receive_failed',
165
170
  target: `${conversation.kind}:${conversation.id}`,
166
171
  error: err instanceof Error ? err.message : String(err),
@@ -189,7 +194,7 @@ export class NapCatWssEndpoint implements EndpointInstance {
189
194
  );
190
195
  socket.on('message', (data) => {
191
196
  handleNapCatWsMessage(data, {
192
- endpointName: this.#options.config.name,
197
+ endpointId: this.#options.config.id,
193
198
  pending: this.#pending,
194
199
  admit: (event) => this.admit(event),
195
200
  });
@@ -203,8 +208,8 @@ export class NapCatWssEndpoint implements EndpointInstance {
203
208
  }
204
209
  }
205
210
  });
206
- logger.debug(formatCompact({
207
- endpoint: this.#options.config.name,
211
+ this.#logger.debug(formatCompact({
212
+ endpoint: this.#options.config.id,
208
213
  mode: 'wss',
209
214
  peer: connection.request.socket.remoteAddress,
210
215
  }));
File without changes
File without changes
File without changes
File without changes