@zhin.js/adapter-napcat 1.1.22 → 1.1.24

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,60 @@
1
1
  # @zhin.js/adapter-napcat
2
2
 
3
+ ## 1.1.24
4
+
5
+ ### Patch Changes
6
+
7
+ - cbf1fb1: Unify Console page blueprints (`definePageBlueprint`, `defineAdapterPage`, `defineCommentReviewPage`, `projectBlueprintToImCard`, `projectBlueprintToCliPanel`) into `@zhin.js/console-contract` and `@zhin.js/page`, add zero-esbuild pure Blueprint synthesis + static client bundle mounting to `@zhin.js/pagemanager`, remove `@zhin.js/client` (React 19) and `.tsx` from all 30 server-side plugins and adapters (including `bilibili` and `juejin`), and remove legacy Era-1 Koa `PageManager` code.
8
+ - Updated dependencies [cbf1fb1]
9
+ - Updated dependencies [008ccad]
10
+ - Updated dependencies [a1f1028]
11
+ - @zhin.js/console-contract@1.1.4
12
+ - @zhin.js/page@1.1.16
13
+ - @zhin.js/core@1.1.58
14
+ - @zhin.js/im-contract@1.1.13
15
+ - @zhin.js/host-http@1.1.16
16
+ - @zhin.js/adapter@1.1.30
17
+ - @zhin.js/adapter-onebot-events@0.0.3
18
+ - @zhin.js/tool@1.1.17
19
+ - @zhin.js/agent-feature@1.1.15
20
+ - @zhin.js/feature-kit@1.1.14
21
+ - @zhin.js/permission@1.1.14
22
+ - @zhin.js/skill@1.1.15
23
+
24
+ ## 1.1.23
25
+
26
+ ### Patch Changes
27
+
28
+ - bc6ccb1: Deep audit bug fixes and 7 architectural improvements:
29
+ - 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.
30
+ - 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'`).
31
+ - 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.
32
+ - Push down `DatabaseContextRepository` and `DatabaseScheduleJobStorage` full-session/full-table memory scans to indexed SQL queries.
33
+ - Add kernel-level inbound `replyTo` and `mentioned` auto-promotion (`promoteInboundMessagePayload`) plus reusable `InboundDeduper` and `TtlLookupCache` primitives in `@zhin.js/adapter`.
34
+ - Add `MiddlewareContext.dispatch` (`InboundDispatchSummary`) and `onAfterDispatch` hooks for `after-dispatch` middlewares, and support incremental rendering of in-place `envelope.payload` array mutations in `OutboundDeliveryRuntime`.
35
+ - Unify `HistoryWindowTracker` and `AgentCompactionRuntime` token/message budgets (`resolveUnifiedHistoryAndCompactionBudget`).
36
+ - 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.
37
+ - Updated dependencies [3a8504b]
38
+ - Updated dependencies [6163abc]
39
+ - Updated dependencies [fed2c27]
40
+ - Updated dependencies [bc6ccb1]
41
+ - Updated dependencies [72b5a12]
42
+ - Updated dependencies [aa41889]
43
+ - Updated dependencies [4427e72]
44
+ - Updated dependencies [22bd14a]
45
+ - @zhin.js/agent-feature@1.1.14
46
+ - @zhin.js/core@1.1.57
47
+ - @zhin.js/client@1.1.25
48
+ - @zhin.js/page@1.1.15
49
+ - @zhin.js/im-contract@1.1.12
50
+ - @zhin.js/adapter@1.1.29
51
+ - @zhin.js/host-http@1.1.15
52
+ - @zhin.js/feature-kit@1.1.13
53
+ - @zhin.js/tool@1.1.16
54
+ - @zhin.js/adapter-onebot-events@0.0.2
55
+ - @zhin.js/permission@1.1.13
56
+ - @zhin.js/skill@1.1.14
57
+
3
58
  ## 1.1.22
4
59
 
5
60
  ### Patch Changes
