@zhin.js/adapter-icqq 6.0.1 → 6.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +32 -437
  3. package/adapters/icqq.ts +30 -0
  4. package/agent/tools/announce.ts +21 -0
  5. package/agent/tools/essence.ts +22 -0
  6. package/agent/tools/friend_list.ts +19 -0
  7. package/agent/tools/group_files.ts +27 -0
  8. package/agent/tools/group_invite.ts +20 -0
  9. package/agent/tools/group_list.ts +20 -0
  10. package/agent/tools/group_sign.ts +19 -0
  11. package/agent/tools/list_muted.ts +22 -0
  12. package/agent/tools/poke.ts +20 -0
  13. package/agent/tools/send_user_like.ts +22 -0
  14. package/agent/tools/set_anonymous.ts +22 -0
  15. package/agent/tools/set_title.ts +25 -0
  16. package/lib/agent-prompt.d.ts +0 -1
  17. package/lib/agent-prompt.js +0 -1
  18. package/lib/cq-message.d.ts +0 -1
  19. package/lib/cq-message.js +0 -1
  20. package/lib/endpoint.d.ts +45 -97
  21. package/lib/endpoint.js +357 -628
  22. package/lib/forward-msg.d.ts +1 -2
  23. package/lib/forward-msg.js +4 -2
  24. package/lib/icqq-agent-deps.d.ts +25 -0
  25. package/lib/icqq-agent-deps.js +32 -0
  26. package/lib/icqq-guild.d.ts +1 -2
  27. package/lib/icqq-guild.js +4 -2
  28. package/lib/icqq-inbound.d.ts +10 -1
  29. package/lib/icqq-inbound.js +27 -3
  30. package/lib/index.d.ts +6 -18
  31. package/lib/index.js +6 -50
  32. package/lib/ipc-client.d.ts +0 -1
  33. package/lib/ipc-client.js +0 -1
  34. package/lib/outbound-media.d.ts +0 -1
  35. package/lib/outbound-media.js +0 -1
  36. package/lib/protocol.d.ts +73 -2
  37. package/lib/protocol.js +124 -2
  38. package/lib/types.d.ts +0 -1
  39. package/lib/types.js +0 -1
  40. package/package.json +51 -43
  41. package/plugin.ts +32 -0
  42. package/schema.json +35 -0
  43. package/src/agent-prompt.ts +1 -2
  44. package/src/endpoint.ts +399 -800
  45. package/src/forward-msg.ts +2 -2
  46. package/src/icqq-agent-deps.ts +57 -0
  47. package/src/icqq-guild.ts +1 -2
  48. package/src/icqq-inbound.ts +31 -2
  49. package/src/index.ts +35 -69
  50. package/src/protocol.ts +191 -1
  51. package/client/ICQQManagement.tsx +0 -147
  52. package/client/LoginAssistPanel.tsx +0 -300
  53. package/client/index.tsx +0 -11
  54. package/client/tsconfig.json +0 -7
  55. package/client/utils/api.ts +0 -30
  56. package/dist/index.js +0 -34
  57. package/lib/adapter.d.ts +0 -46
  58. package/lib/adapter.d.ts.map +0 -1
  59. package/lib/adapter.js +0 -128
  60. package/lib/adapter.js.map +0 -1
  61. package/lib/agent-prompt.d.ts.map +0 -1
  62. package/lib/agent-prompt.js.map +0 -1
  63. package/lib/cq-message.d.ts.map +0 -1
  64. package/lib/cq-message.js.map +0 -1
  65. package/lib/endpoint.d.ts.map +0 -1
  66. package/lib/endpoint.js.map +0 -1
  67. package/lib/forward-msg.d.ts.map +0 -1
  68. package/lib/forward-msg.js.map +0 -1
  69. package/lib/get-msg.d.ts +0 -3
  70. package/lib/get-msg.d.ts.map +0 -1
  71. package/lib/get-msg.js +0 -46
  72. package/lib/get-msg.js.map +0 -1
  73. package/lib/icqq-guild.d.ts.map +0 -1
  74. package/lib/icqq-guild.js.map +0 -1
  75. package/lib/icqq-inbound.d.ts.map +0 -1
  76. package/lib/icqq-inbound.js.map +0 -1
  77. package/lib/icqq-login-assist-bridge.d.ts +0 -5
  78. package/lib/icqq-login-assist-bridge.d.ts.map +0 -1
  79. package/lib/icqq-login-assist-bridge.js +0 -148
  80. package/lib/icqq-login-assist-bridge.js.map +0 -1
  81. package/lib/icqq-side-events.d.ts +0 -34
  82. package/lib/icqq-side-events.d.ts.map +0 -1
  83. package/lib/icqq-side-events.js +0 -194
  84. package/lib/icqq-side-events.js.map +0 -1
  85. package/lib/index.d.ts.map +0 -1
  86. package/lib/index.js.map +0 -1
  87. package/lib/ipc-client.d.ts.map +0 -1
  88. package/lib/ipc-client.js.map +0 -1
  89. package/lib/login-assist-routes.d.ts +0 -7
  90. package/lib/login-assist-routes.d.ts.map +0 -1
  91. package/lib/login-assist-routes.js +0 -29
  92. package/lib/login-assist-routes.js.map +0 -1
  93. package/lib/login-ipc-contract.d.ts +0 -44
  94. package/lib/login-ipc-contract.d.ts.map +0 -1
  95. package/lib/login-ipc-contract.js +0 -35
  96. package/lib/login-ipc-contract.js.map +0 -1
  97. package/lib/outbound-media.d.ts.map +0 -1
  98. package/lib/outbound-media.js.map +0 -1
  99. package/lib/protocol.d.ts.map +0 -1
  100. package/lib/protocol.js.map +0 -1
  101. package/lib/routes.d.ts +0 -8
  102. package/lib/routes.d.ts.map +0 -1
  103. package/lib/routes.js +0 -67
  104. package/lib/routes.js.map +0 -1
  105. package/lib/segment-mapper.d.ts +0 -2
  106. package/lib/segment-mapper.d.ts.map +0 -1
  107. package/lib/segment-mapper.js +0 -2
  108. package/lib/segment-mapper.js.map +0 -1
  109. package/lib/tools/index.d.ts +0 -10
  110. package/lib/tools/index.d.ts.map +0 -1
  111. package/lib/tools/index.js +0 -341
  112. package/lib/tools/index.js.map +0 -1
  113. package/lib/types.d.ts.map +0 -1
  114. package/lib/types.js.map +0 -1
  115. package/plugin.yml +0 -3
  116. package/src/adapter.ts +0 -151
  117. package/src/get-msg.ts +0 -56
  118. package/src/icqq-login-assist-bridge.ts +0 -196
  119. package/src/icqq-side-events.ts +0 -228
  120. package/src/login-assist-routes.ts +0 -39
  121. package/src/login-ipc-contract.ts +0 -52
  122. package/src/routes.ts +0 -83
  123. package/src/segment-mapper.ts +0 -1
  124. package/src/tools/index.ts +0 -412
  125. /package/{skills/icqq → agent}/PERMITS.md +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
