@zhin.js/adapter-napcat 1.1.21 → 1.1.23

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,75 @@
1
1
  # @zhin.js/adapter-napcat
2
2
 
3
+ ## 1.1.23
4
+
5
+ ### Patch Changes
6
+
7
+ - bc6ccb1: Deep audit bug fixes and 7 architectural improvements:
8
+ - Sink Agent Turn ingress pipeline from `@zhin.js/cli` into `@zhin.js/agent/runtime` (`plugin-runtime/ingress/`) and decompose `ingress-route.ts` into modular pipeline stages.
9
+ - Bind Session State Plane (`JsxButtonRegistry`, `InteractionRuntime`, `RuntimeInteractiveRouter`) to `PluginIncarnationRef` so active buttons and interactions survive unrelated-plugin HMR while expiring when the owning plugin reloads, and support per-conversation fallback numbering (`numberingScope: 'conversation'`).
10
+ - Establish `conversation_events` as the canonical message SSOT and reference `conversation_event_id`, `conversation_key`, `platform_message_id`, and `message_key` in `agent_messages` (`AGENT_MESSAGE_QUERY_VERSION = 2`), threading `conversationSequence` across inbound and outbound delivery receipts.
11
+ - Push down `DatabaseContextRepository` and `DatabaseScheduleJobStorage` full-session/full-table memory scans to indexed SQL queries.
12
+ - Add kernel-level inbound `replyTo` and `mentioned` auto-promotion (`promoteInboundMessagePayload`) plus reusable `InboundDeduper` and `TtlLookupCache` primitives in `@zhin.js/adapter`.
13
+ - Add `MiddlewareContext.dispatch` (`InboundDispatchSummary`) and `onAfterDispatch` hooks for `after-dispatch` middlewares, and support incremental rendering of in-place `envelope.payload` array mutations in `OutboundDeliveryRuntime`.
14
+ - Unify `HistoryWindowTracker` and `AgentCompactionRuntime` token/message budgets (`resolveUnifiedHistoryAndCompactionBudget`).
15
+ - Fix schema validation, database transaction connection cleanup and SQLite WAL mode, schedule timer wheel head-of-line blocking, security policy bash read paths and subagent guard propagation, group merge window permission carrier handoff, micro-compact namespaced tool matching, and WebSocket force-close / inbound segment mapping across adapters.
16
+ - Updated dependencies [3a8504b]
17
+ - Updated dependencies [6163abc]
18
+ - Updated dependencies [fed2c27]
19
+ - Updated dependencies [bc6ccb1]
20
+ - Updated dependencies [72b5a12]
21
+ - Updated dependencies [aa41889]
22
+ - Updated dependencies [4427e72]
23
+ - Updated dependencies [22bd14a]
24
+ - @zhin.js/agent-feature@1.1.14
25
+ - @zhin.js/core@1.1.57
26
+ - @zhin.js/client@1.1.25
27
+ - @zhin.js/page@1.1.15
28
+ - @zhin.js/im-contract@1.1.12
29
+ - @zhin.js/adapter@1.1.29
30
+ - @zhin.js/host-http@1.1.15
31
+ - @zhin.js/feature-kit@1.1.13
32
+ - @zhin.js/tool@1.1.16
33
+ - @zhin.js/adapter-onebot-events@0.0.2
34
+ - @zhin.js/permission@1.1.13
35
+ - @zhin.js/skill@1.1.14
36
+
37
+ ## 1.1.22
38
+
39
+ ### Patch Changes
40
+
41
+ - b249e5e: ADR 0044 C4:事件映射迁出 core。新增 `@zhin.js/adapter-onebot-events`,承载 OneBot v11 / NapCat / icqq 的原始通知与请求 → 标准事件映射(`receiveOneBotLikeSideEvent`、`mapNoticeParts`、`mapRequestParts`、`resolveSideEventDedupeKey` 及 OneBot 映射表,icqq 差异用覆盖表表达);KOOK、Slack 的映射迁入各自适配器;`@zhin.js/core` 只保留标准事件名、类型与 `buildNotice` / `buildRequest` / `buildSystem` / `senderFromId`,不再含任何平台名。core 不再导出上述映射函数与 `ONEBOT_/KOOK_/SLACK_NOTICE_PARTS_MAP`、`SideEventPlatform`、`OneBotLike*` 类型,适配器改从共享包导入。
42
+ - 41ff1a5: ADR 0044 C5: every official adapter now declares a capability `profile` (its `segments` moved into `profile.default.messaging.segments`). icqq declares the complete profile (per conversation kind: scene, all 14 actions with real limits, events, feedback, delivery) and implements the seven new management ports (`pokeUser`, `unmuteGroupMember`, `muteAllGroupMembers`, `setGroupCard`, `setGroupTitle`, `setGroupEssence`, `inviteGroupMember`) with fresh bot-role reads. qq official declares its passive reply windows; undeclared capabilities stay unsupported.
43
+ - cbd3f14: ADR 0044 integration and C6 guard. `delivery.spool` now reproduces the pre-0044 media rule (icqq `false`, every other official adapter `true`). icqq narrows `actions.recall.others` unless the Bot is admin or owner, and declares recall for groups. Endpoint kind capabilities merge the Endpoint's `operations` (Discord reactions keep working; Discord declares reaction feedback). Adapters in the `@zhin.js/` scope are trusted: their custom prompt text is not labelled `external_content`. Core no longer keeps a telegram/discord interactive-policy table (adapters declare `segments.interactive`). New `pnpm check:no-platform-branches` blocks platform-name branches in runtime code.
44
+ - 4fe49b5: 支持配置按需生效:分离保存与有效版本,独立变更先应用,账号资料更新保留连接,模型按回合使用确定版本;Console 返回结构化应用结果。监听改为仅显式 --watch 启用,开发模板显式选择源码和配置监听。
45
+
46
+ 自变更消费配置应用回执,避免已处理配置被二次重载;持久化本次写入修订和应用结果,未知结果或修订冲突时停止自动续作并保留现场,恢复不会覆盖未经本次变更确认的配置。
47
+
48
+ - Updated dependencies [b249e5e]
49
+ - Updated dependencies [57661f6]
50
+ - Updated dependencies [cbd3f14]
51
+ - Updated dependencies [299863a]
52
+ - Updated dependencies [aaa93d7]
53
+ - Updated dependencies [bd524d1]
54
+ - Updated dependencies [126ae99]
55
+ - Updated dependencies [4fe49b5]
56
+ - Updated dependencies [cce07db]
57
+ - Updated dependencies [b652db3]
58
+ - Updated dependencies [0315ba0]
59
+ - Updated dependencies [00be764]
60
+ - @zhin.js/adapter-onebot-events@0.0.1
61
+ - @zhin.js/core@1.1.56
62
+ - @zhin.js/adapter@1.1.28
63
+ - @zhin.js/im-contract@1.1.11
64
+ - @zhin.js/tool@1.1.15
65
+ - @zhin.js/skill@1.1.13
66
+ - @zhin.js/host-http@1.1.14
67
+ - @zhin.js/feature-kit@1.1.12
68
+ - @zhin.js/client@1.1.24
69
+ - @zhin.js/page@1.1.14
70
+ - @zhin.js/agent-feature@1.1.13
71
+ - @zhin.js/permission@1.1.12
72
+
3
73
  ## 1.1.21
