@zhin.js/adapter-qq 1.0.84 → 1.1.2

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 (107) hide show
  1. package/CHANGELOG.md +940 -17
  2. package/PERMITS.md +25 -0
  3. package/README.md +135 -233
  4. package/adapters/qq/index.js +50 -0
  5. package/adapters/qq/index.ts +71 -0
  6. package/agents/qq/agent.json +20 -0
  7. package/agents/qq/boundaries.md +3 -0
  8. package/agents/qq/conventions.md +3 -0
  9. package/agents/qq/skills/qq/SKILL.md +22 -0
  10. package/agents/qq/skills/qq/tools/add_role/index.js +21 -0
  11. package/agents/qq/skills/qq/tools/add_role/index.ts +21 -0
  12. package/agents/qq/skills/qq/tools/channel_info/index.js +17 -0
  13. package/agents/qq/skills/qq/tools/channel_info/index.ts +17 -0
  14. package/agents/qq/skills/qq/tools/create_role/index.js +20 -0
  15. package/agents/qq/skills/qq/tools/create_role/index.ts +20 -0
  16. package/agents/qq/skills/qq/tools/list_channels/index.js +18 -0
  17. package/agents/qq/skills/qq/tools/list_channels/index.ts +18 -0
  18. package/agents/qq/skills/qq/tools/list_guilds/index.js +14 -0
  19. package/agents/qq/skills/qq/tools/list_guilds/index.ts +14 -0
  20. package/agents/qq/skills/qq/tools/list_roles/index.js +18 -0
  21. package/agents/qq/skills/qq/tools/list_roles/index.ts +18 -0
  22. package/agents/qq/skills/qq/tools/member_detail/index.js +18 -0
  23. package/agents/qq/skills/qq/tools/member_detail/index.ts +18 -0
  24. package/agents/qq/skills/qq/tools/remove_role/index.js +21 -0
  25. package/agents/qq/skills/qq/tools/remove_role/index.ts +21 -0
  26. package/agents/qq/system.md +3 -0
  27. package/commands/qq/endpoint/add/[[id]]/index.js +3 -0
  28. package/commands/qq/endpoint/add/[[id]]/index.ts +3 -0
  29. package/commands/qq/endpoint/cancel/index.js +11 -0
  30. package/commands/qq/endpoint/cancel/index.ts +15 -0
  31. package/commands/qq/endpoint/definition.js +188 -0
  32. package/commands/qq/endpoint/definition.ts +263 -0
  33. package/commands/qq/endpoint/list/index.js +3 -0
  34. package/commands/qq/endpoint/list/index.ts +3 -0
  35. package/commands/qq/endpoint/remove/[id]/index.js +3 -0
  36. package/commands/qq/endpoint/remove/[id]/index.ts +3 -0
  37. package/lib/client.d.ts +12 -0
  38. package/lib/client.js +2 -0
  39. package/lib/endpoint.d.ts +56 -0
  40. package/lib/endpoint.js +456 -0
  41. package/lib/index.d.ts +8 -18
  42. package/lib/index.js +8 -338
  43. package/lib/outbound.d.ts +17 -0
  44. package/lib/outbound.js +234 -0
  45. package/lib/platform-permit.d.ts +15 -0
  46. package/lib/platform-permit.js +41 -0
  47. package/lib/protocol.d.ts +90 -0
  48. package/lib/protocol.js +161 -0
  49. package/lib/qq-bind-api.d.ts +36 -0
  50. package/lib/qq-bind-api.js +89 -0
  51. package/lib/qq-bind-flow.d.ts +19 -0
  52. package/lib/qq-bind-flow.js +85 -0
  53. package/lib/qq-bot-kind-prompt.d.ts +9 -0
  54. package/lib/qq-bot-kind-prompt.js +37 -0
  55. package/lib/qq-intents.d.ts +27 -0
  56. package/lib/qq-intents.js +51 -0
  57. package/lib/qq-runtime-state.d.ts +30 -0
  58. package/lib/qq-runtime-state.js +14 -0
  59. package/lib/side-event-dispatch.d.ts +10 -0
  60. package/lib/side-event-dispatch.js +114 -0
  61. package/lib/webhook.d.ts +18 -0
  62. package/lib/webhook.js +103 -0
  63. package/lib/ws.d.ts +55 -0
  64. package/lib/ws.js +234 -0
  65. package/middlewares/bot-kind-reply/index.js +47 -0
  66. package/middlewares/bot-kind-reply/index.ts +65 -0
  67. package/package.json +82 -29
  68. package/plugin.js +18 -0
  69. package/schema.json +158 -0
  70. package/src/client.ts +16 -0
  71. package/src/endpoint.ts +534 -0
  72. package/src/index.ts +66 -326
  73. package/src/outbound.ts +255 -0
  74. package/src/platform-permit.ts +56 -0
  75. package/src/protocol.ts +249 -0
  76. package/src/qq-bind-api.ts +145 -0
  77. package/src/qq-bind-flow.ts +127 -0
  78. package/src/qq-bot-kind-prompt.ts +47 -0
  79. package/src/qq-intents.ts +77 -0
  80. package/src/qq-runtime-state.ts +51 -0
  81. package/src/side-event-dispatch.ts +140 -0
  82. package/src/webhook.ts +132 -0
  83. package/src/ws.ts +331 -0
  84. package/client/Dashboard.tsx +0 -230
  85. package/client/index.tsx +0 -11
  86. package/client/tsconfig.json +0 -7
  87. package/client/utils/api.ts +0 -17
  88. package/dist/index.js +0 -30
  89. package/lib/adapter.d.ts +0 -17
  90. package/lib/adapter.d.ts.map +0 -1
  91. package/lib/adapter.js +0 -49
  92. package/lib/adapter.js.map +0 -1
  93. package/lib/bot.d.ts +0 -102
  94. package/lib/bot.d.ts.map +0 -1
  95. package/lib/bot.js +0 -337
  96. package/lib/bot.js.map +0 -1
  97. package/lib/index.d.ts.map +0 -1
  98. package/lib/index.js.map +0 -1
  99. package/lib/types.d.ts +0 -16
  100. package/lib/types.d.ts.map +0 -1
  101. package/lib/types.js +0 -2
  102. package/lib/types.js.map +0 -1
  103. package/plugin.yml +0 -3
  104. package/skills/qq/SKILL.md +0 -97
  105. package/src/adapter.ts +0 -58
  106. package/src/bot.ts +0 -347
  107. package/src/types.ts +0 -24