3
3
  */
4
- import type { MessageSegment, QuotedMessagePayload } from "zhin.js";
4
+ import { type MessageSegment, type QuotedMessagePayload } from 'zhin.js';
5
5
  import type { IpcClient } from "./ipc-client.js";
6
6
  import { type IcqqMessageElement } from "./icqq-inbound.js";
7
7
  /** 从 QQ 合并转发 json 元素提取 resid */
@@ -24,4 +24,3 @@ export declare function formatForwardMsgResponse(data: unknown): string;
24
24
  export declare function fetchForwardMsgText(ipc: IpcClient, id: string): Promise<string>;
25
25
  /** 若 payload 含合并转发,拉取并追加到 content */
26
26
  export declare function enrichQuotedPayloadWithForward(ipc: IpcClient | null | undefined, payload: QuotedMessagePayload, ipcRaw?: unknown): Promise<QuotedMessagePayload>;
27
- //# sourceMappingURL=forward-msg.d.ts.map
@@ -1,4 +1,7 @@
1
- import { segment } from "zhin.js";
1
+ /**
2
+ * 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
3
+ */
4
+ import { segment } from 'zhin.js';
2
5
  import { Actions } from "./protocol.js";
3
6
  import { icqqElementsToSegments, } from "./icqq-inbound.js";
4
7
  import { parseCqMessage } from "./cq-message.js";
@@ -384,4 +387,3 @@ export async function enrichQuotedPayloadWithForward(ipc, payload, ipcRaw) {
384
387
  }
385
388
  return payload;
386
389
  }