4
74
 
5
75
  ### Patch Changes
@@ -9,12 +9,25 @@ export { NapCatWsEndpoint, } from "../../lib/ws-endpoint.js";
9
9
  export { NapCatWssEndpoint, } from "../../lib/wss-endpoint.js";
10
10
  export default defineAdapter({
11
11
  capabilities: ['inbound', 'outbound'],
12
- operations: ['recall'],
13
12
  // OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
14
13
  // 无卡片交互面,交互段降级纯文本。
15
- segments: {
16
- outboundMedia: ['url', 'base64', 'path'],
17
- interactive: 'text',
14
+ profile: {
15
+ default: {
16
+ messaging: {
17
+ segments: {
18
+ outboundMedia: ['url', 'base64', 'path'],
19
+ interactive: 'text',
20
+ },
21
+ quote: true,
22
+ recall: {},
23
+ },
24
+ actions: {
25
+ recall: { scopes: ['private', 'group'] },
26
+ quoteReply: { scopes: ['private', 'group'] },
27
+ announce: { scopes: ['group'], requiresRole: 'admin' },
28
+ },
29
+ delivery: { send: true, proactive: true, spool: true },
30
+ },
18
31
  },
19
32
  createAdapter: context => new NapcatAdapter(context),
20
33
  });
@@ -21,12 +21,25 @@ export type { NapCatWsSocket, NapCatWsCreateOptions } from '../../src/ws-types.j
21
21
 
22
22
  export default defineAdapter<NapCatEndpointConfig>({
23
23
  capabilities: ['inbound', 'outbound'],
24
- operations: ['recall'],
25
24
  // OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
26
25
  // 无卡片交互面,交互段降级纯文本。
27
- segments: {
28
- outboundMedia: ['url', 'base64', 'path'],
29
- interactive: 'text',
26
+ profile: {
27
+ default: {
28
+ messaging: {
29
+ segments: {
30
+ outboundMedia: ['url', 'base64', 'path'],
31
+ interactive: 'text',
32
+ },
33
+ quote: true,
34
+ recall: {},
35
+ },
36
+ actions: {
37
+ recall: { scopes: ['private', 'group'] },
38
+ quoteReply: { scopes: ['private', 'group'] },
39
+ announce: { scopes: ['group'], requiresRole: 'admin' },
40
+ },
41
+ delivery: { send: true, proactive: true, spool: true },
42
+ },
30
43
  },
31
44
  createAdapter: context => new NapcatAdapter(context),
32
45
  });
@@ -1,10 +1,11 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, } from 'zhin.js/adapter';
3
4
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
4
5
  import { createNapCatEndpointManagement } from './endpoint-management.js';
5
6
  import { createNapcatSelfProfile } from './self-profile.js';
6
7
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
7
- import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
8
+ import { buildSendAction, callNapCatHttpAction, extractQuoteId, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
8
9
  import { receiveNapCatSideEvent } from './side-event-dispatch.js';
9
10
  import { createNapCatContentPort } from './onebot-get-msg.js';
10
11
  import { NapcatClient } from './client.js';
@@ -120,12 +121,15 @@ export class NapCatHttpEndpoint extends NapcatClient {
120
121
  const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
121
122
  const nickname = senderNickname(ev);
122
123
  const mentioned = isNapCatBotMentioned(ev);
124
+ const quoteId = extractQuoteId(ev);
125
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
123
126
  void this[endpointSpec].events.message({
124
127
  event: nativeEvent,
125
128
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
126
129
  conversation,
127
130
  message: { conversation, id: msgId },
128
131
  content: formatInboundContent(ev),
132
+ ...(segments?.length ? { segments } : {}),
129
133
  sender: {
130
134
  id: senderUserId(ev),
131
135
  name: nickname,
@@ -133,6 +137,7 @@ export class NapCatHttpEndpoint extends NapcatClient {
133
137
  },
134
138
  endpointId: this.#options.config.id,
135
139
  ...(mentioned ? { mentioned: true } : {}),
140
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
136
141
  metadata: Object.freeze({
137
142
  message_type: ev.message_type,
138
143
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -1,12 +1,9 @@
1
1
  /**
2
2
  * NapCat 入站消息治理:去重、自发过滤、消息归一化
3
3
  */
4
+ import { InboundMessageDeduper } from 'zhin.js/adapter';
4
5
  import type { NapCatMessageEvent, MessageSegment } from './protocol.js';
5
- export declare class InboundMessageDeduper {
6
- private readonly seen;
7
- shouldProcess(messageId: string): boolean;
8
- clear(): void;
9
- }
6
+ export { InboundMessageDeduper };
10
7
  /** 判断是否为 bot 自身发出的消息 */
11
8
  export declare function isSelfMessage(event: NapCatMessageEvent | {
12
9
  post_type?: string;
@@ -1,21 +1,8 @@
1
- const DEDUPE_TTL_MS = 120_000;
2
- export class InboundMessageDeduper {
3
- seen = new Map();
4
- shouldProcess(messageId) {
5
- const now = Date.now();
6
- for (const [id, t] of this.seen) {
7
- if (now - t > DEDUPE_TTL_MS)
8
- this.seen.delete(id);
9
- }
10
- if (this.seen.has(messageId))
11
- return false;
12
- this.seen.set(messageId, now);
13
- return true;
14
- }
15
- clear() {
16
- this.seen.clear();
17
- }
18
- }
1
+ /**
2
+ * NapCat 入站消息治理:去重、自发过滤、消息归一化
3
+ */
4
+ import { InboundMessageDeduper, promoteInboundMessagePayload } from 'zhin.js/adapter';
5
+ export { InboundMessageDeduper };
19
6
  /** 判断是否为 bot 自身发出的消息 */
20
7
  export function isSelfMessage(event) {
21
8
  if (event.post_type === 'message_sent')
@@ -53,9 +40,8 @@ export function isNapCatBotMentioned(ev) {
53
40
  if (!uin)
54
41
  return false;
55
42
  const segments = normalizeMessage(ev.message ?? []);
56
- for (const seg of segments) {
57
- if (seg.type === 'at' && atTargetMatchesUin(seg.data?.qq, uin))
58
- return true;
43
+ if (promoteInboundMessagePayload({ segments }, uin).mentioned === true) {
44
+ return true;
59
45
  }
60
46
  const text = segments
61
47
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
@@ -67,9 +67,10 @@ export function createNapCatContentPort(callApi) {
67
67
  },
68
68
  });
69
69
  }
70
+ import { toCanonicalSegments } from '@zhin.js/core';
70
71
  function canonicalSegments(content) {
71
- return Object.freeze(content.map((segment) => Object.freeze({
72
- type: segment.type,
72
+ return Object.freeze(toCanonicalSegments(content).map((segment) => Object.freeze({
73
+ ...segment,
73
74
  data: Object.freeze(segment.data ?? {}),
74
75
  })));
75
76
  }
package/lib/protocol.d.ts CHANGED
@@ -119,6 +119,11 @@ export declare function formatInboundContent(ev: NapCatEvent): string;
119
119
  export declare function senderUserId(ev: NapCatEvent): string;
120
120
  /** 显示名(群名片 card 优先于 nickname),没有则 undefined。 */
121
121
  export declare function senderNickname(ev: NapCatEvent): string | undefined;
122
+ /**
123
+ * 回复引用 id:优先取 message 数组中的 {type:'reply', data:{id}} 段;
124
+ * 兼容部分实现把 reply 放在事件顶层(标量或带 message_id 的对象)。
125
+ */
126
+ export declare function extractQuoteId(ev: NapCatEvent): string | undefined;
122
127
  /**
123
128
  * canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
124
129
  * 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
package/lib/protocol.js CHANGED
@@ -157,6 +157,27 @@ export function senderNickname(ev) {
157
157
  const name = ev.sender?.card || ev.sender?.nickname;
158
158
  return typeof name === 'string' && name ? name : undefined;
159
159
  }
160
+ /**
161
+ * 回复引用 id:优先取 message 数组中的 {type:'reply', data:{id}} 段;
162
+ * 兼容部分实现把 reply 放在事件顶层(标量或带 message_id 的对象)。
163
+ */
164
+ export function extractQuoteId(ev) {
165
+ if (Array.isArray(ev.message)) {
166
+ for (const seg of ev.message) {
167
+ if (seg?.type === 'reply' && seg.data?.id != null)
168
+ return String(seg.data.id);
169
+ }
170
+ }
171
+ const reply = ev.reply;
172
+ if (typeof reply === 'number' || typeof reply === 'string')
173
+ return String(reply);
174
+ if (reply && typeof reply === 'object' && 'message_id' in reply) {
175
+ const id = reply.message_id;
176
+ if (id != null)
177
+ return String(id);
178
+ }
179
+ return undefined;
180
+ }
160
181
  /**
161
182
  * canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
162
183
  * 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
@@ -1,4 +1,4 @@
1
- import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/adapter-onebot-events';
2
2
  import { formatCompact } from '@zhin.js/logger';
3
3
  export function receiveNapCatSideEvent(emit, endpointKey, caller, raw, logger) {
4
4
  if (!emit)
@@ -1,4 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  /**
3
4
  * NapCat WS client endpoint — outbound connect to NapCat.
4
5
  */
@@ -8,7 +9,7 @@ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
9
  import { createNapCatEndpointManagement } from './endpoint-management.js';
9
10
  import { createNapcatSelfProfile } from './self-profile.js';
10
11
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
11
- import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
12
+ import { buildSendAction, buildWsConnectOptions, extractQuoteId, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
12
13
  import { receiveNapCatSideEvent } from './side-event-dispatch.js';
13
14
  import { createNapCatContentPort } from './onebot-get-msg.js';
14
15
  import { NapcatClient } from './client.js';
@@ -134,12 +135,15 @@ export class NapCatWsEndpoint extends NapcatClient {
134
135
  const content = formatInboundContent(ev);
135
136
  const nickname = senderNickname(ev);
136
137
  const mentioned = isNapCatBotMentioned(ev);
138
+ const quoteId = extractQuoteId(ev);
139
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
137
140
  void this[endpointSpec].events.message({
138
141
  event: nativeEvent,
139
142
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
140
143
  conversation,
141
144
  message: { conversation, id: msgId },
142
145
  content,
146
+ ...(segments?.length ? { segments } : {}),
143
147
  sender: {
144
148
  id: senderUserId(ev),
145
149
  name: nickname,
@@ -147,6 +151,7 @@ export class NapCatWsEndpoint extends NapcatClient {
147
151
  },
148
152
  endpointId: this.#options.config.id,
149
153
  ...(mentioned ? { mentioned: true } : {}),
154
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
150
155
  metadata: Object.freeze({
151
156
  message_type: ev.message_type,
152
157
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -178,6 +183,7 @@ export class NapCatWsEndpoint extends NapcatClient {
178
183
  }
179
184
  try {
180
185
  ws.close();
186
+ ws.terminate?.();
181
187
  }
182
188
  catch {
183
189
  /* ignore */
package/lib/ws-types.d.ts CHANGED
@@ -3,6 +3,7 @@ export interface NapCatWsSocket {
3
3
  readonly readyState: number;
4
4
  send(data: string): void;
5
5
  close(code?: number, reason?: string): void;
6
+ terminate?(): void;
6
7
  ping?(): void;
7
8
  on(event: 'open' | 'message' | 'close' | 'error' | 'pong', listener: (...args: unknown[]) => void): void;
8
9
  }
@@ -1,4 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  /**
3
4
  * NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
4
5
  */
@@ -7,7 +8,7 @@ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
8
  import { createNapCatEndpointManagement } from './endpoint-management.js';
8
9
  import { createNapcatSelfProfile } from './self-profile.js';
9
10
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
10
- import { buildSendAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
11
+ import { buildSendAction, extractQuoteId, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
11
12
  import { receiveNapCatSideEvent } from './side-event-dispatch.js';
12
13
  import { createNapCatContentPort } from './onebot-get-msg.js';
13
14
  import { NapcatClient } from './client.js';
@@ -147,12 +148,15 @@ export class NapCatWssEndpoint extends NapcatClient {
147
148
  const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
148
149
  const nickname = senderNickname(ev);
149
150
  const mentioned = isNapCatBotMentioned(ev);
151
+ const quoteId = extractQuoteId(ev);
152
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
150
153
  void this[endpointSpec].events.message({
151
154
  event: nativeEvent,
152
155
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
153
156
  conversation,
154
157
  message: { conversation, id: msgId },
155
158
  content: formatInboundContent(ev),
159
+ ...(segments?.length ? { segments } : {}),
156
160
  sender: {
157
161
  id: senderUserId(ev),
158
162
  name: nickname,
@@ -160,6 +164,7 @@ export class NapCatWssEndpoint extends NapcatClient {
160
164
  },
161
165
  endpointId: this.#options.config.id,
162
166
  ...(mentioned ? { mentioned: true } : {}),
167
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
163
168
  metadata: Object.freeze({
164
169
  message_type: ev.message_type,
165
170
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -197,6 +202,7 @@ export class NapCatWssEndpoint extends NapcatClient {
197
202
  }
198
203
  try {
199
204
  socket.close();
205
+ socket.terminate?.();
200
206
  }
201
207
  catch {
202
208
  /* ignore */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-napcat",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
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",
@@ -45,19 +45,20 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "ws": "^8.21.1",
48
- "@zhin.js/adapter": "1.1.27",
49
- "@zhin.js/agent-feature": "1.1.12",
50
- "@zhin.js/client": "1.1.23",
48
+ "@zhin.js/adapter-onebot-events": "0.0.2",
49
+ "@zhin.js/adapter": "1.1.29",
50
+ "@zhin.js/agent-feature": "1.1.14",
51
+ "@zhin.js/client": "1.1.25",
51
52
  "@zhin.js/console-contract": "1.1.3",
52
- "@zhin.js/core": "1.1.55",
53
- "@zhin.js/feature-kit": "1.1.11",
54
- "@zhin.js/host-http": "1.1.13",
55
- "@zhin.js/im-contract": "1.1.10",
53
+ "@zhin.js/core": "1.1.57",
54
+ "@zhin.js/feature-kit": "1.1.13",
55
+ "@zhin.js/host-http": "1.1.15",
56
+ "@zhin.js/im-contract": "1.1.12",
56
57
  "@zhin.js/logger": "1.1.2",
57
- "@zhin.js/page": "1.1.13",
58
- "@zhin.js/permission": "1.1.11",
59
- "@zhin.js/skill": "1.1.12",
60
- "@zhin.js/tool": "1.1.14"
58
+ "@zhin.js/page": "1.1.15",
59
+ "@zhin.js/permission": "1.1.13",
60
+ "@zhin.js/skill": "1.1.14",
61
+ "@zhin.js/tool": "1.1.16"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@types/node": "^26.1.2",
@@ -65,15 +66,15 @@
65
66
  "typescript": "^6.0.3",
66
67
  "vitest": "^4.1.10",
67
68
  "zod": "^4.4.3",
68
- "@zhin.js/host-http": "1.1.13",
69
- "zhin.js": "1.1.20"
69
+ "@zhin.js/host-http": "1.1.15",
70
+ "zhin.js": "1.1.22"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "zod": "^4.0.0",
73
- "@zhin.js/adapter": "^1.1.27",
74
- "@zhin.js/command": "^1.1.13",
75
- "@zhin.js/core": "^1.1.55",
76
- "zhin.js": "^1.1.20"
74
+ "@zhin.js/adapter": "^1.1.29",
75
+ "@zhin.js/command": "^1.1.15",
76
+ "@zhin.js/core": "^1.1.57",
77
+ "zhin.js": "^1.1.22"
77
78
  },
78
79
  "peerDependenciesMeta": {
79
80
  "@zhin.js/command": {
package/plugin.js CHANGED
@@ -4,6 +4,7 @@ import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/pe
4
4
  import { createNapcatConsoleRpc } from "./lib/console.js";
5
5
  export default definePlugin({
6
6
  name: 'napcat',
7
+ configUpdate: { featurePaths: [['nickname'], ['profile'], ['endpoints']] },
7
8
  metadata: {
8
9
  displayName: 'NapCat Adapter',
9
10
  },
@@ -1,4 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  /**
3
4
  * NapCat HTTP endpoint — POST inbound events + HTTP API outbound.
4
5
  */
@@ -25,6 +26,7 @@ import {
25
26
  import {
26
27
  buildSendAction,
27
28
  callNapCatHttpAction,
29
+ extractQuoteId,
28
30
  formatInboundContent,
29
31
  formatOutboundSegments,
30
32
  isMessageEvent,
@@ -171,12 +173,15 @@ export class NapCatHttpEndpoint extends NapcatClient implements Endpoint {
171
173
  const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
172
174
  const nickname = senderNickname(ev);
173
175
  const mentioned = isNapCatBotMentioned(ev);
176
+ const quoteId = extractQuoteId(ev);
177
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
174
178
  void this[endpointSpec].events.message({
175
179
  event: nativeEvent,
176
180
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
177
181
  conversation,
178
182
  message: { conversation, id: msgId },
179
183
  content: formatInboundContent(ev),
184
+ ...(segments?.length ? { segments } : {}),
180
185
  sender: {
181
186
  id: senderUserId(ev),
182
187
  name: nickname,
@@ -184,6 +189,7 @@ export class NapCatHttpEndpoint extends NapcatClient implements Endpoint {
184
189
  },
185
190
  endpointId: this.#options.config.id,
186
191
  ...(mentioned ? { mentioned: true } : {}),
192
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
187
193
  metadata: Object.freeze({
188
194
  message_type: ev.message_type,
189
195
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -1,27 +1,10 @@
1
1
  /**
2
2
  * NapCat 入站消息治理:去重、自发过滤、消息归一化
3
3
  */
4
+ import { InboundMessageDeduper, promoteInboundMessagePayload } from 'zhin.js/adapter';
4
5
  import type { NapCatMessageEvent, MessageSegment } from './protocol.js';
5
6
 
6
- const DEDUPE_TTL_MS = 120_000;
7
-
8
- export class InboundMessageDeduper {
9
- private readonly seen = new Map<string, number>();
10
-
11
- shouldProcess(messageId: string): boolean {
12
- const now = Date.now();
13
- for (const [id, t] of this.seen) {
14
- if (now - t > DEDUPE_TTL_MS) this.seen.delete(id);
15
- }
16
- if (this.seen.has(messageId)) return false;
17
- this.seen.set(messageId, now);
18
- return true;
19
- }
20
-
21
- clear(): void {
22
- this.seen.clear();
23
- }
24
- }
7
+ export { InboundMessageDeduper };
25
8
 
26
9
  /** 判断是否为 bot 自身发出的消息 */
27
10
  export function isSelfMessage(event: NapCatMessageEvent | {
@@ -67,8 +50,8 @@ export function isNapCatBotMentioned(ev: {
67
50
  const uin = String(ev.self_id).trim();
68
51
  if (!uin) return false;
69
52
  const segments = normalizeMessage(ev.message ?? []);
70
- for (const seg of segments) {
71
- if (seg.type === 'at' && atTargetMatchesUin(seg.data?.qq, uin)) return true;
53
+ if (promoteInboundMessagePayload<{ segments: MessageSegment[]; mentioned?: boolean }>({ segments }, uin).mentioned === true) {
54
+ return true;
72
55
  }
73
56
  const text = segments
74
57
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
@@ -82,9 +82,13 @@ export function createNapCatContentPort(
82
82
  });
83
83
  }
84
84
 
85
+ import { toCanonicalSegments } from '@zhin.js/core';
86
+ import type { PendingRpcRequest, EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
87
+ import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
88
+
85
89
  function canonicalSegments(content: Array<{ type: string; data?: Record<string, unknown> }>) {
86
- return Object.freeze(content.map((segment) => Object.freeze({
87
- type: segment.type,
90
+ return Object.freeze(toCanonicalSegments(content).map((segment) => Object.freeze({
91
+ ...segment,
88
92
  data: Object.freeze(segment.data ?? {}),
89
93
  })));
90
94
  }
@@ -93,5 +97,3 @@ function toMillis(value: unknown): number {
93
97
  const time = Number(value);
94
98
  return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
95
99
  }
96
- import type { PendingRpcRequest, EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
97
- import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
package/src/protocol.ts CHANGED
@@ -283,6 +283,25 @@ export function senderNickname(ev: NapCatEvent): string | undefined {
283
283
  return typeof name === 'string' && name ? name : undefined;
284
284
  }
285
285
 
286
+ /**
287
+ * 回复引用 id:优先取 message 数组中的 {type:'reply', data:{id}} 段;
288
+ * 兼容部分实现把 reply 放在事件顶层(标量或带 message_id 的对象)。
289
+ */
290
+ export function extractQuoteId(ev: NapCatEvent): string | undefined {
291
+ if (Array.isArray(ev.message)) {
292
+ for (const seg of ev.message) {
293
+ if (seg?.type === 'reply' && seg.data?.id != null) return String(seg.data.id);
294
+ }
295
+ }
296
+ const reply = ev.reply;
297
+ if (typeof reply === 'number' || typeof reply === 'string') return String(reply);
298
+ if (reply && typeof reply === 'object' && 'message_id' in reply) {
299
+ const id = (reply as { message_id?: unknown }).message_id;
300
+ if (id != null) return String(id);
301
+ }
302
+ return undefined;
303
+ }
304
+
286
305
  /**
287
306
  * canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
288
307
  * 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
@@ -1,4 +1,4 @@
1
- import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/adapter-onebot-events';
2
2
  import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
  import type { NapCatEvent } from './protocol.js';
@@ -1,4 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  /**
3
4
  * NapCat WS client endpoint — outbound connect to NapCat.
4
5
  */
@@ -27,6 +28,7 @@ import {
27
28
  import {
28
29
  buildSendAction,
29
30
  buildWsConnectOptions,
31
+ extractQuoteId,
30
32
  formatInboundContent,
31
33
  formatOutboundSegments,
32
34
  isMessageEvent,
@@ -190,12 +192,15 @@ export class NapCatWsEndpoint extends NapcatClient implements Endpoint {
190
192
  const content = formatInboundContent(ev);
191
193
  const nickname = senderNickname(ev);
192
194
  const mentioned = isNapCatBotMentioned(ev);
195
+ const quoteId = extractQuoteId(ev);
196
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
193
197
  void this[endpointSpec].events.message({
194
198
  event: nativeEvent,
195
199
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
196
200
  conversation,
197
201
  message: { conversation, id: msgId },
198
202
  content,
203
+ ...(segments?.length ? { segments } : {}),
199
204
  sender: {
200
205
  id: senderUserId(ev),
201
206
  name: nickname,
@@ -203,6 +208,7 @@ export class NapCatWsEndpoint extends NapcatClient implements Endpoint {
203
208
  },
204
209
  endpointId: this.#options.config.id,
205
210
  ...(mentioned ? { mentioned: true } : {}),
211
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
206
212
  metadata: Object.freeze({
207
213
  message_type: ev.message_type,
208
214
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -237,6 +243,7 @@ export class NapCatWsEndpoint extends NapcatClient implements Endpoint {
237
243
  }
238
244
  try {
239
245
  ws.close();
246
+ ws.terminate?.();
240
247
  } catch {
241
248
  /* ignore */
242
249
  }
package/src/ws-types.ts CHANGED
@@ -3,6 +3,7 @@ export interface NapCatWsSocket {
3
3
  readonly readyState: number;
4
4
  send(data: string): void;
5
5
  close(code?: number, reason?: string): void;
6
+ terminate?(): void;
6
7
  ping?(): void;
7
8
  on(event: 'open' | 'message' | 'close' | 'error' | 'pong', listener: (...args: unknown[]) => void): void;
8
9
  }
@@ -1,4 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
2
3
  /**
3
4
  * NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
4
5
  */
@@ -25,6 +26,7 @@ import {
25
26
  } from './napcat-inbound.js';
26
27
  import {
27
28
  buildSendAction,
29
+ extractQuoteId,
28
30
  formatInboundContent,
29
31
  formatOutboundSegments,
30
32
  isMessageEvent,
@@ -195,12 +197,15 @@ export class NapCatWssEndpoint extends NapcatClient implements Endpoint {
195
197
  const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
196
198
  const nickname = senderNickname(ev);
197
199
  const mentioned = isNapCatBotMentioned(ev);
200
+ const quoteId = extractQuoteId(ev);
201
+ const segments = Array.isArray(ev.message) ? toCanonicalSegments(ev.message) : undefined;
198
202
  void this[endpointSpec].events.message({
199
203
  event: nativeEvent,
200
204
  ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
201
205
  conversation,
202
206
  message: { conversation, id: msgId },
203
207
  content: formatInboundContent(ev),
208
+ ...(segments?.length ? { segments } : {}),
204
209
  sender: {
205
210
  id: senderUserId(ev),
206
211
  name: nickname,
@@ -208,6 +213,7 @@ export class NapCatWssEndpoint extends NapcatClient implements Endpoint {
208
213
  },
209
214
  endpointId: this.#options.config.id,
210
215
  ...(mentioned ? { mentioned: true } : {}),
216
+ ...(quoteId ? { replyTo: { id: quoteId } } : {}),
211
217
  metadata: Object.freeze({
212
218
  message_type: ev.message_type,
213
219
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -246,6 +252,7 @@ export class NapCatWssEndpoint extends NapcatClient implements Endpoint {
246
252
  }
247
253
  try {
248
254
  socket.close();
255
+ socket.terminate?.();
249
256
  } catch {
250
257
  /* ignore */
251
258
  }