@@ -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,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.22",
3
+ "version": "1.1.24",
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,20 +45,19 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "ws": "^8.21.1",
48
- "@zhin.js/adapter-onebot-events": "0.0.1",
49
- "@zhin.js/adapter": "1.1.28",
50
- "@zhin.js/agent-feature": "1.1.13",
51
- "@zhin.js/client": "1.1.24",
52
- "@zhin.js/console-contract": "1.1.3",
53
- "@zhin.js/core": "1.1.56",
54
- "@zhin.js/feature-kit": "1.1.12",
55
- "@zhin.js/host-http": "1.1.14",
56
- "@zhin.js/im-contract": "1.1.11",
48
+ "@zhin.js/adapter-onebot-events": "0.0.3",
49
+ "@zhin.js/adapter": "1.1.30",
50
+ "@zhin.js/agent-feature": "1.1.15",
51
+ "@zhin.js/console-contract": "1.1.4",
52
+ "@zhin.js/core": "1.1.58",
53
+ "@zhin.js/feature-kit": "1.1.14",
54
+ "@zhin.js/host-http": "1.1.16",
55
+ "@zhin.js/im-contract": "1.1.13",
57
56
  "@zhin.js/logger": "1.1.2",
58
- "@zhin.js/page": "1.1.14",
59
- "@zhin.js/permission": "1.1.12",
60
- "@zhin.js/skill": "1.1.13",
61
- "@zhin.js/tool": "1.1.15"
57
+ "@zhin.js/page": "1.1.16",
58
+ "@zhin.js/permission": "1.1.14",
59
+ "@zhin.js/skill": "1.1.15",
60
+ "@zhin.js/tool": "1.1.17"
62
61
  },
63
62
  "devDependencies": {
64
63
  "@types/node": "^26.1.2",
@@ -66,15 +65,15 @@
66
65
  "typescript": "^6.0.3",
67
66
  "vitest": "^4.1.10",
68
67
  "zod": "^4.4.3",
69
- "@zhin.js/host-http": "1.1.14",
70
- "zhin.js": "1.1.21"
68
+ "@zhin.js/host-http": "1.1.16",
69
+ "zhin.js": "1.1.23"
71
70
  },
72
71
  "peerDependencies": {
73
72
  "zod": "^4.0.0",
74
- "@zhin.js/adapter": "^1.1.28",
75
- "@zhin.js/command": "^1.1.14",
76
- "@zhin.js/core": "^1.1.56",
77
- "zhin.js": "^1.1.21"
73
+ "@zhin.js/adapter": "^1.1.30",
74
+ "@zhin.js/command": "^1.1.16",
75
+ "@zhin.js/core": "^1.1.58",
76
+ "zhin.js": "^1.1.23"
78
77
  },
79
78
  "peerDependenciesMeta": {
80
79
  "@zhin.js/command": {
@@ -1,6 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { definePage } from '@zhin.js/console-contract';
3
- import { createAdapterConsolePage } from '@zhin.js/client';
2
+ import { definePage, createAdapterConsolePage } from '@zhin.js/console-contract';
4
3
  export const meta = definePage({
5
4
  title: 'NapCat',
6
5
  titleI18n: { en: 'NapCat' },
@@ -1,5 +1,4 @@
1
- import { definePage } from '@zhin.js/console-contract';
2
- import { createAdapterConsolePage } from '@zhin.js/client';
1
+ import { definePage, createAdapterConsolePage } from '@zhin.js/console-contract';
3
2
 
4
3
  export const meta = definePage({
5
4
  title: 'NapCat',
package/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
2
+ import { definePlugin } from 'zhin.js';
3
3
  import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
4
4
  import { createNapcatConsoleRpc } from "./lib/console.js";
5
5
  export default definePlugin({
@@ -9,10 +9,12 @@ export default definePlugin({
9
9
  displayName: 'NapCat Adapter',
10
10
  },
11
11
  setup(context) {
12
- context.resources.provide(pluginConsoleRpcToken, createNapcatConsoleRpc(context.resources, () => context.config.get()));
13
- if (context.resources.has(permissionHostToken)) {
14
- const host = context.resources.use(permissionHostToken);
15
- return host.registerPlatform('napcat', createSceneRolePlatformChecker());
16
- }
12
+ const dispose = context.resources.has(permissionHostToken)
13
+ ? context.resources.use(permissionHostToken).registerPlatform('napcat', createSceneRolePlatformChecker())
14
+ : undefined;
15
+ return {
16
+ consoleRpc: createNapcatConsoleRpc(context.resources, () => context.config.get()),
17
+ ...(dispose ? { dispose } : {}),
18
+ };
17
19
  },
18
20
  });
@@ -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,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
  }