mu-agent-bridge 1.0.71 → 1.0.78

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 (195) hide show
  1. package/README.md +17 -10
  2. package/dist/src-ts/agent.js +35 -0
  3. package/dist/src-ts/channel-sessions.js +3 -2
  4. package/dist/src-ts/cli.js +67 -37
  5. package/dist/src-ts/control-server.js +12 -30
  6. package/dist/src-ts/core-updater.js +141 -33
  7. package/dist/src-ts/daemon-process.js +2 -1
  8. package/dist/src-ts/dsh-agent.js +31 -13
  9. package/dist/src-ts/http-agent.js +36 -0
  10. package/dist/src-ts/plugin-catalog.js +11 -2
  11. package/dist/src-ts/plugin-manager.js +351 -50
  12. package/dist/src-ts/plugins/abweb/index.js +1 -1
  13. package/dist/src-ts/plugins/abweb/public/app.js +73 -5
  14. package/dist/src-ts/plugins/abweb/public/index.html +15 -0
  15. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  16. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  17. package/dist/src-ts/plugins/aun/index.js +155 -21
  18. package/dist/src-ts/plugins/aun/messages.js +4 -1
  19. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  20. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  21. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  22. package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  23. package/dist/src-ts/plugins/chatgpt/index.js +80 -19
  24. package/dist/src-ts/plugins/claude-cli/index.js +19 -2
  25. package/dist/src-ts/plugins/codex-cli/index.js +19 -2
  26. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  27. package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  28. package/dist/src-ts/plugins/doubao/index.js +152 -8
  29. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  30. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  31. package/dist/src-ts/plugins/dsh/index.js +172 -21
  32. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  33. package/dist/src-ts/plugins/feishu/index.js +1 -1
  34. package/dist/src-ts/plugins/local-cli/agent.js +74 -9
  35. package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  36. package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  37. package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  38. package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  39. package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  40. package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  41. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  42. package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  43. package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  44. package/dist/src-ts/plugins/qwenwork/index.js +139 -24
  45. package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  46. package/dist/src-ts/plugins/workbuddy/index.js +124 -41
  47. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  48. package/dist/src-ts/plugins/zcode/index.js +97 -0
  49. package/dist/src-ts/session-management.js +132 -14
  50. package/dist/src-ts/version-manager.js +6 -3
  51. package/dist/tests-ts/abweb-cli.test.js +10 -2
  52. package/dist/tests-ts/abweb-model-config.test.js +73 -0
  53. package/dist/tests-ts/agent.test.js +27 -0
  54. package/dist/tests-ts/aun-attachments.test.js +19 -17
  55. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  56. package/dist/tests-ts/aun-messages.test.js +20 -0
  57. package/dist/tests-ts/aun-plugin.test.js +292 -19
  58. package/dist/tests-ts/bundled-package.test.js +12 -2
  59. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  60. package/dist/tests-ts/channel-sessions.test.js +27 -0
  61. package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
  62. package/dist/tests-ts/control-server.test.js +56 -0
  63. package/dist/tests-ts/core-updater.test.js +215 -46
  64. package/dist/tests-ts/daemon-process.test.js +126 -1
  65. package/dist/tests-ts/desktop-launch.test.js +73 -0
  66. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  67. package/dist/tests-ts/doubao-plugin.test.js +773 -12
  68. package/dist/tests-ts/dsh-agent.test.js +157 -8
  69. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  70. package/dist/tests-ts/dsh-history.test.js +37 -0
  71. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  72. package/dist/tests-ts/http-agent.test.js +18 -0
  73. package/dist/tests-ts/installer-assets.test.js +1 -1
  74. package/dist/tests-ts/local-cli.test.js +146 -1
  75. package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
  76. package/dist/tests-ts/native-session-management.test.js +8 -7
  77. package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
  78. package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
  79. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  80. package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
  81. package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
  82. package/dist/tests-ts/personal-assistant.test.js +663 -0
  83. package/dist/tests-ts/platform-integration.test.js +183 -1
  84. package/dist/tests-ts/plugin-catalog.test.js +12 -2
  85. package/dist/tests-ts/plugin-manager.test.js +356 -4
  86. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  87. package/dist/tests-ts/publish-packages.test.js +42 -0
  88. package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
  89. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  90. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  91. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  92. package/dist/tests-ts/session-management.test.js +159 -4
  93. package/dist/tests-ts/version-manager.test.js +40 -0
  94. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  95. package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
  96. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  97. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  98. package/package.json +1 -1
  99. package/plugins/abweb/dist/src-ts/agent.js +35 -0
  100. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  101. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
  102. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
  103. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
  104. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  105. package/plugins/abweb/package.json +1 -1
  106. package/plugins/abweb/plugin.json +1 -1
  107. package/plugins/aun/config.schema.json +1 -1
  108. package/plugins/aun/dist/src-ts/agent.js +35 -0
  109. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  110. package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
  111. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
  112. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  113. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  114. package/plugins/aun/package.json +1 -1
  115. package/plugins/aun/plugin.json +1 -1
  116. package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
  117. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  118. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  119. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
  120. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  121. package/plugins/chatgpt/package.json +1 -1
  122. package/plugins/chatgpt/plugin.json +1 -1
  123. package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
  124. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
  125. package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  126. package/plugins/claude-cli/package.json +1 -1
  127. package/plugins/claude-cli/plugin.json +3 -2
  128. package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
  129. package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
  130. package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  131. package/plugins/codex-cli/package.json +1 -1
  132. package/plugins/codex-cli/plugin.json +3 -2
  133. package/plugins/doubao/config.schema.json +2 -1
  134. package/plugins/doubao/dist/src-ts/agent.js +35 -0
  135. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  136. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  137. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
  138. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  139. package/plugins/doubao/package.json +1 -1
  140. package/plugins/doubao/plugin.json +3 -2
  141. package/plugins/dsh/config.schema.json +5 -2
  142. package/plugins/dsh/dist/src-ts/agent.js +35 -0
  143. package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
  144. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  145. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  146. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
  147. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  148. package/plugins/dsh/package.json +1 -1
  149. package/plugins/dsh/plugin.json +3 -2
  150. package/plugins/feishu/dist/src-ts/agent.js +35 -0
  151. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  152. package/plugins/feishu/package.json +1 -1
  153. package/plugins/feishu/plugin.json +1 -1
  154. package/plugins/personal-assistant/config.schema.json +23 -0
  155. package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
  156. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  157. package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
  158. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  159. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  160. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  161. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  162. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  163. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  164. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  165. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  166. package/plugins/personal-assistant/package.json +39 -0
  167. package/plugins/personal-assistant/plugin.json +15 -0
  168. package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
  169. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  170. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  171. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  172. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
  173. package/plugins/qwenwork/package.json +1 -1
  174. package/plugins/qwenwork/plugin.json +3 -2
  175. package/plugins/workbuddy/config.schema.json +1 -1
  176. package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
  177. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  178. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  179. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
  180. package/plugins/workbuddy/package.json +1 -1
  181. package/plugins/workbuddy/plugin.json +3 -2
  182. package/plugins/zcode/config.schema.json +13 -0
  183. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  184. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  185. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  186. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  187. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  188. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  189. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  190. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  191. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  192. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  193. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  194. package/plugins/zcode/package.json +16 -0
  195. package/plugins/zcode/plugin.json +11 -0
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DshCdpClient = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_path_1 = require("node:path");
7
+ const node_os_1 = require("node:os");
8
+ const attachments_1 = require("../../attachments");
9
+ const dsh_agent_1 = require("../../dsh-agent");
10
+ const stateExpression = `JSON.stringify((() => {
11
+ const content = document.querySelector('[data-conversation-content][data-conversation-session]');
12
+ const editor = document.querySelector('[data-composer-input][contenteditable="true"]');
13
+ const transcript = [...(content?.querySelectorAll('[data-chat-flow-kind="user"], [data-chat-flow-kind="assistant-step"]:not([data-chat-group-part="reasoning"])') || [])].map(node => node.getAttribute('data-chat-flow-kind') === 'user' ? node.querySelector('[data-slot="conversation.user.text.node"]')?.innerText.trim() || '' : node.innerText.trim()).filter(Boolean).join('\\n\\n');
14
+ const labels = button => [button.getAttribute('aria-label'), button.title, button.innerText].filter(Boolean).join(' ').trim();
15
+ const busy = [...document.querySelectorAll('button,[role="button"]')].some(button => /停止|stop|interrupt/i.test(labels(button)) && !button.disabled && button.getAttribute('aria-disabled') !== 'true');
16
+ const pending = document.querySelector('[data-composer-seat] [role="group"][aria-label*="Pending attachments"], [data-composer-seat] [role="group"][aria-label*="待发送附件"]');
17
+ const pendingAttachments = pending ? [...pending.querySelectorAll('[title],[alt]')].map(item => item.getAttribute('title') || item.getAttribute('alt')).filter(Boolean) : [];
18
+ return {
19
+ sessionId: content?.getAttribute('data-conversation-session') || '',
20
+ editorText: (editor?.innerText || '').replace(/[\\u200b\\ufeff]/g, '').trim(),
21
+ conversationText: transcript,
22
+ busy,
23
+ turnCount: content?.querySelectorAll('[data-turn-tail]').length || 0,
24
+ blank: content?.getAttribute('data-content-phase') === 'hero',
25
+ pendingAttachments,
26
+ };
27
+ })())`;
28
+ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
29
+ function normalize(value) { return value.replace(/[\u200b\ufeff]/g, "").replace(/\s+/g, " ").trim(); }
30
+ class DshCdpClient {
31
+ options;
32
+ port;
33
+ timeoutMs;
34
+ connectionTimeoutMs;
35
+ fetchImpl;
36
+ webSocketFactory;
37
+ socket;
38
+ sequence = 0;
39
+ pending = new Map();
40
+ constructor(options = {}) {
41
+ this.options = options;
42
+ this.port = options.port ?? 9226;
43
+ this.timeoutMs = options.timeoutMs ?? 240_000;
44
+ this.connectionTimeoutMs = options.connectionTimeoutMs ?? 10_000;
45
+ this.fetchImpl = options.fetchImpl ?? fetch;
46
+ this.webSocketFactory = options.webSocketFactory ?? (url => new WebSocket(url));
47
+ }
48
+ connected() { return this.socket?.readyState === 1; }
49
+ async connect() {
50
+ if (this.connected())
51
+ return;
52
+ this.close();
53
+ const response = await this.fetchImpl(`http://127.0.0.1:${this.port}/json`, { signal: AbortSignal.timeout(this.connectionTimeoutMs) });
54
+ if (!response.ok)
55
+ throw new Error(`DSH CDP /json HTTP ${response.status}`);
56
+ const targets = await response.json();
57
+ const page = targets.find(item => item.type === "page" && item.url?.startsWith("dsh-app://app/") && item.webSocketDebuggerUrl);
58
+ if (!page?.webSocketDebuggerUrl)
59
+ throw new Error(`未找到 DSH 桌面会话页(CDP ${this.port})`);
60
+ const socket = this.webSocketFactory(page.webSocketDebuggerUrl);
61
+ this.socket = socket;
62
+ let opened = false;
63
+ try {
64
+ await new Promise((resolve, reject) => {
65
+ const timer = setTimeout(() => reject(new Error("DSH CDP WebSocket 连接超时")), this.connectionTimeoutMs);
66
+ socket.onopen = () => { opened = true; clearTimeout(timer); resolve(); };
67
+ socket.onerror = () => { clearTimeout(timer); reject(new Error("DSH CDP WebSocket 连接失败")); };
68
+ socket.onclose = () => {
69
+ if (this.socket === socket)
70
+ this.socket = undefined;
71
+ this.rejectPending(new Error("DSH CDP WebSocket 已关闭"));
72
+ if (!opened) {
73
+ clearTimeout(timer);
74
+ reject(new Error("DSH CDP WebSocket 已关闭"));
75
+ }
76
+ };
77
+ socket.onerror = () => {
78
+ this.rejectPending(new Error("DSH CDP WebSocket 连接错误"));
79
+ if (!opened) {
80
+ clearTimeout(timer);
81
+ reject(new Error("DSH CDP WebSocket 连接失败"));
82
+ }
83
+ };
84
+ socket.onmessage = event => this.onMessage(String(event.data));
85
+ });
86
+ await this.cdp("Runtime.enable");
87
+ await this.waitForState(state => Boolean(state.sessionId), "DSH 桌面会话输入框未就绪", this.connectionTimeoutMs);
88
+ }
89
+ catch (error) {
90
+ this.close();
91
+ throw error;
92
+ }
93
+ }
94
+ async activeSession() { return (await this.state()).sessionId; }
95
+ async createSession() {
96
+ const before = await this.state();
97
+ this.assertIdle(before, "新建会话");
98
+ if (before.blank && before.turnCount === 0)
99
+ return before.sessionId;
100
+ const clicked = await this.evaluate(`(() => {
101
+ const labels = /^(?:新建会话|新会话|new session|new chat)$/i;
102
+ const button = [...document.querySelectorAll('button,[role="button"]')].find(item => {
103
+ const label = [item.getAttribute('aria-label'), item.title, item.innerText].filter(Boolean).join(' ').trim();
104
+ return labels.test(label) && !item.disabled && item.getAttribute('aria-disabled') !== 'true';
105
+ });
106
+ if (!button) return false;
107
+ button.click(); return true;
108
+ })()`);
109
+ if (clicked !== true)
110
+ throw new Error("DSH 桌面未找到可用的新建会话入口");
111
+ const state = await this.waitForState(value => value.sessionId !== before.sessionId && value.blank && !value.busy && !value.editorText && value.turnCount === 0 && !value.pendingAttachments.length, "DSH 新会话状态未确认");
112
+ if (!state.sessionId)
113
+ throw new Error("DSH 未返回真实新会话标识");
114
+ return state.sessionId;
115
+ }
116
+ async activateSession(sessionId) {
117
+ if (!sessionId.trim())
118
+ throw new Error("DSH 会话标识不能为空");
119
+ const before = await this.state();
120
+ if (before.sessionId === sessionId)
121
+ return;
122
+ this.assertIdle(before, "切换会话");
123
+ const clicked = await this.evaluate(`(() => {
124
+ const id = ${JSON.stringify(sessionId)};
125
+ const item = [...document.querySelectorAll('[data-row-key],[data-session-id],[data-session],[data-conversation-session]')]
126
+ .find(node => node.getAttribute('data-row-key') === 'session:' + id
127
+ || ['data-session-id', 'data-session', 'data-conversation-session'].some(key => node.getAttribute(key) === id));
128
+ const button = item?.matches('button,[role="button"],[role="treeitem"],a') ? item : item?.closest('button,[role="button"],[role="treeitem"],a');
129
+ if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false;
130
+ button.click(); return true;
131
+ })()`);
132
+ if (clicked !== true)
133
+ throw new Error(`DSH 桌面未找到可切换的会话 ${sessionId},未发送消息`);
134
+ await this.waitForState(state => state.sessionId === sessionId, "DSH 会话切换未确认,未发送消息");
135
+ }
136
+ async complete(request) {
137
+ if (!request.task.trim())
138
+ throw new Error("DSH 消息内容不能为空");
139
+ (0, attachments_1.validateAttachments)(request.attachments);
140
+ const before = await this.state();
141
+ if (!before.sessionId)
142
+ throw new Error("DSH 当前没有可用会话");
143
+ this.assertIdle(before, "发送消息");
144
+ const deliveryBefore = request.attachmentOutput ? await this.readDeliveryState(before.sessionId) : undefined;
145
+ const workDirectory = request.attachments?.length ? await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-dsh-cdp-")) : undefined;
146
+ let outputDirectory;
147
+ let outputCwd;
148
+ let failed = true;
149
+ try {
150
+ if (deliveryBefore) {
151
+ if (!(0, node_path_1.isAbsolute)(deliveryBefore.cwd))
152
+ throw new Error("DSH 工作目录不是绝对路径,无法安全回传文件");
153
+ const cwd = await (0, promises_1.realpath)(deliveryBefore.cwd);
154
+ const info = await (0, promises_1.lstat)(cwd);
155
+ if (!info.isDirectory() || info.isSymbolicLink())
156
+ throw new Error("DSH 工作目录不是受信任的目录");
157
+ outputCwd = cwd;
158
+ outputDirectory = (0, node_path_1.join)(cwd, `.agent-bridge-dsh-${(0, node_crypto_1.randomUUID)()}`);
159
+ await (0, promises_1.mkdir)(outputDirectory, { mode: 0o700 });
160
+ const outputRelative = (0, node_path_1.relative)(cwd, await (0, promises_1.realpath)(outputDirectory));
161
+ if (outputRelative === ".." || outputRelative.startsWith(`..${node_path_1.sep}`) || (0, node_path_1.isAbsolute)(outputRelative))
162
+ throw new Error("DSH 回传目录超出工作目录");
163
+ }
164
+ const filenames = [];
165
+ if (request.attachments?.length && workDirectory) {
166
+ const paths = [];
167
+ for (const [index, item] of request.attachments.entries()) {
168
+ const filename = (0, attachments_1.attachmentFilename)(item.filename);
169
+ filenames.push(filename);
170
+ const directory = (0, node_path_1.join)(workDirectory, String(index));
171
+ await (0, promises_1.mkdir)(directory, { mode: 0o700 });
172
+ const path = (0, node_path_1.join)(directory, filename);
173
+ await (0, promises_1.writeFile)(path, item.data, { mode: 0o600, flag: "wx" });
174
+ paths.push(path);
175
+ }
176
+ await this.setFiles(paths);
177
+ await this.waitForState(state => filenames.every(filename => state.pendingAttachments.includes(filename)), "DSH 未确认收到上传附件");
178
+ }
179
+ let task = request.task;
180
+ if (filenames.length)
181
+ task += `\n\n请读取本条已上传附件:${filenames.join("、")}。`;
182
+ if (outputDirectory)
183
+ task += `\n\n如需回传文件,将文件写入 ${outputDirectory} 并调用 present 声明。`;
184
+ await this.evaluate(`(() => { const editor = document.querySelector('[data-composer-input][contenteditable="true"]'); if (!editor || editor.innerText.trim()) return false; editor.focus(); return document.activeElement === editor; })()`);
185
+ await this.cdp("Input.insertText", { text: task });
186
+ const entered = await this.state();
187
+ if (normalize(entered.editorText) !== normalize(task))
188
+ throw new Error("DSH 输入框内容校验失败,未发送消息");
189
+ const turnCount = entered.turnCount;
190
+ await this.cdp("Input.dispatchKeyEvent", { type: "keyDown", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 });
191
+ await this.cdp("Input.dispatchKeyEvent", { type: "keyUp", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 });
192
+ const startedAt = Date.now();
193
+ let stable = 0;
194
+ let previous = "";
195
+ let observed = "";
196
+ while (Date.now() - startedAt < (request.timeoutMs ?? this.timeoutMs)) {
197
+ await sleep(600);
198
+ const state = await this.state();
199
+ if (state.sessionId !== before.sessionId)
200
+ throw new Error("DSH 发送后会话发生变化,结果未确认且未重发");
201
+ if (state.conversationText.includes(task))
202
+ observed = state.conversationText;
203
+ const signature = `${state.turnCount}:${state.busy}:${state.conversationText}`;
204
+ stable = !state.busy && state.turnCount > turnCount && signature === previous ? stable + 1 : 0;
205
+ previous = signature;
206
+ if (stable >= 2 && observed) {
207
+ const userIndex = observed.lastIndexOf(task);
208
+ const reply = userIndex >= 0 ? observed.slice(userIndex + task.length).trim() : "";
209
+ const presented = request.attachmentOutput && deliveryBefore
210
+ ? (await this.readDeliveryState(before.sessionId)).presented.filter(item => item.sequence > deliveryBefore.sequence).flatMap(item => item.paths)
211
+ : [];
212
+ const attachments = outputDirectory && presented.length
213
+ ? await (0, dsh_agent_1.readPresentedFiles)(outputDirectory, presented, outputCwd, request.attachments ?? [])
214
+ : [];
215
+ if (!reply && !attachments.length)
216
+ throw new Error("DSH 本轮历史中没有可确认的回复内容");
217
+ failed = false;
218
+ return { text: reply, ...(attachments.length ? { attachments } : {}) };
219
+ }
220
+ }
221
+ throw new Error(`DSH 桌面回复超时;任务已提交但未确认完成,未重发(会话 ${before.sessionId})`);
222
+ }
223
+ finally {
224
+ if (workDirectory)
225
+ await (0, promises_1.rm)(workDirectory, { recursive: true, force: true });
226
+ if (outputDirectory) {
227
+ try {
228
+ await (0, promises_1.rm)(outputDirectory, { recursive: true, force: true });
229
+ }
230
+ catch (error) {
231
+ if (!failed)
232
+ throw error;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ close() {
238
+ const socket = this.socket;
239
+ this.socket = undefined;
240
+ socket?.close();
241
+ this.rejectPending(new Error("DSH CDP 已关闭"));
242
+ }
243
+ async setFiles(files) {
244
+ const document = await this.cdp("DOM.getDocument", { depth: -1, pierce: true });
245
+ const result = await this.cdp("DOM.querySelector", { nodeId: document.root.nodeId, selector: 'input[type="file"]' });
246
+ if (!result.nodeId)
247
+ throw new Error("DSH 桌面原生附件输入控件未找到");
248
+ await this.cdp("DOM.setFileInputFiles", { nodeId: result.nodeId, files });
249
+ }
250
+ assertIdle(state, action) {
251
+ if (state.busy)
252
+ throw new Error(`DSH 正在生成回复,不能${action}`);
253
+ if (state.editorText || state.pendingAttachments.length)
254
+ throw new Error(`DSH 输入框有未发送内容或附件,不能${action}`);
255
+ }
256
+ async state() {
257
+ const value = await this.evaluate(stateExpression);
258
+ if (typeof value !== "string")
259
+ throw new Error("DSH 桌面状态响应无效");
260
+ const state = JSON.parse(value);
261
+ if (!state || typeof state.sessionId !== "string" || typeof state.editorText !== "string" || typeof state.conversationText !== "string" || typeof state.busy !== "boolean" || !Number.isInteger(state.turnCount) || typeof state.blank !== "boolean" || !Array.isArray(state.pendingAttachments))
262
+ throw new Error("DSH 桌面页面结构不符合预期");
263
+ return state;
264
+ }
265
+ async waitForState(predicate, message, timeoutMs = this.timeoutMs) {
266
+ const deadline = Date.now() + timeoutMs;
267
+ let lastError;
268
+ while (Date.now() < deadline) {
269
+ try {
270
+ const state = await this.state();
271
+ if (predicate(state))
272
+ return state;
273
+ }
274
+ catch (error) {
275
+ lastError = error;
276
+ }
277
+ await sleep(250);
278
+ }
279
+ throw new Error(message, lastError ? { cause: lastError } : undefined);
280
+ }
281
+ async evaluate(expression) {
282
+ const result = await this.cdp("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true, userGesture: true });
283
+ if (result.exceptionDetails)
284
+ throw new Error(`DSH CDP 页面执行失败:${result.exceptionDetails.text ?? result.exceptionDetails.exception?.description ?? "未知错误"}`);
285
+ return result.result?.value;
286
+ }
287
+ async cdp(method, params = {}) {
288
+ const socket = this.socket;
289
+ if (!socket || socket.readyState !== 1)
290
+ throw new Error("DSH CDP 未连接");
291
+ const id = ++this.sequence;
292
+ const promise = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
293
+ socket.send(JSON.stringify({ id, method, params }));
294
+ let timer;
295
+ try {
296
+ const timeout = new Promise((_, reject) => { timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`DSH CDP ${method} 超时`)); }, this.connectionTimeoutMs); });
297
+ const response = await Promise.race([promise, timeout]);
298
+ if (response.error)
299
+ throw new Error(response.error.message ?? `DSH CDP ${method} 失败`);
300
+ return response.result ?? {};
301
+ }
302
+ finally {
303
+ if (timer)
304
+ clearTimeout(timer);
305
+ }
306
+ }
307
+ onMessage(raw) {
308
+ let message;
309
+ try {
310
+ message = JSON.parse(raw);
311
+ }
312
+ catch {
313
+ return;
314
+ }
315
+ if (typeof message.id !== "number")
316
+ return;
317
+ const pending = this.pending.get(message.id);
318
+ if (!pending)
319
+ return;
320
+ this.pending.delete(message.id);
321
+ pending.resolve(message);
322
+ }
323
+ rejectPending(error) {
324
+ for (const pending of this.pending.values())
325
+ pending.reject(error);
326
+ this.pending.clear();
327
+ }
328
+ async readDeliveryState(sessionId) {
329
+ if (!this.options.readDeliveryState)
330
+ throw new Error("DSH 附件回传需要可读取的持久化会话日志");
331
+ return this.options.readDeliveryState(sessionId);
332
+ }
333
+ }
334
+ exports.DshCdpClient = DshCdpClient;
@@ -1,45 +1,94 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DshPlugin = exports.dshManifest = void 0;
4
+ const promises_1 = require("node:fs/promises");
4
5
  const node_path_1 = require("node:path");
6
+ const agent_1 = require("../../agent");
5
7
  const dsh_agent_1 = require("../../dsh-agent");
6
8
  const dsh_paths_1 = require("../../dsh-paths");
9
+ const plugin_1 = require("../../plugin");
7
10
  const openai_adapter_1 = require("../qwenwork/openai-adapter");
8
11
  const session_history_1 = require("./session-history");
12
+ const cdp_client_1 = require("./cdp-client");
9
13
  exports.dshManifest = {
10
14
  id: "dsh",
11
- version: "1.0.11",
15
+ version: "1.0.15",
12
16
  displayName: "DeepSeek Harness",
13
17
  entry: "dist/index.js",
14
18
  apiVersion: "1",
15
19
  process: "in-process",
16
- capabilities: ["chat", "health"],
20
+ capabilities: ["chat", "health", "model-provider"],
17
21
  configSchema: "config.schema.json",
18
22
  };
19
23
  class DshPlugin {
24
+ options;
20
25
  manifest = exports.dshManifest;
26
+ modelProvider = { complete: (request) => this.completeModelProvider(request) };
21
27
  attachmentInput = true;
22
28
  sessionOperations = ["new"];
23
29
  sessionHistory;
30
+ client;
24
31
  agent;
32
+ modelConfig = {};
33
+ modelSessions = new Map();
34
+ modelSessionsPath;
25
35
  status = "failed";
26
36
  message = "插件尚未启动";
27
37
  adapter;
28
38
  activeRequests = 0;
39
+ port = 9226;
40
+ queue = Promise.resolve();
41
+ pendingSessions = new Set();
29
42
  unregister = [];
43
+ constructor(options = {}) {
44
+ this.options = options;
45
+ }
30
46
  async activate(context) {
31
- const configured = context.config.patchPath;
32
- const patchPath = typeof configured === "string" && configured.trim()
33
- ? configured
34
- : (0, node_path_1.resolve)(__dirname, "..", "..", "headless-account.patch.yml");
35
- const paths = await (0, dsh_paths_1.resolveDshPaths)(patchPath, typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
36
- this.agent = new dsh_agent_1.DshAgent({ patchPath, paths });
37
- context.logger?.info?.("DSH 客户端路径已确认", { executablePath: paths.executable });
47
+ this.modelConfig = context.config;
48
+ this.modelSessions.clear();
49
+ this.modelSessionsPath = context.dataDir ? (0, node_path_1.join)(context.dataDir, "model-provider-sessions.json") : undefined;
50
+ if (this.modelSessionsPath) {
51
+ try {
52
+ const saved = JSON.parse(await (0, promises_1.readFile)(this.modelSessionsPath, "utf8"));
53
+ const valid = (value) => typeof value === "string" && value.trim() && value.length <= 512 && !/[\u0000-\u001f]/.test(value);
54
+ if (!saved || typeof saved !== "object" || Array.isArray(saved) || Object.entries(saved).some(([key, value]) => !valid(key) || !valid(value)))
55
+ throw new Error("DSH 个人助理模型会话记录无效");
56
+ for (const [key, value] of Object.entries(saved))
57
+ this.modelSessions.set(key, value);
58
+ }
59
+ catch (error) {
60
+ if (error.code !== "ENOENT")
61
+ throw error;
62
+ }
63
+ }
64
+ this.port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "dsh", "DSH_CDP_PORT", 9226);
65
+ const sessionsRoot = typeof context.config.sessionsRoot === "string" ? context.config.sessionsRoot : undefined;
38
66
  this.sessionHistory = new session_history_1.DshSessionHistoryProvider({
39
- sessionsRoot: typeof context.config.sessionsRoot === "string" ? context.config.sessionsRoot : undefined,
67
+ ...(sessionsRoot ? { sessionsRoot } : {}),
40
68
  unavailableReason: () => this.status !== "ready" ? "DSH 插件尚未就绪" : this.activeRequests ? "DSH 正在处理任务,暂不读取持久化历史" : "",
41
69
  });
42
- this.adapter = new openai_adapter_1.OpenAIAdapter((prompt) => this.handle({ task: prompt, timeoutMs: 240_000 }).then((result) => result.text), { model: "dsh", prefix: "/dsh/v1", onExchange: (direction, text, peer, agentAid) => context.logger?.info?.("DSH 通信", { direction, text, peer, agentAid }) });
70
+ this.client = this.options.client ?? new cdp_client_1.DshCdpClient({
71
+ port: this.port,
72
+ timeoutMs: typeof context.config.timeoutMs === "number" && context.config.timeoutMs > 0 ? context.config.timeoutMs : undefined,
73
+ connectionTimeoutMs: typeof context.config.startupTimeoutMs === "number" && context.config.startupTimeoutMs > 0 ? context.config.startupTimeoutMs : undefined,
74
+ readDeliveryState: sessionId => this.sessionHistory.readDeliveryState(sessionId),
75
+ });
76
+ this.adapter = new openai_adapter_1.OpenAIAdapter((prompt) => this.handle({ task: prompt, timeoutMs: 240_000 }).then(result => result.text), {
77
+ model: "dsh", prefix: "/dsh/v1",
78
+ onExchange: (direction, text, peer, agentAid) => context.logger?.info?.("DSH 通信", { direction, text, peer, agentAid }),
79
+ });
80
+ try {
81
+ await this.client.connect();
82
+ this.status = "ready";
83
+ this.message = "";
84
+ }
85
+ catch (error) {
86
+ this.status = "failed";
87
+ this.message = error instanceof Error ? error.message : String(error);
88
+ context.logger?.error?.(this.message);
89
+ this.client.close();
90
+ throw error;
91
+ }
43
92
  if (context.http) {
44
93
  for (const [method, path] of [["GET", "/dsh/v1/models"], ["POST", "/dsh/v1/chat/completions"], ["OPTIONS", "/dsh/v1/chat/completions"]]) {
45
94
  const dispose = context.http.register({ method, path, handler: (req, res) => this.adapter.handle(req, res) });
@@ -47,34 +96,136 @@ class DshPlugin {
47
96
  this.unregister.push(dispose);
48
97
  }
49
98
  }
50
- this.status = "ready";
51
- this.message = "";
52
- context.logger?.info?.("dsh plugin activated");
99
+ context.logger?.info?.("DSH 桌面 CDP 已连接", { port: this.port });
53
100
  }
54
101
  async deactivate() {
102
+ this.status = "failed";
103
+ this.message = "插件正在停止";
104
+ await this.queue;
55
105
  for (const dispose of this.unregister.splice(0))
56
106
  dispose();
57
107
  await this.adapter?.close();
58
108
  this.adapter = undefined;
109
+ this.client?.close();
110
+ this.client = undefined;
59
111
  this.agent = undefined;
60
112
  this.sessionHistory = undefined;
113
+ this.pendingSessions.clear();
61
114
  this.status = "failed";
62
115
  this.message = "插件已停止";
63
116
  }
64
117
  async health() {
65
- return { status: this.status, ...(this.message ? { message: this.message } : {}), details: { adapter: "DshAgent" } };
118
+ const connected = this.client?.connected() === true;
119
+ return {
120
+ status: connected ? "ready" : this.status === "failed" ? "failed" : "degraded",
121
+ ...(!connected ? { message: this.message || "DSH CDP 已断开" } : {}),
122
+ details: { transport: "cdp", port: this.port, connected },
123
+ };
124
+ }
125
+ async controlSession(request) {
126
+ if (request.operation !== "new")
127
+ throw new Error(`DSH 不支持会话操作:${String(request.operation)}`);
128
+ if (!request.sessionId?.trim())
129
+ throw new Error("DSH 新会话缺少 sessionId");
130
+ return this.enqueue(async () => {
131
+ this.assertConnected();
132
+ const sessionId = await this.client.createSession();
133
+ this.pendingSessions.add(sessionId);
134
+ return { text: "DSH 已创建新会话", sessionId };
135
+ });
66
136
  }
67
137
  async handle(request) {
68
- if (!this.agent || this.status !== "ready")
69
- throw new Error("dsh 插件未就绪");
70
- this.activeRequests++;
138
+ return this.enqueue(async () => {
139
+ this.assertConnected();
140
+ this.activeRequests++;
141
+ try {
142
+ const current = await this.client.activeSession();
143
+ let sessionId = request.sessionId?.trim();
144
+ if (request.newSession) {
145
+ if (sessionId && (this.pendingSessions.has(sessionId) || current === sessionId)) {
146
+ if (current !== sessionId)
147
+ await this.client.activateSession(sessionId);
148
+ this.pendingSessions.delete(sessionId);
149
+ }
150
+ else {
151
+ sessionId = await this.client.createSession();
152
+ }
153
+ }
154
+ else if (sessionId) {
155
+ if (current !== sessionId)
156
+ await this.client.activateSession(sessionId);
157
+ }
158
+ else {
159
+ sessionId = await this.client.createSession();
160
+ }
161
+ const response = await this.client.complete(request);
162
+ return { ...response, sessionId };
163
+ }
164
+ finally {
165
+ this.activeRequests--;
166
+ }
167
+ });
168
+ }
169
+ async completeModelProvider(request) {
170
+ let stage = "DSH 模型排队(queue)";
71
171
  try {
72
- return await this.agent.send(request);
172
+ request.signal.throwIfAborted();
173
+ const run = this.queue.then(async () => {
174
+ request.signal.throwIfAborted();
175
+ stage = "连接 DSH 模型(connect)";
176
+ if (this.status !== "ready")
177
+ throw new Error("DSH 插件未就绪");
178
+ if (!this.agent) {
179
+ const configured = this.modelConfig.patchPath;
180
+ const patchPath = typeof configured === "string" && configured.trim() ? configured : (0, node_path_1.resolve)(__dirname, "..", "..", "headless-account.patch.yml");
181
+ const paths = await (0, dsh_paths_1.resolveDshPaths)(patchPath, typeof this.modelConfig.executablePath === "string" ? this.modelConfig.executablePath : undefined);
182
+ this.agent = new dsh_agent_1.DshAgent({ patchPath, paths });
183
+ }
184
+ this.activeRequests++;
185
+ try {
186
+ const sessionId = this.modelSessions.get(request.sessionId);
187
+ stage = "等待 DSH 模型生成(generate)";
188
+ const response = await this.agent.send({
189
+ task: request.prompt,
190
+ timeoutMs: request.timeoutMs,
191
+ signal: request.signal,
192
+ ...(sessionId ? { sessionId } : { newSession: true }),
193
+ }, true);
194
+ if (!sessionId) {
195
+ if (!response.sessionId)
196
+ throw new Error("DSH 未返回个人助理模型会话标识");
197
+ const next = new Map(this.modelSessions).set(request.sessionId, response.sessionId);
198
+ if (this.modelSessionsPath) {
199
+ await (0, promises_1.mkdir)((0, node_path_1.resolve)(this.modelSessionsPath, ".."), { recursive: true });
200
+ await (0, promises_1.writeFile)(`${this.modelSessionsPath}.tmp`, JSON.stringify(Object.fromEntries(next)), { mode: 0o600 });
201
+ await (0, promises_1.rename)(`${this.modelSessionsPath}.tmp`, this.modelSessionsPath);
202
+ }
203
+ this.modelSessions.set(request.sessionId, response.sessionId);
204
+ }
205
+ return { text: response.text };
206
+ }
207
+ finally {
208
+ this.activeRequests--;
209
+ }
210
+ });
211
+ this.queue = run.then(() => undefined, () => undefined);
212
+ return await (0, agent_1.abortable)(run, request.signal);
73
213
  }
74
- finally {
75
- this.activeRequests--;
214
+ catch (error) {
215
+ if (error instanceof agent_1.AgentOperationError)
216
+ throw error;
217
+ throw new agent_1.AgentOperationError("DSH_MODEL_FAILED", stage, error);
76
218
  }
77
219
  }
220
+ enqueue(work) {
221
+ const run = this.queue.then(work);
222
+ this.queue = run.then(() => undefined, () => undefined);
223
+ return run;
224
+ }
225
+ assertConnected() {
226
+ if (!this.client || this.status !== "ready" || !this.client.connected())
227
+ throw new Error(this.message || `DSH CDP ${this.port} 未连接`);
228
+ }
78
229
  }
79
230
  exports.DshPlugin = DshPlugin;
80
231
  exports.default = DshPlugin;