387
- //# sourceMappingURL=forward-msg.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Agent tool deps for icqq.
3
+ * Endpoints register themselves on start; tools look up by config name / QQ uin.
4
+ */
5
+ import type { IpcFriendInfo, IpcGroupInfo } from './types.js';
6
+ import type { IpcResponse } from './protocol.js';
7
+ export interface IcqqAgentEndpoint {
8
+ readonly name: string;
9
+ readonly ipc: {
10
+ request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
11
+ };
12
+ readonly friends: Map<number, IpcFriendInfo>;
13
+ readonly groups: Map<number, IpcGroupInfo>;
14
+ request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
15
+ }
16
+ export interface IcqqAgentDeps {
17
+ getEndpoint: (endpointId: string) => IcqqAgentEndpoint;
18
+ getAdapter?: () => {
19
+ getEndpoint: (id: string) => IcqqAgentEndpoint;
20
+ };
21
+ }
22
+ export declare function registerIcqqAgentEndpoint(endpointId: string, endpoint: IcqqAgentEndpoint): () => void;
23
+ /** Optional override used by tests / transitional callers. Pass `null` to clear. */
24
+ export declare function setIcqqAgentDeps(deps: IcqqAgentDeps | null): void;
25
+ export declare function getIcqqAgentDeps(): IcqqAgentDeps;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Agent tool deps for icqq.
3
+ * Endpoints register themselves on start; tools look up by config name / QQ uin.
4
+ */
5
+ const endpoints = new Map();
6
+ let override = null;
7
+ export function registerIcqqAgentEndpoint(endpointId, endpoint) {
8
+ endpoints.set(endpointId, endpoint);
9
+ return () => {
10
+ if (endpoints.get(endpointId) === endpoint) {
11
+ endpoints.delete(endpointId);
12
+ }
13
+ };
14
+ }
15
+ /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
+ export function setIcqqAgentDeps(deps) {
17
+ override = deps;
18
+ }
19
+ function lookup(endpointId) {
20
+ const registered = endpoints.get(endpointId);
21
+ if (!registered)
22
+ throw new Error(`Endpoint ${endpointId} 不存在`);
23
+ return registered;
24
+ }
25
+ export function getIcqqAgentDeps() {
26
+ if (override)
27
+ return override;
28
+ return {
29
+ getEndpoint: lookup,
30
+ getAdapter: () => ({ getEndpoint: lookup }),
31
+ };
32
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
3
3
  */
4
- import type { MessageSegment } from "zhin.js";
4
+ import { type MessageSegment } from "zhin.js";
5
5
  import { type IpcGuildMessageEventData } from "./protocol.js";
6
6
  import type { IpcClient } from "./ipc-client.js";
7
7
  export interface IcqqGuildChannelEntry {
@@ -45,4 +45,3 @@ export declare class IcqqGuildCatalog {
45
45
  }
46
46
  export declare function isIcqqGuildIpcEvent(eventName: string | undefined): boolean;
47
47
  export declare function normalizeIcqqGuildInboundMessage(data: IpcGuildMessageEventData & Record<string, unknown>): NormalizedIcqqGuildInbound | null;
48
- //# sourceMappingURL=icqq-guild.d.ts.map
package/lib/icqq-guild.js CHANGED
@@ -1,6 +1,9 @@
1
+ /**
2
+ * ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
3
+ */
4
+ import { toCanonicalSegments } from "zhin.js";
1
5
  import { Actions } from "./protocol.js";
2
6
  import { parseCqMessage } from "./cq-message.js";
3
- import { toCanonicalSegments } from "./segment-mapper.js";
4
7
  export class IcqqGuildCatalog {
5
8
  channels = new Map();
6
9
  guilds = new Map();
@@ -112,4 +115,3 @@ export function normalizeIcqqGuildInboundMessage(data) {
112
115
  raw: data,
113
116
  };
114
117
  }
115
- //# sourceMappingURL=icqq-guild.js.map
@@ -91,6 +91,16 @@ export interface NormalizedIcqqInbound {
91
91
  export declare function parseIcqqSenderGroupRole(sender: {
92
92
  role?: unknown;
93
93
  } | undefined): GroupRole | undefined;
94
+ /**
95
+ * 判断入站消息是否 @ 了本机账号(uin = endpoint 配置 name,即 QQ 号)。
96
+ * 新 Plugin Runtime 的 Message.content 为纯文本,at 信息只能经 metadata 传递,
97
+ * endpoint 在 admit 时用本函数标注 `metadata.mentioned`(供 Agent Host 的 @ 触发)。
98
+ */
99
+ export declare function isIcqqBotMentioned(input: {
100
+ readonly uin: string;
101
+ readonly content?: readonly MessageSegment[] | null;
102
+ readonly rawMessage?: string;
103
+ }): boolean;
94
104
  /** 从 IPC 事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
95
105
  export declare function unwrapIcqqIpcEventPayload(event: {
96
106
  data?: unknown;
@@ -127,4 +137,3 @@ export declare class InboundMessageDeduper {
127
137
  shouldProcess(messageId: string): boolean;
128
138
  clear(): void;
129
139
  }
130
- //# sourceMappingURL=icqq-inbound.d.ts.map
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * icqq 守护进程 IPC 入站事件归一化(post_type / message_type / message 段)
3
3
  */
4
- import { Message } from "zhin.js";
4
+ import { Message, toCanonicalSegments } from "zhin.js";
5
5
  import { parseCqMessage } from "./cq-message.js";
6
- import { toCanonicalSegments } from "./segment-mapper.js";
7
6
  import { extractForwardResidFromJsonElement } from "./forward-msg.js";
8
7
  const DEDUPE_TTL_MS = 120_000;
9
8
  const ICQQ_GROUP_ROLES = new Set(["owner", "admin", "member"]);
@@ -15,6 +14,32 @@ export function parseIcqqSenderGroupRole(sender) {
15
14
  const role = raw.trim().toLowerCase();
16
15
  return ICQQ_GROUP_ROLES.has(role) ? role : undefined;
17
16
  }
17
+ /**
18
+ * 判断入站消息是否 @ 了本机账号(uin = endpoint 配置 name,即 QQ 号)。
19
+ * 新 Plugin Runtime 的 Message.content 为纯文本,at 信息只能经 metadata 传递,
20
+ * endpoint 在 admit 时用本函数标注 `metadata.mentioned`(供 Agent Host 的 @ 触发)。
21
+ */
22
+ export function isIcqqBotMentioned(input) {
23
+ const uin = String(input.uin ?? "").trim();
24
+ if (!uin)
25
+ return false;
26
+ for (const seg of input.content ?? []) {
27
+ if (!seg || (seg.type !== "at" && seg.type !== "mention"))
28
+ continue;
29
+ const data = (seg.data ?? {});
30
+ const target = String(data.qq ?? data.user_id ?? data.id ?? data.target ?? "").trim();
31
+ if (target && target !== "all" && Number(target) === Number(uin)) {
32
+ return true;
33
+ }
34
+ }
35
+ const raw = input.rawMessage ?? "";
36
+ if (!raw)
37
+ return false;
38
+ const escaped = uin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
+ // OneBot CQ 码 `[CQ:at,qq=uin]`;QQ 频道 markdown `<@!uin>`
40
+ return new RegExp(`\\[CQ:at,qq=${escaped}\\]`, "u").test(raw)
41
+ || new RegExp(`<@!?${escaped}>`, "u").test(raw);
42
+ }
18
43
  const MESSAGE_POST_TYPES = new Set(["message"]);
19
44
  function isMessagePostType(postType) {
20
45
  if (typeof postType !== "string" || postType === "")
@@ -538,4 +563,3 @@ export class InboundMessageDeduper {
538
563
  this.seen.clear();
539
564
  }
540
565
  }
541
- //# sourceMappingURL=icqq-inbound.js.map
package/lib/index.d.ts CHANGED
@@ -1,18 +1,6 @@
1
- import type { Router } from "@zhin.js/host-router";
2
- import { PageManager } from "@zhin.js/host-api";
3
- import { IcqqAdapter } from "./adapter.js";
4
- declare module "zhin.js" {
5
- namespace Plugin {
6
- interface Contexts {
7
- web: PageManager;
8
- router: Router;
9
- }
10
- }
11
- interface Adapters {
12
- icqq: IcqqAdapter;
13
- }
14
- }
15
- export * from "./types.js";
16
- export { IcqqEndpoint } from "./endpoint.js";
17
- export { IcqqAdapter } from "./adapter.js";
18
- //# sourceMappingURL=index.d.ts.map
1
+ export { Actions, formatInboundContent, formatInboundTarget, formatOutboundBody, parseSendTarget, resolveIcqqConfig, type IcqqAdapterConfig, type IcqqInboundMessage, type IcqqWireSegment, type IpcEvent, type IpcMessage, type IpcRequest, type IpcResponse, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
2
+ export { IcqqIpcEndpoint, type CreateIcqqIpc, type IcqqEndpointOptions, type IcqqIpcTransport, } from './endpoint.js';
3
+ export * from './types.js';
4
+ export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, type IcqqAgentDeps, type IcqqAgentEndpoint, } from './icqq-agent-deps.js';
5
+ export { IpcClient } from './ipc-client.js';
6
+ export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
package/lib/index.js CHANGED
@@ -1,50 +1,6 @@
1
- /**
2
- * ICQQ 适配器入口:类型扩展、导出、注册
3
- */
4
- import path from "path";
5
- import { usePlugin, } from "zhin.js";
6
- import { registerAgentPromptContributor, unregisterAgentPromptContributor } from "zhin.js/agent";
7
- import { createIcqqAgentPromptContributor } from "./agent-prompt.js";
8
- import { IcqqAdapter } from "./adapter.js";
9
- import { registerTools } from "./tools/index.js";
10
- import { registerRoutes } from "./routes.js";
11
- import { registerLoginAssistRoutes } from "./login-assist-routes.js";
12
- export * from "./types.js";
13
- export { IcqqEndpoint } from "./endpoint.js";
14
- export { IcqqAdapter } from "./adapter.js";
15
- const plugin = usePlugin();
16
- const { provide, useContext, addCommand, root } = plugin;
17
- // ── 适配器注册 ─────────────────────────────────────────────────────
18
- provide({
19
- name: "icqq",
20
- description: "ICQQ Adapter",
21
- mounted: async (p) => {
22
- registerAgentPromptContributor(createIcqqAgentPromptContributor());
23
- const adapter = new IcqqAdapter(p);
24
- await adapter.start();
25
- return adapter;
26
- },
27
- dispose: async (adapter) => {
28
- unregisterAgentPromptContributor("icqq");
29
- await adapter.stop();
30
- },
31
- });
32
- // ── AI 工具注册 ────────────────────────────────────────────────────
33
- useContext("tool", "icqq", (toolService, icqq) => {
34
- return registerTools(toolService, icqq, plugin.name);
35
- });
36
- // ── Web 控制台入口 ─────────────────────────────────────────────────
37
- useContext("web", (pageManager) => {
38
- pageManager.addEntry({
39
- id: "icqq",
40
- development: path.resolve(import.meta.dirname, "../client/index.tsx"),
41
- production: path.resolve(import.meta.dirname, "../dist/index.js"),
42
- meta: { name: "ICQQ" },
43
- });
44
- });
45
- // ── HTTP 路由 ──────────────────────────────────────────────────────
46
- useContext("router", "icqq", async (router, icqq) => {
47
- registerRoutes(router, icqq, root);
48
- registerLoginAssistRoutes(router, root);
49
- });
50
- //# sourceMappingURL=index.js.map
1
+ export { Actions, formatInboundContent, formatInboundTarget, formatOutboundBody, parseSendTarget, resolveIcqqConfig, } from './protocol.js';
2
+ export { IcqqIpcEndpoint, } from './endpoint.js';
3
+ export * from './types.js';
4
+ export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, } from './icqq-agent-deps.js';
5
+ export { IpcClient } from './ipc-client.js';
6
+ export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
@@ -71,4 +71,3 @@ export declare class IpcClient {
71
71
  /** 关闭连接 */
72
72
  close(): void;
73
73
  }
74
- //# sourceMappingURL=ipc-client.d.ts.map
package/lib/ipc-client.js CHANGED
@@ -356,4 +356,3 @@ export class IpcClient {
356
356
  this.socket.destroy();
357
357
  }
358
358
  }
359
- //# sourceMappingURL=ipc-client.js.map
@@ -11,4 +11,3 @@ export declare function resolveIcqqOutboundMediaMode(config: Pick<IcqqEndpointCo
11
11
  * base64 模式:不改动(由 toCqString 生成 base64://,供异机守护进程读取)。
12
12
  */
13
13
  export declare function materializeOutboundBase64(content: SendContent, mode?: IcqqOutboundMediaMode): SendContent;
14
- //# sourceMappingURL=outbound-media.d.ts.map
@@ -63,4 +63,3 @@ export function materializeOutboundBase64(content, mode = "file") {
63
63
  return seg;
64
64
  });
65
65
  }
66
- //# sourceMappingURL=outbound-media.js.map
package/lib/protocol.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * 2. 认证通过后发送 IpcRequest → 等待 IpcResponse
10
10
  * 3. 认证通过后自动接收 icqq 事件推送(IpcEvent),断开连接自动停止
11
11
  *
12
- * 登录相关:**推送**走 IpcEvent(`system.login.*`),续传 invoke 见 `login-ipc-contract.ts`。
12
+ * 登录相关:**推送**走 IpcEvent(`system.login.*`)。
13
13
  */
14
14
  /** CLI → Daemon 请求 */
15
15
  export type IpcRequest = {
@@ -195,4 +195,75 @@ export declare const Actions: {
195
195
  readonly SET_NOTIFY: "set_notify";
196
196
  readonly GET_NOTIFY: "get_notify";
197
197
  };
198
- //# sourceMappingURL=protocol.d.ts.map
198
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
199
+ export interface IcqqAdapterConfig {
200
+ readonly name?: string;
201
+ readonly autoReconnect?: boolean;
202
+ readonly outboundMedia?: 'file' | 'base64';
203
+ readonly rpc?: {
204
+ readonly host?: string;
205
+ readonly port?: number;
206
+ readonly token?: string;
207
+ };
208
+ /** Transitional: legacy root `endpoints[]` with `context: icqq`. */
209
+ readonly endpoints?: ReadonlyArray<{
210
+ readonly context?: string;
211
+ readonly name?: string;
212
+ readonly autoReconnect?: boolean;
213
+ readonly outboundMedia?: 'file' | 'base64';
214
+ readonly rpc?: {
215
+ readonly host?: string;
216
+ readonly port?: number;
217
+ readonly token?: string;
218
+ };
219
+ }>;
220
+ }
221
+ export interface ResolvedIcqqConfig {
222
+ readonly context: 'icqq';
223
+ readonly name: string;
224
+ readonly autoReconnect: boolean;
225
+ readonly outboundMedia?: 'file' | 'base64';
226
+ readonly rpc?: {
227
+ readonly host: string;
228
+ readonly port: number;
229
+ readonly token: string;
230
+ };
231
+ }
232
+ export interface IcqqWireSegment {
233
+ readonly type: string;
234
+ readonly data?: Record<string, unknown>;
235
+ }
236
+ export interface IcqqInboundMessage {
237
+ readonly id: string;
238
+ readonly target: string;
239
+ readonly content: string;
240
+ readonly sender: string;
241
+ readonly channelType: 'private' | 'group' | 'channel';
242
+ readonly metadata?: Record<string, unknown>;
243
+ }
244
+ export type ParsedIcqqSendTarget = {
245
+ readonly kind: 'private';
246
+ readonly userId: number;
247
+ } | {
248
+ readonly kind: 'group';
249
+ readonly groupId: number;
250
+ } | {
251
+ readonly kind: 'temp';
252
+ readonly groupId: number;
253
+ readonly userId: number;
254
+ } | {
255
+ readonly kind: 'channel';
256
+ readonly guildId: string;
257
+ readonly channelId: string;
258
+ };
259
+ export declare function resolveIcqqConfig(config?: IcqqAdapterConfig): ResolvedIcqqConfig;
260
+ /** Gateway reply target: `private:uid` / `group:gid` / `temp:gid:uid` / `channel:guild:channel`. */
261
+ export declare function formatInboundTarget(input: {
262
+ readonly channelType: 'private' | 'group' | 'channel';
263
+ readonly channelId: string;
264
+ readonly channelParentGroupId?: string;
265
+ readonly guildId?: string;
266
+ }): string;
267
+ export declare function parseSendTarget(target: string): ParsedIcqqSendTarget;
268
+ export declare function formatOutboundBody(payload: unknown): string;
269
+ export declare function formatInboundContent(rawMessage: string): string;
package/lib/protocol.js CHANGED
@@ -9,8 +9,9 @@
9
9
  * 2. 认证通过后发送 IpcRequest → 等待 IpcResponse
10
10
  * 3. 认证通过后自动接收 icqq 事件推送(IpcEvent),断开连接自动停止
11
11
  *
12
- * 登录相关:**推送**走 IpcEvent(`system.login.*`),续传 invoke 见 `login-ipc-contract.ts`。
12
+ * 登录相关:**推送**走 IpcEvent(`system.login.*`)。
13
13
  */
14
+ import { pickCredential } from '@zhin.js/adapter';
14
15
  /** 从 IPC 入站 payload 解析可用于引用/撤回的消息 ID */
15
16
  export { resolveIcqqInboundMessageId } from "./icqq-inbound.js";
16
17
  /**
@@ -184,4 +185,125 @@ export const Actions = {
184
185
  SET_NOTIFY: "set_notify",
185
186
  GET_NOTIFY: "get_notify",
186
187
  };
187
- //# sourceMappingURL=protocol.js.map
188
+ export function resolveIcqqConfig(config = {}) {
189
+ const entry = config.endpoints?.find((item) => item.context === 'icqq');
190
+ const name = pickCredential(config.name, entry?.name, process.env.ICQQ_ACCOUNT);
191
+ if (!/^\d+$/.test(name)) {
192
+ throw new TypeError('ICQQ adapter requires numeric name (QQ uin) via plugins.<key>.name or ICQQ_ACCOUNT');
193
+ }
194
+ const autoReconnect = config.autoReconnect ?? entry?.autoReconnect ?? true;
195
+ const outboundMedia = config.outboundMedia ?? entry?.outboundMedia;
196
+ const rpcRaw = config.rpc ?? entry?.rpc;
197
+ const rpc = rpcRaw?.host && rpcRaw.port != null && rpcRaw.token
198
+ ? { host: rpcRaw.host, port: Number(rpcRaw.port), token: rpcRaw.token }
199
+ : undefined;
200
+ return {
201
+ context: 'icqq',
202
+ name,
203
+ autoReconnect,
204
+ ...(outboundMedia ? { outboundMedia } : {}),
205
+ ...(rpc ? { rpc } : {}),
206
+ };
207
+ }
208
+ /** Gateway reply target: `private:uid` / `group:gid` / `temp:gid:uid` / `channel:guild:channel`. */
209
+ export function formatInboundTarget(input) {
210
+ if (input.channelType === 'private' && input.channelParentGroupId) {
211
+ return `temp:${input.channelParentGroupId}:${input.channelId}`;
212
+ }
213
+ if (input.channelType === 'channel' && input.guildId) {
214
+ return `channel:${input.guildId}:${input.channelId}`;
215
+ }
216
+ return `${input.channelType}:${input.channelId}`;
217
+ }
218
+ export function parseSendTarget(target) {
219
+ const trimmed = target.trim();
220
+ if (trimmed.startsWith('temp:')) {
221
+ const rest = trimmed.slice(5);
222
+ const [groupId, userId] = rest.split(':');
223
+ if (!groupId || !userId)
224
+ throw new TypeError(`Invalid icqq temp target: ${target}`);
225
+ return { kind: 'temp', groupId: Number(groupId), userId: Number(userId) };
226
+ }
227
+ if (trimmed.startsWith('channel:')) {
228
+ const rest = trimmed.slice(8);
229
+ const idx = rest.indexOf(':');
230
+ if (idx <= 0)
231
+ throw new TypeError(`Invalid icqq channel target: ${target}`);
232
+ return {
233
+ kind: 'channel',
234
+ guildId: rest.slice(0, idx),
235
+ channelId: rest.slice(idx + 1),
236
+ };
237
+ }
238
+ if (trimmed.startsWith('private:')) {
239
+ return { kind: 'private', userId: Number(trimmed.slice(8)) };
240
+ }
241
+ if (trimmed.startsWith('group:')) {
242
+ return { kind: 'group', groupId: Number(trimmed.slice(6)) };
243
+ }
244
+ // Bare numeric id → private (common reply path)
245
+ if (/^\d+$/.test(trimmed)) {
246
+ return { kind: 'private', userId: Number(trimmed) };
247
+ }
248
+ throw new TypeError(`Invalid icqq send target: ${target}`);
249
+ }
250
+ export function formatOutboundBody(payload) {
251
+ if (typeof payload === 'string') {
252
+ const text = payload.trim();
253
+ return text || '\u200b';
254
+ }
255
+ if (payload == null)
256
+ return '\u200b';
257
+ if (!Array.isArray(payload)) {
258
+ // 单个段对象(非数组)按单元素数组处理,杜绝 String(object) → '[object Object]'
259
+ if (isWireSegment(payload)) {
260
+ return formatOutboundBody([payload]);
261
+ }
262
+ if (typeof payload === 'object' && payload !== null && 'text' in payload) {
263
+ const text = String(payload.text ?? '').trim();
264
+ return text || '\u200b';
265
+ }
266
+ const text = String(payload).trim();
267
+ return text || '\u200b';
268
+ }
269
+ const joined = payload.map((seg) => {
270
+ if (typeof seg === 'string')
271
+ return seg;
272
+ const item = seg;
273
+ switch (item.type) {
274
+ case 'text':
275
+ return String(item.data?.text ?? '');
276
+ case 'at':
277
+ case 'mention':
278
+ return `[at:${item.data?.qq ?? item.data?.id ?? item.data?.target ?? ''}]`;
279
+ case 'image':
280
+ return `[image:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
281
+ case 'face':
282
+ return `[face:${item.data?.id ?? ''}]`;
283
+ case 'reply':
284
+ return `[reply:${item.data?.message_id ?? item.data?.id ?? ''}]`;
285
+ case 'record':
286
+ case 'audio':
287
+ return `[record:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
288
+ case 'video':
289
+ return `[video:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
290
+ default:
291
+ return String(item.data?.text ?? '');
292
+ }
293
+ }).join('').trim();
294
+ return joined || '\u200b';
295
+ }
296
+ export function formatInboundContent(rawMessage) {
297
+ return rawMessage;
298
+ }
299
+ /** base64 媒体回退:CQ `base64://` 由守护进程解码(异机 RPC 场景)。 */
300
+ function base64Media(data) {
301
+ const b64 = data?.base64;
302
+ return typeof b64 === 'string' && b64 ? `base64://${b64}` : undefined;
303
+ }
304
+ /** 段对象判定:`{ type: string, data? }`(非数组 payload 的防御入口)。 */
305
+ function isWireSegment(value) {
306
+ return typeof value === 'object'
307
+ && value !== null
308
+ && typeof value.type === 'string';
309
+ }
package/lib/types.d.ts CHANGED
@@ -70,4 +70,3 @@ export interface IpcMemberInfo {
70
70
  last_sent_time?: number;
71
71
  shutup_time?: number;
72
72
  }
73
- //# sourceMappingURL=types.d.ts.map
package/lib/types.js CHANGED
@@ -4,4 +4,3 @@
4
4
  * 不再直接依赖 @icqqjs/icqq,通过 @icqqjs/cli 守护进程 IPC 通信。
5
5
  */
6
6
  export {};
7
- //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-icqq",
3
- "version": "6.0.1",
4
- "description": "Zhin.js adapter for ICQQ (QQ Bot)",
3
+ "version": "6.0.3",
4
+ "description": "Zhin.js ICQQ adapter for Plugin Runtime (IPC daemon client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -33,56 +33,49 @@
33
33
  "type": "git",
34
34
  "directory": "plugins/adapters/icqq"
35
35
  },
36
- "files": [
37
- "src",
38
- "lib",
39
- "client",
40
- "dist",
41
- "skills",
42
- "plugin.yml",
43
- "README.md",
44
- "CHANGELOG.md"
45
- ],
46
- "dependencies": {},
47
- "devDependencies": {
48
- "@radix-ui/themes": "^3.2.1",
49
- "@types/react": "^19.2.17",
50
- "@types/react-dom": "^19.2.3",
51
- "lucide-react": "^1.22.0",
52
- "radix-ui": "^1.6.2",
53
- "typescript": "^6.0.3",
54
- "@zhin.js/agent": "1.0.2",
55
- "@zhin.js/cli": "1.0.92",
56
- "@zhin.js/client": "2.0.4",
57
- "@zhin.js/contract": "1.0.2",
58
- "@zhin.js/core": "1.3.3",
59
- "@zhin.js/host-api": "2.0.4",
60
- "@zhin.js/host-router": "2.0.2",
61
- "zhin.js": "4.1.1"
36
+ "dependencies": {
37
+ "@zhin.js/adapter": "1.0.1",
38
+ "@zhin.js/core": "1.3.5",
39
+ "@zhin.js/logger": "1.0.75",
40
+ "@zhin.js/plugin-runtime": "1.0.1"
62
41
  },
63
42
  "peerDependencies": {
64
- "@zhin.js/agent": "1.0.2",
65
- "@zhin.js/client": "2.0.4",
66
- "@zhin.js/host-api": "2.0.4",
67
- "@zhin.js/contract": "1.0.2",
68
- "@zhin.js/core": "1.3.3",
69
- "@zhin.js/host-router": "2.0.2",
70
- "zhin.js": "4.1.1"
43
+ "zod": "^4.0.0",
44
+ "@zhin.js/adapter": "1.0.1",
45
+ "@zhin.js/agent": "1.0.4",
46
+ "@zhin.js/core": "1.3.5",
47
+ "@zhin.js/plugin-runtime": "1.0.1",
48
+ "zhin.js": "4.1.3"
71
49
  },
72
50
  "peerDependenciesMeta": {
73
- "@zhin.js/agent": {
74
- "optional": true
75
- },
76
- "@zhin.js/client": {
51
+ "zhin.js": {
77
52
  "optional": true
78
53
  },
79
- "@zhin.js/host-router": {
54
+ "@zhin.js/agent": {
80
55
  "optional": true
81
56
  },
82
- "@zhin.js/host-api": {
57
+ "zod": {
83
58
  "optional": true
84
59
  }
85
60
  },
61
+ "devDependencies": {
62
+ "@types/node": "^26.1.0",
63
+ "typescript": "^6.0.3",
64
+ "vitest": "^4.1.10",
65
+ "zod": "^4.4.3",
66
+ "@zhin.js/agent": "1.0.4",
67
+ "zhin.js": "4.1.3"
68
+ },
69
+ "files": [
70
+ "adapters",
71
+ "plugin.ts",
72
+ "schema.json",
73
+ "src",
74
+ "lib",
75
+ "agent",
76
+ "README.md",
77
+ "CHANGELOG.md"
78
+ ],
86
79
  "publishConfig": {
87
80
  "access": "public",
88
81
  "registry": "https://registry.npmjs.org"
@@ -90,8 +83,23 @@
90
83
  "engines": {
91
84
  "node": "^20.19.0 || >=22.12.0"
92
85
  },
86
+ "zhin": {
87
+ "protocol": 1,
88
+ "type": "plugin",
89
+ "entry": "./plugin.ts",
90
+ "engine": "^1.0.0",
91
+ "runtime": "trusted",
92
+ "features": [
93
+ {
94
+ "package": "@zhin.js/adapter",
95
+ "api": "^1.0.0"
96
+ }
97
+ ],
98
+ "plugins": []
99
+ },
93
100
  "scripts": {
94
- "build": "zhin build",
95
- "clean": "rimraf lib"
101
+ "build": "tsc",
102
+ "clean": "rimraf lib",
103
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/icqq/tests"
96
104
  }
97
105
  }