mu-agent-bridge 1.0.71 → 1.0.73
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/README.md +9 -6
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/control-server.js +5 -2
- package/dist/src-ts/dsh-agent.js +25 -12
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +183 -9
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +68 -2
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/index.js +66 -10
- package/dist/src-ts/plugins/aun/messages.js +3 -0
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +77 -17
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
- package/dist/src-ts/plugins/doubao/index.js +36 -4
- package/dist/src-ts/plugins/dsh/index.js +78 -6
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +242 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
- package/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/dist/src-ts/session-management.js +105 -6
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +70 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-messages.test.js +6 -0
- package/dist/tests-ts/aun-plugin.test.js +85 -0
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +99 -0
- package/dist/tests-ts/control-server.test.js +36 -0
- package/dist/tests-ts/doubao-plugin.test.js +92 -0
- package/dist/tests-ts/dsh-agent.test.js +77 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +119 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +150 -0
- package/dist/tests-ts/personal-assistant-coordination.test.js +76 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +29 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +389 -0
- package/dist/tests-ts/platform-integration.test.js +124 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +280 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +105 -1
- package/dist/tests-ts/session-management.test.js +155 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +154 -2
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +68 -2
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +53 -3
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +66 -10
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +3 -0
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +77 -17
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +36 -4
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +25 -12
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +78 -6
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/personal-assistant/config.schema.json +22 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +242 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TOOL_NAMES = void 0;
|
|
7
|
+
exports.toolDefinitions = toolDefinitions;
|
|
8
|
+
exports.parseToolArguments = parseToolArguments;
|
|
9
|
+
exports.executeTool = executeTool;
|
|
10
|
+
exports.parseExplicitCommand = parseExplicitCommand;
|
|
11
|
+
exports.stringifyToolResult = stringifyToolResult;
|
|
12
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
13
|
+
const coordination_1 = require("./coordination");
|
|
14
|
+
exports.TOOL_NAMES = ["agents_list", "agents_call", "aun_identity_list", "sessions_command", "sessions_today", "self_status", "ability_discover", "ability_call", "ability_request_get", "coordination_dispatch", "coordination_status", "owner_context_read"];
|
|
15
|
+
const ajv = new ajv_1.default({ allErrors: false, strict: false });
|
|
16
|
+
const schemas = {
|
|
17
|
+
agents_list: ajv.compile({ type: "object", additionalProperties: false, properties: {} }),
|
|
18
|
+
agents_call: ajv.compile({ type: "object", additionalProperties: false, required: ["id", "task"], properties: { id: { type: "string", minLength: 1, maxLength: 128 }, task: { type: "string", minLength: 1, maxLength: 20_000 } } }),
|
|
19
|
+
aun_identity_list: ajv.compile({ type: "object", additionalProperties: false, properties: {} }),
|
|
20
|
+
sessions_command: ajv.compile({ type: "object", additionalProperties: false, required: ["command"], properties: { command: { type: "string", minLength: 1, maxLength: 512 } } }),
|
|
21
|
+
sessions_today: ajv.compile({ type: "object", additionalProperties: false, properties: {} }),
|
|
22
|
+
self_status: ajv.compile({ type: "object", additionalProperties: false, properties: {} }),
|
|
23
|
+
ability_discover: ajv.compile({ type: "object", additionalProperties: false, required: ["providerAid"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 } } }),
|
|
24
|
+
ability_call: ajv.compile({ type: "object", additionalProperties: false, required: ["providerAid", "path", "params"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 }, path: { type: "string", minLength: 1, maxLength: 256 }, params: { type: "object" } } }),
|
|
25
|
+
ability_request_get: ajv.compile({ type: "object", additionalProperties: false, required: ["providerAid", "requestId"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 }, requestId: { type: "string", minLength: 1, maxLength: 256 } } }),
|
|
26
|
+
coordination_dispatch: ajv.compile({ type: "object", additionalProperties: false, required: ["agentId", "aid", "task"], properties: { agentId: { type: "string", minLength: 1, maxLength: 64 }, aid: { type: "string", minLength: 3, maxLength: 256 }, task: { type: "string", minLength: 1, maxLength: 20000 } } }),
|
|
27
|
+
coordination_status: ajv.compile({ type: "object", additionalProperties: false, required: ["aid", "threadId"], properties: { aid: { type: "string", minLength: 3, maxLength: 256 }, threadId: { type: "string", minLength: 1, maxLength: 256 } } }),
|
|
28
|
+
owner_context_read: ajv.compile({ type: "object", additionalProperties: false, properties: {} }),
|
|
29
|
+
};
|
|
30
|
+
const toolDescriptions = {
|
|
31
|
+
agents_list: { name: "agents_list", description: "列出当前可调用的 Agent 及其 AID。", parameters: { type: "object", additionalProperties: false, properties: {} } },
|
|
32
|
+
agents_call: { name: "agents_call", description: "向白名单中的 Agent 发送一项独立任务。", parameters: { type: "object", additionalProperties: false, required: ["id", "task"], properties: { id: { type: "string", minLength: 1, maxLength: 128 }, task: { type: "string", minLength: 1, maxLength: 20_000 } } } },
|
|
33
|
+
aun_identity_list: { name: "aun_identity_list", description: "列出本机已知 AUN 身份及绑定状态。", parameters: { type: "object", additionalProperties: false, properties: {} } },
|
|
34
|
+
sessions_command: { name: "sessions_command", description: "执行只读的 /sessions 管理命令。", parameters: { type: "object", additionalProperties: false, required: ["command"], properties: { command: { type: "string", minLength: 1, maxLength: 512 } } } },
|
|
35
|
+
sessions_today: { name: "sessions_today", description: "生成今天工作的小结。", parameters: { type: "object", additionalProperties: false, properties: {} } },
|
|
36
|
+
self_status: { name: "self_status", description: "报告个人助理自身状态。", parameters: { type: "object", additionalProperties: false, properties: {} } },
|
|
37
|
+
ability_discover: { name: "ability_discover", description: "发现已配置 Provider 的只读 AUN Ability。", parameters: { type: "object", additionalProperties: false, required: ["providerAid"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 } } } },
|
|
38
|
+
ability_call: { name: "ability_call", description: "调用无需认证和确认的纯只读 AUN Ability。", parameters: { type: "object", additionalProperties: false, required: ["providerAid", "path", "params"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 }, path: { type: "string", minLength: 1, maxLength: 256 }, params: { type: "object" } } } },
|
|
39
|
+
ability_request_get: { name: "ability_request_get", description: "查询已发送 AUN Ability 的请求状态,不重新发送。", parameters: { type: "object", additionalProperties: false, required: ["providerAid", "requestId"], properties: { providerAid: { type: "string", minLength: 3, maxLength: 256 }, requestId: { type: "string", minLength: 1, maxLength: 256 } } } },
|
|
40
|
+
coordination_dispatch: { name: "coordination_dispatch", description: "显式向白名单 Agent 派发任务并创建独立 thread。", parameters: { type: "object", additionalProperties: false, required: ["agentId", "aid", "task"], properties: { agentId: { type: "string", minLength: 1, maxLength: 64 }, aid: { type: "string", minLength: 3, maxLength: 256 }, task: { type: "string", minLength: 1, maxLength: 20000 } } } },
|
|
41
|
+
coordination_status: { name: "coordination_status", description: "读取派发任务对应 thread 的当前状态和历史。", parameters: { type: "object", additionalProperties: false, required: ["aid", "threadId"], properties: { aid: { type: "string", minLength: 3, maxLength: 256 }, threadId: { type: "string", minLength: 1, maxLength: 256 } } } },
|
|
42
|
+
owner_context_read: { name: "owner_context_read", description: "读取管理员配置的 Owner 共享资料,仅作为参考。", parameters: { type: "object", additionalProperties: false, properties: {} } },
|
|
43
|
+
};
|
|
44
|
+
function toolDefinitions() {
|
|
45
|
+
return exports.TOOL_NAMES.map((name) => ({ type: "function", function: { ...toolDescriptions[name], parameters: { ...toolDescriptions[name].parameters } } }));
|
|
46
|
+
}
|
|
47
|
+
function parseToolArguments(name, raw) {
|
|
48
|
+
if (!exports.TOOL_NAMES.includes(name))
|
|
49
|
+
throw new Error("未知的管理工具");
|
|
50
|
+
let value;
|
|
51
|
+
try {
|
|
52
|
+
value = JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error("管理工具参数不是有效 JSON");
|
|
56
|
+
}
|
|
57
|
+
const validate = schemas[name];
|
|
58
|
+
if (!validate(value))
|
|
59
|
+
throw new Error("管理工具参数不符合要求");
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
async function executeTool(name, args, management, allowedAgents, runtime = {}) {
|
|
63
|
+
if (!exports.TOOL_NAMES.includes(name))
|
|
64
|
+
throw new Error("未知的管理工具");
|
|
65
|
+
if (!schemas[name](args))
|
|
66
|
+
throw new Error("管理工具参数不符合要求");
|
|
67
|
+
switch (name) {
|
|
68
|
+
case "agents_list": {
|
|
69
|
+
const agents = await management.listAgents();
|
|
70
|
+
return allowedAgents ? agents.map((agent) => allowedAgents.includes(agent.id) ? agent : { ...agent, callable: false, reason: "未加入个人助理 Agent 白名单" }) : agents;
|
|
71
|
+
}
|
|
72
|
+
case "agents_call": {
|
|
73
|
+
if (allowedAgents && !allowedAgents.includes(String(args.id)))
|
|
74
|
+
throw new Error("目标 Agent 未加入个人助理白名单");
|
|
75
|
+
const result = await management.callAgent(String(args.id), String(args.task));
|
|
76
|
+
return {
|
|
77
|
+
text: result.text,
|
|
78
|
+
...(result.sessionId ? { sessionId: result.sessionId } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case "aun_identity_list": return management.listAunIdentities();
|
|
82
|
+
case "sessions_command": {
|
|
83
|
+
const command = String(args.command).trim();
|
|
84
|
+
if (!/^\/sessions(?:\s|$)/.test(command) || /[\r\n]/.test(command))
|
|
85
|
+
throw new Error("只支持单行 /sessions 只读命令");
|
|
86
|
+
return management.runSessionCommand(command);
|
|
87
|
+
}
|
|
88
|
+
case "sessions_today": return management.summarizeToday();
|
|
89
|
+
case "self_status": return { status: "ready", capabilities: exports.TOOL_NAMES };
|
|
90
|
+
case "ability_discover": {
|
|
91
|
+
const providerAid = String(args.providerAid);
|
|
92
|
+
if (!runtime.allowedProviders?.includes(providerAid) || !runtime.remoteAbility)
|
|
93
|
+
throw new Error("Provider 未加入远程 Ability 白名单");
|
|
94
|
+
return runtime.remoteAbility.discover(providerAid);
|
|
95
|
+
}
|
|
96
|
+
case "ability_call": {
|
|
97
|
+
const providerAid = String(args.providerAid);
|
|
98
|
+
if (!runtime.allowedProviders?.includes(providerAid) || !runtime.remoteAbility)
|
|
99
|
+
throw new Error("Provider 未加入远程 Ability 白名单");
|
|
100
|
+
return runtime.remoteAbility.call(providerAid, String(args.path), args.params);
|
|
101
|
+
}
|
|
102
|
+
case "ability_request_get": {
|
|
103
|
+
const providerAid = String(args.providerAid);
|
|
104
|
+
if (!runtime.allowedProviders?.includes(providerAid) || !runtime.remoteAbility)
|
|
105
|
+
throw new Error("Provider 未加入远程 Ability 白名单");
|
|
106
|
+
return runtime.remoteAbility.getRequest(providerAid, String(args.requestId));
|
|
107
|
+
}
|
|
108
|
+
case "coordination_dispatch": {
|
|
109
|
+
if (!allowedAgents?.includes(String(args.agentId)) || !runtime.selfAid)
|
|
110
|
+
throw new Error("目标 Agent 未加入个人助理白名单");
|
|
111
|
+
const abilities = await management.listAgents();
|
|
112
|
+
const agent = abilities.find((item) => item.id === args.agentId && item.callable && item.aids.includes(String(args.aid)));
|
|
113
|
+
if (!agent)
|
|
114
|
+
throw new Error("目标 Agent/AID 当前不可派活");
|
|
115
|
+
const coordinator = (0, coordination_1.createCoordinator)({ selfAid: runtime.selfAid, ecPath: runtime.ecPath, allowedAgents: [{ id: agent.id, aids: agent.aids }] });
|
|
116
|
+
const result = await coordinator.dispatch({ agentId: agent.id, aid: String(args.aid), task: String(args.task) });
|
|
117
|
+
if (runtime.threads && runtime.threads.size >= 128)
|
|
118
|
+
runtime.threads.delete(runtime.threads.keys().next().value);
|
|
119
|
+
runtime.threads?.set(result.threadId, result.aid);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
case "coordination_status": {
|
|
123
|
+
if (!runtime.selfAid)
|
|
124
|
+
throw new Error("个人助理 AID 未配置");
|
|
125
|
+
if (runtime.threads?.get(String(args.threadId)) !== String(args.aid))
|
|
126
|
+
throw new Error("thread 不属于本个人助理已派发的任务");
|
|
127
|
+
const coordinator = (0, coordination_1.createCoordinator)({ selfAid: runtime.selfAid, ecPath: runtime.ecPath, allowedAgents: [] });
|
|
128
|
+
return coordinator.getStatus({ aid: String(args.aid), threadId: String(args.threadId) });
|
|
129
|
+
}
|
|
130
|
+
case "owner_context_read": {
|
|
131
|
+
if (!runtime.ownerContext)
|
|
132
|
+
throw new Error("Owner 共享上下文尚未配置");
|
|
133
|
+
return runtime.ownerContext.get();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function parseExplicitCommand(task) {
|
|
138
|
+
if (!task.startsWith("/assistant ") && !task.startsWith("/sessions"))
|
|
139
|
+
return undefined;
|
|
140
|
+
if (/[\r\n]/.test(task))
|
|
141
|
+
return { kind: "invalid" };
|
|
142
|
+
if (task.startsWith("/sessions"))
|
|
143
|
+
return /^\/sessions(?:\s|$)/.test(task) ? { kind: "sessions", command: task } : { kind: "invalid" };
|
|
144
|
+
if (task === "/assistant context refresh")
|
|
145
|
+
return { kind: "context-refresh" };
|
|
146
|
+
const agentCall = task.match(/^\/assistant agent call ([a-z0-9][a-z0-9-]{0,63}) ([\s\S]{1,20000})$/);
|
|
147
|
+
if (agentCall)
|
|
148
|
+
return { kind: "agent-call", agentId: agentCall[1], task: agentCall[2] };
|
|
149
|
+
const create = task.match(/^\/assistant aid create ([A-Za-z0-9][A-Za-z0-9._-]{2,255})(?: --owner ([A-Za-z0-9][A-Za-z0-9._-]{2,255}))?$/);
|
|
150
|
+
if (create)
|
|
151
|
+
return { kind: "create", aid: create[1], ...(create[2] ? { ownerAid: create[2] } : {}) };
|
|
152
|
+
const bind = task.match(/^\/assistant aid bind ([A-Za-z0-9][A-Za-z0-9._-]{2,255}) ([A-Za-z0-9][A-Za-z0-9._-]{0,127})( --replace)?$/);
|
|
153
|
+
if (bind)
|
|
154
|
+
return { kind: "bind", aid: bind[1], agentId: bind[2], replace: Boolean(bind[3]) };
|
|
155
|
+
return { kind: "invalid" };
|
|
156
|
+
}
|
|
157
|
+
function stringifyToolResult(value) {
|
|
158
|
+
let text;
|
|
159
|
+
try {
|
|
160
|
+
text = typeof value === "string" ? value : JSON.stringify(value);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
text = "工具返回结果不可序列化";
|
|
164
|
+
}
|
|
165
|
+
return text.length > 64_000 ? `${text.slice(0, 64_000)}\n[结果已截断]` : text;
|
|
166
|
+
}
|
|
@@ -127,19 +127,40 @@ class QwenCdpClient {
|
|
|
127
127
|
const reply = await this.direct(`这是后台健康检测。请只回复 ${token},不要添加任何其它文字。`, 45_000, undefined, token);
|
|
128
128
|
verifyQwenHeartbeatReply(token, reply);
|
|
129
129
|
}
|
|
130
|
-
async createSession(_name) {
|
|
130
|
+
async createSession(_name, forceNew = false, preserveDraft = false) {
|
|
131
131
|
await this.ensureVisible();
|
|
132
|
-
|
|
132
|
+
if (!preserveDraft)
|
|
133
|
+
await this.recoverDraft();
|
|
133
134
|
const snapshot = `(() => ({ url: location.href, messages: document.querySelectorAll('[data-message-id]').length, input: document.querySelector(${JSON.stringify(exports.inputSelector)})?.innerText ?? null, blank: Boolean(document.querySelector('.new-task-model-selector')) }))()`;
|
|
134
135
|
const before = await this.evaluate(snapshot);
|
|
135
136
|
if (before.input?.trim())
|
|
136
137
|
throw new Error("千问输入框含有未发送内容,不能新建会话");
|
|
137
|
-
if (before.input !== null && before.messages === 0 && before.blank)
|
|
138
|
-
return;
|
|
139
|
-
const clicked = await this.evaluate(`(() => { const button = document.querySelector('button[aria-label="新任务"]'); if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false; button.click(); return true; })()`);
|
|
138
|
+
if (!forceNew && before.input !== null && before.messages === 0 && before.blank)
|
|
139
|
+
return this.currentSessionId();
|
|
140
|
+
const clicked = await this.evaluate(`(() => { if (document.querySelector(${JSON.stringify(exports.inputSelector)})?.innerText.trim() || document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]')) throw new Error('千问当前有未发送内容或正在生成回复,不能新建会话'); const button = document.querySelector('button[aria-label="新任务"]'); if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false; button.click(); return true; })()`);
|
|
140
141
|
if (clicked !== true)
|
|
141
142
|
throw new Error("千问新任务入口未找到或不可用");
|
|
142
143
|
await this.waitFor(`(() => { const state = ${snapshot}; return state.input !== null && state.input.trim() === '' && state.messages === 0 && state.url !== ${JSON.stringify(before.url)}; })()`, "千问新会话状态校验失败");
|
|
144
|
+
return this.currentSessionId();
|
|
145
|
+
}
|
|
146
|
+
async currentSessionId() {
|
|
147
|
+
const href = String(await this.evaluate("location.href"));
|
|
148
|
+
try {
|
|
149
|
+
return new URL(href).searchParams.get("chat") ?? undefined;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async activateSession(sessionId) {
|
|
156
|
+
if (!sessionId.trim() || sessionId.length > 512 || /[\u0000-\u001f]/.test(sessionId))
|
|
157
|
+
throw new Error("千问模型会话标识无效");
|
|
158
|
+
await this.ensureVisible();
|
|
159
|
+
const target = new URL(String(await this.evaluate("location.href")));
|
|
160
|
+
target.searchParams.set("chat", sessionId);
|
|
161
|
+
if (target.href !== String(await this.evaluate("location.href")))
|
|
162
|
+
await this.evaluate(`(() => { if (document.querySelector(${JSON.stringify(exports.inputSelector)})?.innerText.trim() || document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]')) throw new Error('千问当前有未发送内容或正在生成回复,不能切换会话'); location.href = ${JSON.stringify(target.href)}; })()`);
|
|
163
|
+
await this.waitFor(`location.href === ${JSON.stringify(target.href)} && Boolean(document.querySelector(${JSON.stringify(exports.inputSelector)}))`, "千问个人助理模型会话恢复失败");
|
|
143
164
|
}
|
|
144
165
|
async connect() {
|
|
145
166
|
if (this.connected())
|
|
@@ -204,7 +225,7 @@ class QwenCdpClient {
|
|
|
204
225
|
async direct(text, timeoutMs = 240_000, signal, expectedReply) {
|
|
205
226
|
return (await this.complete(text, timeoutMs, signal, expectedReply)).text;
|
|
206
227
|
}
|
|
207
|
-
async complete(text, timeoutMs = 240_000, signal, expectedReply, attachments, onMessage) {
|
|
228
|
+
async complete(text, timeoutMs = 240_000, signal, expectedReply, attachments, onMessage, preserveDraft = false) {
|
|
208
229
|
let stage = "准备千问办公桌面任务";
|
|
209
230
|
let code = "QWEN_TASK_FAILED";
|
|
210
231
|
try {
|
|
@@ -215,7 +236,8 @@ class QwenCdpClient {
|
|
|
215
236
|
throw new Error("千问输入框有用户草稿,跳过后台健康检测");
|
|
216
237
|
}
|
|
217
238
|
await this.ensureVisible();
|
|
218
|
-
|
|
239
|
+
if (!preserveDraft)
|
|
240
|
+
await this.recoverDraft();
|
|
219
241
|
const ready = await this.evaluate(`(() => { const input = document.querySelector(${JSON.stringify(exports.inputSelector)}); return Boolean(input && !input.innerText.trim() && !document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]')); })()`);
|
|
220
242
|
if (!ready)
|
|
221
243
|
throw new Error("千问输入框含有未发送内容或正在生成回复,未发送新消息");
|
|
@@ -254,23 +276,37 @@ class QwenCdpClient {
|
|
|
254
276
|
code = "QWEN_REPLY_FAILED";
|
|
255
277
|
let previousReply = "";
|
|
256
278
|
let stablePolls = 0;
|
|
279
|
+
let seenGenerating = false;
|
|
280
|
+
let stoppedPolls = 0;
|
|
257
281
|
const forwarded = new Set();
|
|
258
282
|
const deadline = Date.now() + timeoutMs;
|
|
259
|
-
while (Date.now() < deadline) {
|
|
260
|
-
|
|
283
|
+
while (Date.now() < deadline || signal?.aborted) {
|
|
284
|
+
// 上游取消不停止桌面生成;确认结束前保留插件队列。
|
|
285
|
+
await wait(2_000);
|
|
261
286
|
const messagesJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.assistantMessageSelector)})].map(element => ({ id: element.getAttribute('data-message-id'), text: element.innerText })))`);
|
|
262
|
-
const
|
|
287
|
+
const messages = JSON.parse(messagesJson || "[]");
|
|
288
|
+
const fresh = freshAssistantMessages(beforeIds, messages);
|
|
263
289
|
const reply = fresh.at(-1)?.text.trim() ?? "";
|
|
264
290
|
stablePolls = reply && reply === previousReply ? stablePolls + 1 : 0;
|
|
265
291
|
previousReply = reply;
|
|
266
292
|
if (reply && stablePolls >= 3 && isQwenApiUnavailableReply(reply))
|
|
267
293
|
throw new Error("QWEN_REMOTE_ERROR:千问生成接口不可用;桌面仅返回通用错误,未提供服务端错误码;CDP 仍连接,未重启或重新提交任务");
|
|
268
294
|
const generating = await this.evaluate(`Boolean(document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]'))`);
|
|
295
|
+
seenGenerating ||= generating === true;
|
|
296
|
+
stoppedPolls = seenGenerating && generating === false ? stoppedPolls + 1 : 0;
|
|
297
|
+
if (signal?.aborted && !reply && stoppedPolls >= 2 && messages.some(message => message.id && !beforeIds.has(message.id))) {
|
|
298
|
+
const attachments = expectedReply ? [] : await this.readArtifacts(beforeIds, sessionUrl);
|
|
299
|
+
if (attachments.length)
|
|
300
|
+
return { text: "", sessionId: new URL(sessionUrl).searchParams.get("chat") ?? undefined, attachments };
|
|
301
|
+
code = "QWEN_REQUEST_CANCELLED";
|
|
302
|
+
throw new Error("请求已取消,已确认本轮桌面生成结束,未返回正文或附件");
|
|
303
|
+
}
|
|
269
304
|
if (!generating && (expectedReply ? isQwenHeartbeatReplyComplete(reply, expectedReply) && stablePolls >= 1 : reply && stablePolls >= 3)) {
|
|
270
305
|
stage = "读取千问办公已完成任务的产物(尚未上传 AUN FS)";
|
|
271
306
|
code = "QWEN_ARTIFACT_READ_FAILED";
|
|
272
307
|
const attachments = expectedReply ? [] : await this.readArtifacts(beforeIds, sessionUrl);
|
|
273
|
-
|
|
308
|
+
const sessionId = new URL(sessionUrl).searchParams.get("chat") ?? undefined;
|
|
309
|
+
return { text: reply, ...(sessionId ? { sessionId } : {}), ...(attachments.length ? { attachments } : {}) };
|
|
274
310
|
}
|
|
275
311
|
if (onMessage && generating && reply && stablePolls >= 1 && !forwarded.has(reply) && !isQwenApiUnavailableReply(reply)) {
|
|
276
312
|
await onMessage(reply);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QwenworkPlugin = exports.qwenworkManifest = void 0;
|
|
4
4
|
exports.createQwenworkPlugin = createQwenworkPlugin;
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
5
7
|
const agent_1 = require("../../agent");
|
|
6
8
|
const openai_adapter_1 = require("./openai-adapter");
|
|
7
9
|
const cdp_client_1 = require("./cdp-client");
|
|
@@ -10,21 +12,29 @@ const desktop_launch_1 = require("../desktop-launch");
|
|
|
10
12
|
const session_history_1 = require("./session-history");
|
|
11
13
|
exports.qwenworkManifest = {
|
|
12
14
|
id: "qwenwork",
|
|
13
|
-
version: "1.0.
|
|
15
|
+
version: "1.0.25",
|
|
14
16
|
displayName: "千问办公",
|
|
15
17
|
entry: "dist/index.js",
|
|
16
18
|
apiVersion: "1",
|
|
17
19
|
process: "in-process",
|
|
18
|
-
capabilities: ["chat", "openai-compatible", "health"],
|
|
20
|
+
capabilities: ["chat", "openai-compatible", "health", "model-provider"],
|
|
19
21
|
configSchema: "config.schema.json",
|
|
20
22
|
defaults: { cdpPort: 9228, httpPort: 17893 },
|
|
21
23
|
};
|
|
22
24
|
function asNumber(value, fallback) {
|
|
23
25
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
24
26
|
}
|
|
27
|
+
function validModelSessionId(value) {
|
|
28
|
+
return typeof value === "string" && Boolean(value.trim()) && value.length <= 512 && !/[\u0000-\u001f]/.test(value);
|
|
29
|
+
}
|
|
25
30
|
/** 千问插件由后台守护,所有通道共用请求队列和恢复流程。 */
|
|
26
31
|
class QwenworkPlugin {
|
|
27
32
|
manifest = exports.qwenworkManifest;
|
|
33
|
+
get modelProvider() {
|
|
34
|
+
if (this.relayTarget || !this.client?.complete || !this.client.createSession || !this.client.activateSession)
|
|
35
|
+
return undefined;
|
|
36
|
+
return { complete: (request) => this.completeModelProvider(request) };
|
|
37
|
+
}
|
|
28
38
|
sessionHistory;
|
|
29
39
|
get attachmentInput() { return Boolean(this.relayTarget || this.client?.attachmentInput); }
|
|
30
40
|
sessionOperations = [];
|
|
@@ -53,6 +63,8 @@ class QwenworkPlugin {
|
|
|
53
63
|
busy = false;
|
|
54
64
|
taskCount = 0;
|
|
55
65
|
stopped = true;
|
|
66
|
+
modelSessions = new Map();
|
|
67
|
+
modelSessionsPath;
|
|
56
68
|
constructor(options = {}) {
|
|
57
69
|
this.injectedClient = options.client;
|
|
58
70
|
this.configuredCdpPort = options.cdpPort;
|
|
@@ -68,6 +80,21 @@ class QwenworkPlugin {
|
|
|
68
80
|
this.lastHeartbeat = undefined;
|
|
69
81
|
this.context = context;
|
|
70
82
|
try {
|
|
83
|
+
this.modelSessions.clear();
|
|
84
|
+
this.modelSessionsPath = typeof context.dataDir === "string" && context.dataDir ? (0, node_path_1.join)(context.dataDir, "model-provider-sessions.json") : undefined;
|
|
85
|
+
try {
|
|
86
|
+
if (!this.modelSessionsPath)
|
|
87
|
+
throw Object.assign(new Error("无持久化目录"), { code: "ENOENT" });
|
|
88
|
+
const saved = JSON.parse(await (0, promises_1.readFile)(this.modelSessionsPath, "utf8"));
|
|
89
|
+
if (!saved || typeof saved !== "object" || Array.isArray(saved) || Object.entries(saved).some(([key, value]) => !validModelSessionId(key) || !validModelSessionId(value)))
|
|
90
|
+
throw new Error("千问个人助理模型会话记录无效");
|
|
91
|
+
for (const [key, value] of Object.entries(saved))
|
|
92
|
+
this.modelSessions.set(key, value);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code !== "ENOENT")
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
71
98
|
const config = context.config;
|
|
72
99
|
const relayTarget = config.relayTarget;
|
|
73
100
|
if (relayTarget !== undefined && relayTarget !== "dsh")
|
|
@@ -204,7 +231,7 @@ class QwenworkPlugin {
|
|
|
204
231
|
throw error;
|
|
205
232
|
}
|
|
206
233
|
}
|
|
207
|
-
async completeRequest(request) {
|
|
234
|
+
async completeRequest(request, modelRequest = false) {
|
|
208
235
|
if (this.relayTarget) {
|
|
209
236
|
return this.callPlugin(this.relayTarget, request);
|
|
210
237
|
}
|
|
@@ -217,8 +244,8 @@ class QwenworkPlugin {
|
|
|
217
244
|
throw new Error("千问当前客户端不支持附件输入");
|
|
218
245
|
const timeoutMs = request.timeoutMs ?? asNumber(this.context?.config.timeoutMs, 240_000);
|
|
219
246
|
const response = this.client.complete
|
|
220
|
-
? await this.client.complete(request.task, timeoutMs,
|
|
221
|
-
: { text: await this.complete(request.task, timeoutMs) };
|
|
247
|
+
? await this.client.complete(request.task, timeoutMs, request.signal, undefined, request.attachments, request.onMessage, modelRequest)
|
|
248
|
+
: { text: await this.complete(request.task, timeoutMs, request.signal) };
|
|
222
249
|
if ((0, cdp_client_1.isQwenApiUnavailableReply)(response.text))
|
|
223
250
|
throw new agent_1.AgentOperationError("QWEN_REMOTE_ERROR", "等待千问办公回复", new Error("千问生成接口不可用;桌面未提供具体服务端错误码,CDP 仍连接,未重启或重新提交任务"));
|
|
224
251
|
this.failures = 0;
|
|
@@ -227,12 +254,66 @@ class QwenworkPlugin {
|
|
|
227
254
|
return response;
|
|
228
255
|
}
|
|
229
256
|
catch (error) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
257
|
+
if (!request.signal?.aborted && !modelRequest) {
|
|
258
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
259
|
+
this.failures = this.client.connected() ? 0 : 2;
|
|
260
|
+
this.state = "degraded";
|
|
261
|
+
}
|
|
233
262
|
throw error;
|
|
234
263
|
}
|
|
235
264
|
}
|
|
265
|
+
async completeModelProvider(request) {
|
|
266
|
+
let stage = "千问办公模型排队(queue)";
|
|
267
|
+
try {
|
|
268
|
+
request.signal.throwIfAborted();
|
|
269
|
+
const run = this.enqueueTask(async () => {
|
|
270
|
+
request.signal.throwIfAborted();
|
|
271
|
+
stage = "连接千问办公模型(connect)";
|
|
272
|
+
const client = this.client;
|
|
273
|
+
if (this.state !== "ready" || !client?.connected() || !client.createSession || !client.activateSession || !client.complete)
|
|
274
|
+
throw new Error("千问办公未连接或不支持隔离模型会话,模型请求不会启动或重启桌面");
|
|
275
|
+
const previousSessionId = await client.currentSessionId?.();
|
|
276
|
+
try {
|
|
277
|
+
stage = "准备千问办公模型会话(submit)";
|
|
278
|
+
const desktopSessionId = this.modelSessions.get(request.sessionId);
|
|
279
|
+
if (desktopSessionId)
|
|
280
|
+
await client.activateSession(desktopSessionId);
|
|
281
|
+
else
|
|
282
|
+
await client.createSession(undefined, true, true);
|
|
283
|
+
request.signal.throwIfAborted();
|
|
284
|
+
stage = "等待千问办公模型生成(generate)";
|
|
285
|
+
const response = await this.completeRequest({ task: request.prompt, sessionId: request.sessionId, timeoutMs: request.timeoutMs, signal: request.signal }, true);
|
|
286
|
+
const actualSessionId = response.sessionId ?? await client.currentSessionId?.();
|
|
287
|
+
if (!validModelSessionId(actualSessionId))
|
|
288
|
+
throw new Error("千问未返回可恢复的个人助理模型会话标识");
|
|
289
|
+
if (actualSessionId !== desktopSessionId) {
|
|
290
|
+
this.modelSessions.set(request.sessionId, actualSessionId);
|
|
291
|
+
const path = this.modelSessionsPath;
|
|
292
|
+
if (path) {
|
|
293
|
+
await (0, promises_1.mkdir)(this.context.dataDir ?? ".", { recursive: true });
|
|
294
|
+
await (0, promises_1.writeFile)(`${path}.tmp`, JSON.stringify(Object.fromEntries(this.modelSessions)), { mode: 0o600 });
|
|
295
|
+
await (0, promises_1.rename)(`${path}.tmp`, path);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { text: response.text };
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
if (client.connected()) {
|
|
302
|
+
if (previousSessionId)
|
|
303
|
+
await client.activateSession(previousSessionId);
|
|
304
|
+
else
|
|
305
|
+
await client.createSession(undefined, false, true);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return await (0, agent_1.abortable)(run, request.signal);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (error instanceof agent_1.AgentOperationError)
|
|
313
|
+
throw error;
|
|
314
|
+
throw new agent_1.AgentOperationError("QWEN_MODEL_FAILED", stage, error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
236
317
|
registerRoutes(http) {
|
|
237
318
|
if (!this.adapter)
|
|
238
319
|
return;
|
|
@@ -46,11 +46,12 @@ class WorkbuddyCdpClient {
|
|
|
46
46
|
const value = await this.evaluate(`([...document.querySelectorAll('[data-conversation-id]')].find(item => String(item.className).includes('_selected_')))?.getAttribute('data-conversation-id')`);
|
|
47
47
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
48
48
|
}
|
|
49
|
-
async activateConversation(conversationId) {
|
|
49
|
+
async activateConversation(conversationId, preserveDraft = false) {
|
|
50
50
|
const id = conversationId.trim();
|
|
51
51
|
if (!id)
|
|
52
52
|
throw new Error("WorkBuddy 会话标识不能为空");
|
|
53
53
|
const clicked = await this.evaluate(`(() => {
|
|
54
|
+
if (${preserveDraft}) { const editor = document.querySelector(${JSON.stringify(exports.editorSelector)})?.cloneNode(true); editor?.querySelectorAll('[data-slate-placeholder="true"],[data-slate-zero-width]').forEach(item => item.remove()); if (editor?.textContent?.replace(/[\\uFEFF\\u200B]/g, '').trim() || document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"],[aria-busy="true"]') || document.querySelector(${JSON.stringify(interactionOptionSelector)})) throw new Error('WorkBuddy 当前有用户草稿、待确认操作或正在生成回复,模型请求未切换会话'); }
|
|
54
55
|
const button = [...document.querySelectorAll('[data-conversation-id]')]
|
|
55
56
|
.filter(element => element.getAttribute('data-conversation-id') === ${JSON.stringify(id)})
|
|
56
57
|
.map(item => item.matches('[role="button"],button') ? item : item.querySelector('[role="button"],button') || item.closest('[role="button"],button'))
|
|
@@ -93,6 +94,8 @@ class WorkbuddyCdpClient {
|
|
|
93
94
|
return await this.evaluate(`(() => ({
|
|
94
95
|
conversationId: [...document.querySelectorAll('[data-conversation-id]')].find(item => String(item.className).includes('_selected_'))?.getAttribute('data-conversation-id') || undefined,
|
|
95
96
|
waiting: Boolean(document.querySelector(${JSON.stringify(interactionOptionSelector)})),
|
|
97
|
+
generating: Boolean(document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"],[aria-busy="true"]')),
|
|
98
|
+
draft: (() => { const text = document.querySelector(${JSON.stringify(exports.editorSelector)})?.cloneNode(true); text?.querySelectorAll('[data-slate-placeholder="true"],[data-slate-zero-width]').forEach(item => item.remove()); return Boolean(text?.textContent?.replace(/[\\uFEFF\\u200B]/g, '').trim()); })(),
|
|
96
99
|
}))()`);
|
|
97
100
|
}
|
|
98
101
|
async connect() {
|
|
@@ -248,7 +251,7 @@ class WorkbuddyCdpClient {
|
|
|
248
251
|
if (!await this.evaluate(`(() => { const button = document.querySelector('button[aria-label="发送"]'); if (!button || button.disabled) return false; button.click(); return true; })()`))
|
|
249
252
|
throw new Error("WorkBuddy 发送按钮不可用");
|
|
250
253
|
stage = "等待 WorkBuddy 回复(任务已提交,桌面未被停止)";
|
|
251
|
-
const response = await this.waitForReply(beforeIds, timeoutMs, undefined, undefined, request.sessionId, request.onMessage);
|
|
254
|
+
const response = await this.waitForReply(beforeIds, timeoutMs, undefined, undefined, request.sessionId, request.onMessage, request.signal);
|
|
252
255
|
if (this.pendingInteraction && temporaryPaths.length)
|
|
253
256
|
this.pendingInteractionCleanup = temporaryPaths;
|
|
254
257
|
return response;
|
|
@@ -314,12 +317,12 @@ class WorkbuddyCdpClient {
|
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
319
|
}
|
|
317
|
-
async waitForReply(beforeIds, timeoutMs, changedId, initialText, sessionId, onMessage) {
|
|
320
|
+
async waitForReply(beforeIds, timeoutMs, changedId, initialText, sessionId, onMessage, signal) {
|
|
318
321
|
const deadline = Date.now() + timeoutMs;
|
|
319
322
|
const previousText = new Map();
|
|
320
323
|
const stablePolls = new Map();
|
|
321
324
|
const forwarded = new Set();
|
|
322
|
-
while (Date.now() < deadline) {
|
|
325
|
+
while (Date.now() < deadline || signal?.aborted) {
|
|
323
326
|
await sleep(1_000);
|
|
324
327
|
const current = JSON.parse(String(await this.evaluate(`JSON.stringify([...document.querySelectorAll('.cr-agent[data-message-id]')].map(element => { const content = element.querySelector('[data-assistant-content]'); let text = content?.innerText?.trim() || ''; for (const thought of content?.querySelectorAll?.('.cr-reasoning') || []) text = text.replace(thought.innerText || '', ''); text = text.trim(); const finished = Boolean(element.querySelector('[data-testid="conversation-finished-footer"]')); const stopped = Boolean(text) && !document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"],[aria-busy="true"]'); return { id: element.getAttribute('data-message-id'), text, finished: finished, stopped: stopped }; }))`)));
|
|
325
328
|
const interaction = await this.readInteraction();
|
|
@@ -353,6 +356,8 @@ class WorkbuddyCdpClient {
|
|
|
353
356
|
return { text: reply ?? "", attachments };
|
|
354
357
|
if (reply || forwarded.size)
|
|
355
358
|
return { text: reply };
|
|
359
|
+
if (signal?.aborted && latest.finished)
|
|
360
|
+
throw new agent_1.AgentOperationError("WORKBUDDY_REQUEST_CANCELLED", "等待 WorkBuddy 取消后的任务结束", new Error("请求已取消,已确认本轮桌面生成结束,未返回正文或附件"));
|
|
356
361
|
}
|
|
357
362
|
if (onMessage) {
|
|
358
363
|
for (const item of candidates) {
|