@@ -0,0 +1,17 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '获取 QQ 频道中指定子频道的详细信息',
7
+ inputSchema: z.object({
8
+ channel_id: z.string().describe('子频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'guild_admin')],
13
+ async execute({ channel_id }, context) {
14
+ const client = context.$client;
15
+ return client.getChannelInfo(channel_id);
16
+ },
17
+ });
@@ -0,0 +1,17 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ channel_id: string }>({
6
+ description: '获取 QQ 频道中指定子频道的详细信息',
7
+ inputSchema: z.object({
8
+ channel_id: z.string().describe('子频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'guild_admin')],
13
+ async execute({ channel_id }: { channel_id: string }, context) {
14
+ const client = context.$client;
15
+ return client.getChannelInfo(channel_id);
16
+ },
17
+ });
@@ -0,0 +1,20 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '创建 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ name: z.string().describe('角色名称'),
10
+ color: z.number().optional().describe('颜色(RGB 十进制数值)'),
11
+ }),
12
+ adapter: 'qq',
13
+ tags: ['qq'],
14
+ permissions: [platformPermission('qq', 'guild_owner')],
15
+ async execute({ guild_id, name, color }, context) {
16
+ const client = context.$client;
17
+ const role = await client.createGuildRole(guild_id, name, color);
18
+ return { success: !!role, role, message: role ? `角色 "${name}" 创建成功` : '创建失败' };
19
+ },
20
+ });
@@ -0,0 +1,20 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string; name: string; color?: number }>({
6
+ description: '创建 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ name: z.string().describe('角色名称'),
10
+ color: z.number().optional().describe('颜色(RGB 十进制数值)'),
11
+ }),
12
+ adapter: 'qq',
13
+ tags: ['qq'],
14
+ permissions: [platformPermission('qq', 'guild_owner')],
15
+ async execute({ guild_id, name, color }: { guild_id: string; name: string; color?: number }, context) {
16
+ const client = context.$client;
17
+ const role = await client.createGuildRole(guild_id, name, color);
18
+ return { success: !!role, role, message: role ? `角色 "${name}" 创建成功` : '创建失败' };
19
+ },
20
+ });
@@ -0,0 +1,18 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '获取 QQ 频道下的子频道列表',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'manage_channels')],
13
+ async execute({ guild_id }, context) {
14
+ const client = context.$client;
15
+ const channels = await client.getChannels(guild_id);
16
+ return { channels, count: channels.length };
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string }>({
6
+ description: '获取 QQ 频道下的子频道列表',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'manage_channels')],
13
+ async execute({ guild_id }: { guild_id: string }, context) {
14
+ const client = context.$client;
15
+ const channels = await client.getChannels(guild_id);
16
+ return { channels, count: channels.length };
17
+ },
18
+ });
@@ -0,0 +1,14 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ export default defineAgentTool({
5
+ description: '获取 QQ 频道列表',
6
+ inputSchema: z.object({}),
7
+ adapter: 'qq',
8
+ tags: ['qq'],
9
+ async execute(_input, context) {
10
+ const client = context.$client;
11
+ const guilds = await client.getGuilds();
12
+ return { guilds, count: guilds.length };
13
+ },
14
+ });
@@ -0,0 +1,14 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+
4
+ export default defineAgentTool<Record<string, never>>({
5
+ description: '获取 QQ 频道列表',
6
+ inputSchema: z.object({}),
7
+ adapter: 'qq',
8
+ tags: ['qq'],
9
+ async execute(_input, context) {
10
+ const client = context.$client;
11
+ const guilds = await client.getGuilds();
12
+ return { guilds, count: guilds.length };
13
+ },
14
+ });
@@ -0,0 +1,18 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '获取 QQ 频道角色列表',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'manage_roles')],
13
+ async execute({ guild_id }, context) {
14
+ const client = context.$client;
15
+ const roles = await client.getGuildRoles(guild_id);
16
+ return { roles, count: roles.length };
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string }>({
6
+ description: '获取 QQ 频道角色列表',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ }),
10
+ adapter: 'qq',
11
+ tags: ['qq'],
12
+ permissions: [platformPermission('qq', 'manage_roles')],
13
+ async execute({ guild_id }: { guild_id: string }, context) {
14
+ const client = context.$client;
15
+ const roles = await client.getGuildRoles(guild_id);
16
+ return { roles, count: roles.length };
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '获取 QQ 频道中指定成员的详细信息',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ user_id: z.string().describe('用户 ID'),
10
+ }),
11
+ adapter: 'qq',
12
+ tags: ['qq'],
13
+ permissions: [platformPermission('qq', 'guild_admin')],
14
+ async execute({ guild_id, user_id }, context) {
15
+ const client = context.$client;
16
+ return client.getGuildMember(guild_id, user_id);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string; user_id: string }>({
6
+ description: '获取 QQ 频道中指定成员的详细信息',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ user_id: z.string().describe('用户 ID'),
10
+ }),
11
+ adapter: 'qq',
12
+ tags: ['qq'],
13
+ permissions: [platformPermission('qq', 'guild_admin')],
14
+ async execute({ guild_id, user_id }: { guild_id: string; user_id: string }, context) {
15
+ const client = context.$client;
16
+ return client.getGuildMember(guild_id, user_id);
17
+ },
18
+ });
@@ -0,0 +1,21 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '移除成员的 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ channel_id: z.string().describe('子频道 ID'),
10
+ user_id: z.string().describe('用户 ID'),
11
+ role_id: z.string().describe('角色 ID'),
12
+ }),
13
+ adapter: 'qq',
14
+ tags: ['qq'],
15
+ permissions: [platformPermission('qq', 'manage_roles')],
16
+ async execute({ guild_id, channel_id, user_id, role_id }, context) {
17
+ const client = context.$client;
18
+ const success = await client.removeMemberRole(guild_id, channel_id, user_id, role_id);
19
+ return { success, message: success ? '已移除成员的角色' : '操作失败' };
20
+ },
21
+ });
@@ -0,0 +1,21 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string; channel_id: string; user_id: string; role_id: string }>({
6
+ description: '移除成员的 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ channel_id: z.string().describe('子频道 ID'),
10
+ user_id: z.string().describe('用户 ID'),
11
+ role_id: z.string().describe('角色 ID'),
12
+ }),
13
+ adapter: 'qq',
14
+ tags: ['qq'],
15
+ permissions: [platformPermission('qq', 'manage_roles')],
16
+ async execute({ guild_id, channel_id, user_id, role_id }: { guild_id: string; channel_id: string; user_id: string; role_id: string }, context) {
17
+ const client = context.$client;
18
+ const success = await client.removeMemberRole(guild_id, channel_id, user_id, role_id);
19
+ return { success, message: success ? '已移除成员的角色' : '操作失败' };
20
+ },
21
+ });
@@ -0,0 +1,3 @@
1
+ # QQ Platform Agent
2
+
3
+ You are the qq platform specialist. Interpret requests in the current qq conversation and load only the narrowest private Skill needed for the task.
@@ -0,0 +1,3 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { qqEndpointCommands } from '../../definition.js';
3
+ export default qqEndpointCommands.add;
@@ -0,0 +1,3 @@
1
+ import { qqEndpointCommands } from '../../definition.js';
2
+
3
+ export default qqEndpointCommands.add;
@@ -0,0 +1,11 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { isQqEndpointOperator, QQ_ENDPOINT_FORBIDDEN, qqRuntimeStateToken, runQqEndpointCancel, } from '../definition.js';
4
+ export default defineCommand({
5
+ description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
6
+ execute({ config, input, use }) {
7
+ if (!isQqEndpointOperator(config, input))
8
+ return QQ_ENDPOINT_FORBIDDEN;
9
+ return runQqEndpointCancel(use(qqRuntimeStateToken));
10
+ },
11
+ });
@@ -0,0 +1,15 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ isQqEndpointOperator,
4
+ QQ_ENDPOINT_FORBIDDEN,
5
+ qqRuntimeStateToken,
6
+ runQqEndpointCancel,
7
+ } from '../definition.js';
8
+
9
+ export default defineCommand({
10
+ description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
11
+ execute({ config, input, use }) {
12
+ if (!isQqEndpointOperator(config, input)) return QQ_ENDPOINT_FORBIDDEN;
13
+ return runQqEndpointCancel(use(qqRuntimeStateToken));
14
+ },
15
+ });
@@ -0,0 +1,188 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * `qq endpoint` 命令的业务逻辑(与命令定义文件分离,便于测试)。
4
+ * 通用部分(权限 / list / remove / 配置写回 / .env 持久化)已迁移到
5
+ * @zhin.js/adapter 的 createEndpointCommands 套件;本文件保留 QQ 特化的
6
+ * 扫码绑定 add 流程(经套件 bindFlow 钩子接入)与 cancel,并导出
7
+ * qqEndpointCommands 供 commands/qq/endpoint/ 下的命令文件默认导出。
8
+ */
9
+ import { buildEndpointEnvKey, createEndpointCommands, endpointCommandForbidden, endpointConfigurationStoreToken, extractEndpointCommandReply, formatEndpointList, isEndpointOperator, removeEndpointById, } from 'zhin.js/adapter';
10
+ import { defineCommand } from 'zhin.js/command';
11
+ import { QQ_BOT_KIND_PROMPT, qqCommandSessionKey } from "../../../lib/qq-bot-kind-prompt.js";
12
+ import { startQqBindFlow } from "../../../lib/qq-bind-flow.js";
13
+ import { defaultQqEndpointIntentFields } from "../../../lib/qq-intents.js";
14
+ import { qqRuntimeStateToken, } from "../../../lib/qq-runtime-state.js";
15
+ export { qqRuntimeStateToken };
16
+ /**
17
+ * endpoint 管理命令的操作者校验:实例配置声明了 master(顶层或任一端点项)时
18
+ * 仅 master 可执行 add/cancel/remove;未配置则放行(首个扫码绑定者即为 owner,
19
+ * legacy applyBindOwnership 会把 operator 写为新端点的 master)。
20
+ */
21
+ export function isQqEndpointOperator(config, input) {
22
+ return isEndpointOperator(config, input);
23
+ }
24
+ export const QQ_ENDPOINT_FORBIDDEN = endpointCommandForbidden('QQ');
25
+ /**
26
+ * 从命令 input(Runtime Message)提取 $reply;非消息来源(如 Host API 调用)降级为 no-op。
27
+ */
28
+ export function extractQqCommandReply(input) {
29
+ return extractEndpointCommandReply(input);
30
+ }
31
+ function busyFooter(state) {
32
+ if (state.bindFlow)
33
+ return '⚠️ 有进行中的扫码绑定,可用 qq endpoint cancel 取消';
34
+ if (state.pendingBotKind) {
35
+ return `⚠️ endpoint「${state.pendingBotKind.endpointId}」等待公域/私域选择,回复 public/private 或 qq endpoint cancel`;
36
+ }
37
+ return undefined;
38
+ }
39
+ /** `qq endpoint list`:运行中的 endpoints(本 generation adapter create 注册)+ 配置里的 endpoints */
40
+ export async function runQqEndpointList(state, store) {
41
+ return formatEndpointList(qqEndpointListSpec, {
42
+ running: state.endpoints.values(),
43
+ configured: await store.list('qq'),
44
+ footer: busyFooter(state),
45
+ });
46
+ }
47
+ const qqEndpointListSpec = {
48
+ adapterKey: 'qq',
49
+ adapterDisplayName: 'QQ',
50
+ describeEntry: (entry) => `appid: ${String(entry.appid)}`,
51
+ };
52
+ async function writeEndpointWithBotKind(pending, botKind, store) {
53
+ const appidKey = buildEndpointEnvKey('qq', pending.endpointId, 'appid');
54
+ const secretKey = buildEndpointEnvKey('qq', pending.endpointId, 'secret');
55
+ const intentFields = defaultQqEndpointIntentFields(botKind);
56
+ const { filePath } = await store.add({
57
+ adapterKey: 'qq',
58
+ entry: {
59
+ id: pending.endpointId,
60
+ appid: `\${${appidKey}}`,
61
+ secret: `\${${secretKey}}`,
62
+ botKind: intentFields.botKind,
63
+ intents: [...intentFields.intents],
64
+ },
65
+ environment: {
66
+ [appidKey]: pending.appId,
67
+ [secretKey]: pending.appSecret,
68
+ },
69
+ });
70
+ return (`✅ 已按 botKind=${intentFields.botKind} 将 endpoint「${pending.endpointId}」写入 .env 与 ${filePath}。\n` +
71
+ `intents: ${intentFields.intents.join(', ')}。\n` +
72
+ '⚠️ 需重启 zhin 后新 endpoint 才会生效。');
73
+ }
74
+ /**
75
+ * 完成 pending 的公域/私域选择:一次性写 .env + yaml。
76
+ * @returns 成功文案;调用方负责清 pending。
77
+ */
78
+ export async function completeQqPendingBotKind(pending, botKind, store) {
79
+ return await writeEndpointWithBotKind(pending, botKind, store);
80
+ }
81
+ /**
82
+ * `qq endpoint add [id]`:启动扫码绑定流程。
83
+ * 返回的 Promise 在二维码链接就绪(或前置失败)时 resolve 为首条回复;
84
+ * 后续状态(已扫码 / 成功 / 失败 / 过期刷新)通过 reply 推回当前会话。
85
+ * 扫码成功后凭据暂存内存并询问公域/私域;确认后一次性写 .env + yaml。
86
+ */
87
+ export function runQqEndpointAdd(state, id, reply, store, input) {
88
+ if (state.bindFlow || state.pendingBotKind) {
89
+ return Promise.resolve('已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先发送 qq endpoint cancel 取消后再试');
90
+ }
91
+ const endpointId = id?.trim() || undefined;
92
+ const sessionKey = qqCommandSessionKey(input);
93
+ return new Promise((resolve) => {
94
+ let firstReplied = false;
95
+ const settle = (text) => {
96
+ if (!firstReplied) {
97
+ firstReplied = true;
98
+ resolve(text);
99
+ return;
100
+ }
101
+ // Follow-ups outlive the inbound Message reply scope; durable reply
102
+ // (OutboundHost) is provided by createEndpointCommands.bindFlow. Never
103
+ // let a late notify crash the process.
104
+ void Promise.resolve(reply(text)).catch(() => undefined);
105
+ };
106
+ const stop = startQqBindFlow({
107
+ onQrDisplayed: (url) => {
108
+ // QQ 出站当前仅支持纯文本(富媒体未迁移),二维码只能发链接文本
109
+ settle(`请用手机 QQ 打开以下链接完成扫码绑定(二维码图片出站暂未支持,故发送链接):\n${url}`);
110
+ },
111
+ onQrExpired: async () => {
112
+ try {
113
+ await reply('二维码已过期,正在刷新,请扫描新链接…');
114
+ }
115
+ catch {
116
+ // same as settle follow-up: best-effort after command scope ends
117
+ }
118
+ },
119
+ onSuccess: async (credentials) => {
120
+ state.bindFlow = null;
121
+ try {
122
+ const [{ appId, appSecret }] = credentials;
123
+ const finalName = endpointId ?? appId;
124
+ // 无会话上下文(Host 等)时无法追问,直接按公域一次性写入
125
+ if (!sessionKey) {
126
+ settle(await writeEndpointWithBotKind({
127
+ endpointId: finalName,
128
+ appId,
129
+ appSecret,
130
+ sessionKey: '',
131
+ }, 'public', store) +
132
+ '\n(当前会话无法交互选择公/私域;私域请手动改配置。)');
133
+ return;
134
+ }
135
+ state.pendingBotKind = {
136
+ endpointId: finalName,
137
+ appId,
138
+ appSecret,
139
+ sessionKey,
140
+ };
141
+ settle(`✅ 扫码成功!endpoint「${finalName}」。\n` +
142
+ QQ_BOT_KIND_PROMPT);
143
+ }
144
+ catch (error) {
145
+ settle(`绑定成功但写入配置失败:${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+ },
148
+ onFailure: (error) => {
149
+ state.bindFlow = null;
150
+ settle(`❌ QQ 绑定失败:${error.message}`);
151
+ },
152
+ }, { source: 'zhin' });
153
+ state.bindFlow = { id: endpointId, stop };
154
+ });
155
+ }
156
+ /** `qq endpoint cancel`:中止进行中的绑定或待选 botKind */
157
+ export function runQqEndpointCancel(state) {
158
+ if (state.bindFlow) {
159
+ state.bindFlow.stop();
160
+ state.bindFlow = null;
161
+ return '已取消进行中的 QQ 绑定流程';
162
+ }
163
+ if (state.pendingBotKind) {
164
+ const name = state.pendingBotKind.endpointId;
165
+ state.pendingBotKind = null;
166
+ return `已取消 endpoint「${name}」的绑定(尚未写入任何文件)`;
167
+ }
168
+ return '当前没有进行中的 QQ 绑定流程';
169
+ }
170
+ /** `qq endpoint remove <id>`:从 zhin.config.yml 移除对应 endpoints 项 */
171
+ export async function runQqEndpointRemove(_state, id, store) {
172
+ return await removeEndpointById(qqEndpointListSpec, id, store);
173
+ }
174
+ /**
175
+ * 通用套件生成的 QQ endpoint 命令(add 经 bindFlow 钩子走扫码绑定)。
176
+ * commands/qq/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
177
+ */
178
+ export const qqEndpointCommands = createEndpointCommands({
179
+ ...qqEndpointListSpec,
180
+ fields: [
181
+ { key: 'appid', required: true, env: true, description: 'QQ 机器人 appid' },
182
+ { key: 'secret', required: true, env: true, description: 'QQ 机器人 secret' },
183
+ ],
184
+ running: (use) => use(qqRuntimeStateToken).endpoints.values(),
185
+ listFooter: (use) => busyFooter(use(qqRuntimeStateToken)),
186
+ addDescription: '手机 QQ 扫码绑定机器人:确认公域/私域后一次性写入 .env 与 zhin.config.yml(重启生效)',
187
+ bindFlow: ({ id, reply, input, use }) => runQqEndpointAdd(use(qqRuntimeStateToken), id, reply, use(endpointConfigurationStoreToken), input),
188
+ }, defineCommand);