mu-agent-bridge 1.0.73 → 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 (152) hide show
  1. package/README.md +12 -8
  2. package/dist/src-ts/cli.js +67 -37
  3. package/dist/src-ts/control-server.js +7 -28
  4. package/dist/src-ts/core-updater.js +141 -33
  5. package/dist/src-ts/daemon-process.js +2 -1
  6. package/dist/src-ts/dsh-agent.js +7 -2
  7. package/dist/src-ts/http-agent.js +36 -0
  8. package/dist/src-ts/plugin-manager.js +185 -58
  9. package/dist/src-ts/plugins/abweb/index.js +1 -1
  10. package/dist/src-ts/plugins/abweb/public/app.js +8 -6
  11. package/dist/src-ts/plugins/abweb/public/index.html +3 -3
  12. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  13. package/dist/src-ts/plugins/aun/index.js +89 -11
  14. package/dist/src-ts/plugins/aun/messages.js +1 -1
  15. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  16. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  17. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  18. package/dist/src-ts/plugins/chatgpt/index.js +3 -2
  19. package/dist/src-ts/plugins/claude-cli/index.js +1 -1
  20. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  21. package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  22. package/dist/src-ts/plugins/doubao/index.js +119 -7
  23. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  24. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  25. package/dist/src-ts/plugins/dsh/index.js +102 -23
  26. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  27. package/dist/src-ts/plugins/feishu/index.js +1 -1
  28. package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  29. package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  30. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  31. package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  32. package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  33. package/dist/src-ts/plugins/qwenwork/index.js +52 -18
  34. package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  35. package/dist/src-ts/plugins/workbuddy/index.js +24 -12
  36. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  37. package/dist/src-ts/plugins/zcode/index.js +97 -0
  38. package/dist/src-ts/session-management.js +27 -8
  39. package/dist/src-ts/version-manager.js +6 -3
  40. package/dist/tests-ts/abweb-model-config.test.js +44 -41
  41. package/dist/tests-ts/aun-attachments.test.js +19 -17
  42. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  43. package/dist/tests-ts/aun-messages.test.js +14 -0
  44. package/dist/tests-ts/aun-plugin.test.js +207 -19
  45. package/dist/tests-ts/bundled-package.test.js +1 -1
  46. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  47. package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
  48. package/dist/tests-ts/control-server.test.js +20 -0
  49. package/dist/tests-ts/core-updater.test.js +215 -46
  50. package/dist/tests-ts/daemon-process.test.js +126 -1
  51. package/dist/tests-ts/desktop-launch.test.js +73 -0
  52. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  53. package/dist/tests-ts/doubao-plugin.test.js +688 -19
  54. package/dist/tests-ts/dsh-agent.test.js +91 -19
  55. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  56. package/dist/tests-ts/dsh-history.test.js +37 -0
  57. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  58. package/dist/tests-ts/http-agent.test.js +18 -0
  59. package/dist/tests-ts/local-cli.test.js +29 -2
  60. package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
  61. package/dist/tests-ts/native-session-management.test.js +8 -7
  62. package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
  63. package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
  64. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  65. package/dist/tests-ts/personal-assistant.test.js +277 -3
  66. package/dist/tests-ts/platform-integration.test.js +60 -1
  67. package/dist/tests-ts/plugin-manager.test.js +78 -6
  68. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  69. package/dist/tests-ts/publish-packages.test.js +1 -1
  70. package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
  71. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  72. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  73. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  74. package/dist/tests-ts/session-management.test.js +8 -8
  75. package/dist/tests-ts/version-manager.test.js +40 -0
  76. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  77. package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
  78. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  79. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  80. package/package.json +1 -1
  81. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  82. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
  83. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
  84. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
  85. package/plugins/abweb/package.json +1 -1
  86. package/plugins/abweb/plugin.json +1 -1
  87. package/plugins/aun/config.schema.json +1 -1
  88. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  89. package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
  90. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
  91. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  92. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  93. package/plugins/aun/package.json +1 -1
  94. package/plugins/aun/plugin.json +1 -1
  95. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  96. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
  97. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  98. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
  99. package/plugins/claude-cli/package.json +1 -1
  100. package/plugins/claude-cli/plugin.json +1 -1
  101. package/plugins/doubao/config.schema.json +2 -1
  102. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  103. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  104. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
  105. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  106. package/plugins/doubao/package.json +1 -1
  107. package/plugins/doubao/plugin.json +1 -1
  108. package/plugins/dsh/config.schema.json +5 -2
  109. package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
  110. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  111. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  112. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
  113. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  114. package/plugins/dsh/package.json +1 -1
  115. package/plugins/dsh/plugin.json +1 -1
  116. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  117. package/plugins/feishu/package.json +1 -1
  118. package/plugins/feishu/plugin.json +1 -1
  119. package/plugins/personal-assistant/config.schema.json +3 -2
  120. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  121. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  122. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  123. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  124. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  125. package/plugins/personal-assistant/package.json +2 -2
  126. package/plugins/personal-assistant/plugin.json +1 -1
  127. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  128. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  129. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  130. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
  131. package/plugins/qwenwork/package.json +1 -1
  132. package/plugins/qwenwork/plugin.json +1 -1
  133. package/plugins/workbuddy/config.schema.json +1 -1
  134. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  135. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  136. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
  137. package/plugins/workbuddy/package.json +1 -1
  138. package/plugins/workbuddy/plugin.json +1 -1
  139. package/plugins/zcode/config.schema.json +13 -0
  140. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  141. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  142. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  143. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  144. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  145. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  146. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  147. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  148. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  149. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  150. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  151. package/plugins/zcode/package.json +16 -0
  152. package/plugins/zcode/plugin.json +11 -0
@@ -59,6 +59,87 @@ const node_test_1 = __importDefault(require("node:test"));
59
59
  await strict_1.default.rejects(() => client.createTask(), /尚未登录.*桌面端登录/);
60
60
  strict_1.default.equal(calls, 1);
61
61
  });
62
+ (0, node_test_1.default)("WorkBuddy 恢复会话等待输入组件切换,不只检查侧栏选中", async () => {
63
+ const client = new cdp_client_1.WorkbuddyCdpClient();
64
+ let clicked = false;
65
+ let checks = 0;
66
+ const button = { className: "_selected_", matches: () => true, getAttribute: () => "saved", click() { clicked = true; } };
67
+ const editor = { __reactFiberTest: { memoizedProps: { get viewConversationId() { return checks >= 3 ? "saved" : "previous"; } } } };
68
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: { querySelectorAll: () => [button], querySelector: () => editor } });
69
+ client.waitFor = async (expression) => {
70
+ strict_1.default.equal(clicked, true);
71
+ for (checks = 1; checks <= 3; checks++)
72
+ if (await client.evaluate(expression))
73
+ break;
74
+ };
75
+ await client.activateConversation("saved");
76
+ strict_1.default.equal(checks, 3);
77
+ });
78
+ (0, node_test_1.default)("WorkBuddy 旧版输入组件没有会话属性时仍能恢复侧栏任务", async () => {
79
+ const client = new cdp_client_1.WorkbuddyCdpClient();
80
+ const button = { className: "_selected_", matches: () => true, getAttribute: () => "saved", click() { } };
81
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: { querySelectorAll: () => [button], querySelector: () => ({}) } });
82
+ client.waitFor = async (expression) => { strict_1.default.equal(await client.evaluate(expression), true); };
83
+ await client.activateConversation("saved");
84
+ });
85
+ (0, node_test_1.default)("WorkBuddy 预期会话与侧栏或输入组件不符时拒绝输入", async () => {
86
+ for (const [expected, selected, input] of [["saved", "other", "saved"], ["saved", "saved", "other"], [null, "other", "other"]]) {
87
+ const client = new cdp_client_1.WorkbuddyCdpClient();
88
+ let inputs = 0;
89
+ const editor = { __reactFiberTest: { memoizedProps: { viewConversationId: input } } };
90
+ client.evaluate = async (expression) => expression.includes("_selected_")
91
+ ? (0, node_vm_1.runInNewContext)(expression, { document: { querySelectorAll: () => [{ className: "_selected_", getAttribute: () => selected }], querySelector: () => editor } })
92
+ : expression.includes("JSON.stringify") ? "[]" : true;
93
+ client.cdp = async () => { inputs++; };
94
+ client.readInteraction = async () => undefined;
95
+ client.waitForReply = async () => ({ text: "不应发送" });
96
+ await strict_1.default.rejects(client.complete({ task: "在线不?", sessionId: "logical" }, undefined, false, expected), /桌面任务|会话/);
97
+ strict_1.default.equal(inputs, 0);
98
+ }
99
+ });
100
+ (0, node_test_1.default)("WorkBuddy 输入后会话切换时不点击发送", async () => {
101
+ const client = new cdp_client_1.WorkbuddyCdpClient();
102
+ let selected = "saved";
103
+ let clicks = 0;
104
+ const editor = { innerText: "在线不?", focus() { }, __reactFiberTest: { memoizedProps: { get viewConversationId() { return selected; } } } };
105
+ const button = { disabled: false, click() { clicks++; } };
106
+ client.evaluate = async (expression) => {
107
+ if (expression.includes("_selected_") || expression.includes('button[aria-label="发送"]'))
108
+ return (0, node_vm_1.runInNewContext)(expression, { window: { getSelection() { } }, document: {
109
+ querySelectorAll: () => [{ className: "_selected_", getAttribute: () => selected }],
110
+ querySelector: (selector) => selector.includes("contenteditable") ? editor : button,
111
+ createRange: () => ({ selectNodeContents() { } }),
112
+ } });
113
+ return expression.includes("JSON.stringify") ? "[]" : true;
114
+ };
115
+ client.cdp = async (method) => { if (method === "Input.insertText")
116
+ selected = "other"; };
117
+ client.readInteraction = async () => undefined;
118
+ client.waitForReply = async () => ({ text: "不应发送" });
119
+ await strict_1.default.rejects(client.complete({ task: "在线不?", sessionId: "logical" }, undefined, false, "saved"), /桌面任务|会话|未发送/);
120
+ strict_1.default.equal(clicks, 0);
121
+ });
122
+ (0, node_test_1.default)("WorkBuddy 续接选项点击前再次核对桌面会话", async () => {
123
+ const client = new cdp_client_1.WorkbuddyCdpClient();
124
+ let selected = "saved";
125
+ let clicks = 0;
126
+ const editor = { __reactFiberTest: { memoizedProps: { get viewConversationId() { return selected; } } } };
127
+ const option = { disabled: false, getAttribute: () => null, click() { clicks++; } };
128
+ client.pendingInteraction = { prompt: "确认?", options: ["确认"], sessionId: "logical" };
129
+ client.evaluate = async (expression) => {
130
+ if (expression.includes("option.click()"))
131
+ selected = "other";
132
+ if (expression.includes("_selected_") || expression.includes("option.click()"))
133
+ return (0, node_vm_1.runInNewContext)(expression, { document: {
134
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => selected }] : [option],
135
+ querySelector: () => editor,
136
+ } });
137
+ return "[]";
138
+ };
139
+ client.waitForReply = async () => ({ text: "不应确认" });
140
+ await strict_1.default.rejects(client.complete({ task: "1", sessionId: "logical" }, undefined, false, "saved"), /桌面任务|会话|未确认/);
141
+ strict_1.default.equal(clicks, 0);
142
+ });
62
143
  const node_vm_1 = require("node:vm");
63
144
  const workbuddy_1 = require("../src-ts/plugins/workbuddy");
64
145
  const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
@@ -385,7 +466,7 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
385
466
  strict_1.default.deepEqual(fetched, ['file:///C:/movie.bin', 'file:///C:/image.png']);
386
467
  strict_1.default.equal(cancelled, true);
387
468
  });
388
- (0, node_test_1.default)("workbuddy 默认允许长任务等待一小时并遵守显式超时", async () => {
469
+ (0, node_test_1.default)("workbuddy 最多等待二十分钟并遵守较短的显式超时", async () => {
389
470
  const timeouts = [];
390
471
  const plugin = new workbuddy_1.WorkbuddyPlugin({ client: {
391
472
  connect: async () => undefined, close() { }, connected: () => true,
@@ -395,7 +476,8 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
395
476
  try {
396
477
  await plugin.handle({ task: '生成视频' });
397
478
  await plugin.handle({ task: '继续', timeoutMs: 1234 });
398
- strict_1.default.deepEqual(timeouts, [3_600_000, 1234]);
479
+ await plugin.handle({ task: '旧版长等待', timeoutMs: 3_600_000 });
480
+ strict_1.default.deepEqual(timeouts, [1_200_000, 1234, 1_200_000]);
399
481
  }
400
482
  finally {
401
483
  await plugin.deactivate('stop');
@@ -507,6 +589,118 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
507
589
  strict_1.default.deepEqual(await client.complete({ task: "继续", timeoutMs: 2_200 }), { text: "视频已停止" });
508
590
  strict_1.default.ok(polls >= 2);
509
591
  });
592
+ (0, node_test_1.default)("WorkBuddy 隐藏正文回退读取 textContent 且排除隐藏思考区", async () => {
593
+ const client = new cdp_client_1.WorkbuddyCdpClient();
594
+ const content = { innerText: "", textContent: "内部推理已完成 10s在线。有事直接说。", querySelectorAll: () => [{ innerText: "", textContent: "内部推理" }, { innerText: "", textContent: "已完成 10s" }] };
595
+ const message = { getAttribute: () => "reply", querySelector: (selector) => selector === "[data-assistant-content]" ? content : {} };
596
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: { querySelectorAll: () => [message], querySelector: () => null } });
597
+ client.readInteraction = async () => undefined;
598
+ client.readArtifacts = async () => [];
599
+ strict_1.default.deepEqual(await client.waitForReply(new Set(), 1200), { text: "在线。有事直接说。" });
600
+ });
601
+ (0, node_test_1.default)("WorkBuddy 等待期间会话切换立即拒绝其他任务的正文和产物", async () => {
602
+ const client = new cdp_client_1.WorkbuddyCdpClient();
603
+ const forwarded = [];
604
+ let artifacts = 0;
605
+ client.conversationId = async () => "other";
606
+ const message = { getAttribute: () => "foreign", querySelector: (selector) => selector === "[data-assistant-content]" ? { innerText: "其他任务结果" } : {} };
607
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: {
608
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => "other" }] : [message],
609
+ querySelector: () => ({}),
610
+ } });
611
+ client.readInteraction = async () => undefined;
612
+ client.readArtifacts = async () => { artifacts++; return []; };
613
+ await strict_1.default.rejects(client.waitForReply(new Set(), 2000, undefined, undefined, "logical", async (text) => { forwarded.push(text); }, undefined, "saved"), (error) => error.code === "WORKBUDDY_CONVERSATION_CHANGED");
614
+ strict_1.default.deepEqual(forwarded, []);
615
+ strict_1.default.equal(artifacts, 0);
616
+ });
617
+ (0, node_test_1.default)("WorkBuddy 缺少新任务标识时不能把首次选中的旧会话当成本次任务", async () => {
618
+ const client = new cdp_client_1.WorkbuddyCdpClient();
619
+ client.conversationId = async () => "other";
620
+ const message = { getAttribute: () => "foreign", querySelector: (selector) => selector === "[data-assistant-content]" ? { innerText: "其他任务结果" } : {} };
621
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: {
622
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => "other" }] : [message],
623
+ querySelector: () => ({}),
624
+ } });
625
+ client.readInteraction = async () => undefined;
626
+ client.readArtifacts = async () => [];
627
+ await strict_1.default.rejects(client.waitForReply(new Set(), 1200, undefined, undefined, "logical", undefined, undefined, null), (error) => error.code === "WORKBUDDY_CONVERSATION_CHANGED");
628
+ });
629
+ for (const [mode, label] of [["created", "等待侧栏及输入组件同步后绑定新 ID"], ["navigation", "提交后立即点击旧任务时拒绝绑定"], ["timeout", "分配 ID 超时后清理监听"], ["changed", "输入组件就绪前再切换时拒绝绑定"]]) {
630
+ (0, node_test_1.default)(`WorkBuddy 首页${label}`, async () => {
631
+ const client = new cdp_client_1.WorkbuddyCdpClient({ timeoutMs: 150 });
632
+ let selected = null;
633
+ let input = null;
634
+ const listeners = new Map();
635
+ const bound = [];
636
+ const editor = { innerText: "", focus() { }, __reactFiberTest: { memoizedProps: { get viewConversationId() { return input; } } } };
637
+ const button = { disabled: false, click() {
638
+ listeners.get("click")?.();
639
+ if (mode === "created" || mode === "changed")
640
+ selected = "created";
641
+ if (mode === "created")
642
+ setTimeout(() => { input = "created"; }, 0);
643
+ if (mode === "navigation")
644
+ setTimeout(() => { listeners.get("click")?.(); selected = input = "other"; }, 0);
645
+ if (mode === "changed")
646
+ setTimeout(() => { selected = input = "other"; }, 75);
647
+ } };
648
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { setTimeout, clearTimeout, setInterval, clearInterval, window: { getSelection() { } }, document: {
649
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? ["other", ...(selected === "created" ? ["created"] : [])].map(id => ({ className: selected === id ? "_selected_" : "", getAttribute: () => id })) : [],
650
+ querySelector: (selector) => selector.includes("contenteditable") ? editor : selector.includes('button[aria-label="发送"]') ? button : null,
651
+ createRange: () => ({ selectNodeContents() { } }),
652
+ addEventListener: (name, callback) => { listeners.set(name, callback); },
653
+ removeEventListener: (name) => { listeners.delete(name); },
654
+ } });
655
+ client.cdp = async (method, params) => { if (method === "Input.insertText")
656
+ editor.innerText = params.text; };
657
+ client.waitForReply = async (...args) => { bound.push(args[7]); return { text: "已完成" }; };
658
+ const response = client.complete({ task: "在线不?", sessionId: "logical" }, undefined, false, null);
659
+ if (mode === "created") {
660
+ strict_1.default.deepEqual(await response, { text: "已完成" });
661
+ strict_1.default.deepEqual(bound, ["created"]);
662
+ }
663
+ else {
664
+ await strict_1.default.rejects(response, /会话|任务标识/);
665
+ strict_1.default.deepEqual(bound, []);
666
+ }
667
+ strict_1.default.equal(listeners.size, 0);
668
+ });
669
+ }
670
+ (0, node_test_1.default)("WorkBuddy 正文快照之后切换会话也不能读取其他任务的确认选项", async () => {
671
+ const client = new cdp_client_1.WorkbuddyCdpClient();
672
+ let selected = "saved";
673
+ const editor = { __reactFiberTest: { memoizedProps: { get viewConversationId() { return selected; } } } };
674
+ const message = { getAttribute: () => "reply", querySelector: (selector) => selector === "[data-assistant-content]" ? { innerText: "本轮正文" } : {} };
675
+ const option = { innerText: "其他会话选项", closest: () => ({ innerText: "其他会话确认?", getAttribute: () => "foreign", querySelector: () => ({ innerText: "其他会话正文" }) }) };
676
+ client.evaluate = async (expression) => {
677
+ if (expression.includes("conversation-input-area"))
678
+ selected = "other";
679
+ return (0, node_vm_1.runInNewContext)(expression, { document: {
680
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => selected }] : selector === ".cr-agent[data-message-id]" ? [message] : [option],
681
+ querySelector: (selector) => selector.includes("contenteditable") ? editor : null,
682
+ } });
683
+ };
684
+ client.readArtifacts = async () => [];
685
+ await strict_1.default.rejects(client.waitForReply(new Set(), 1200, undefined, undefined, "logical", undefined, undefined, "saved"), (error) => error.code === "WORKBUDDY_CONVERSATION_CHANGED");
686
+ strict_1.default.equal(client.pendingInteraction, undefined);
687
+ });
688
+ (0, node_test_1.default)("WorkBuddy 产物读取前及异步读取期间切换会话均拒绝成功回传", async () => {
689
+ for (const initial of ["other", "saved"]) {
690
+ const client = new cdp_client_1.WorkbuddyCdpClient();
691
+ let selected = initial;
692
+ const editor = { __reactFiberTest: { memoizedProps: { get viewConversationId() { return selected; } } } };
693
+ const message = { getAttribute: () => "reply", querySelector: () => null, querySelectorAll: () => [{ getAttribute: () => "file:///result.txt" }] };
694
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { URL, btoa, fetch: async () => {
695
+ selected = "other";
696
+ return new Response("结果", { headers: { "content-type": "text/plain" } });
697
+ }, document: {
698
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => selected }] : [message],
699
+ querySelector: () => editor,
700
+ } });
701
+ await strict_1.default.rejects(client.readArtifacts(new Set(["reply"]), "saved"), (error) => error.code === "WORKBUDDY_CONVERSATION_CHANGED");
702
+ }
703
+ });
510
704
  (0, node_test_1.default)("workbuddy CDP 选择后仍在处理时不能把工具状态当成最终回复", async () => {
511
705
  const client = new cdp_client_1.WorkbuddyCdpClient();
512
706
  let polls = 0;
@@ -1069,6 +1263,127 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1069
1263
  strict_1.default.equal(selected, false);
1070
1264
  strict_1.default.equal(messages, 0);
1071
1265
  });
1266
+ (0, node_test_1.default)("workbuddy CDP 新建时立即分配会话 ID 后允许发送首条消息", async () => {
1267
+ let conversationId = "old-conversation";
1268
+ let messages = 1;
1269
+ const editor = { innerText: "", focus() { }, querySelector: () => null, __reactFiberTest: { memoizedProps: { get viewConversationId() { return conversationId; } } }, cloneNode: () => ({ textContent: "", querySelectorAll: () => [{ remove() { } }] }) };
1270
+ const createButton = { textContent: "新建任务", disabled: false, getAttribute: () => null, click: () => { conversationId = "new-conversation"; messages = 0; } };
1271
+ const sendButton = { disabled: false, click() { } };
1272
+ const document = {
1273
+ querySelectorAll: (selector) => selector === '.cr-agent[data-message-id]' ? Array(messages).fill({}) : selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => conversationId }] : selector.includes("conversation-input-area") ? [] : [createButton],
1274
+ querySelector: (selector) => selector.includes("contenteditable") ? editor : selector.includes('button[aria-label="发送"]') ? sendButton : null,
1275
+ createRange: () => ({ selectNodeContents() { } }),
1276
+ };
1277
+ const client = new cdp_client_1.WorkbuddyCdpClient({ timeoutMs: 500 });
1278
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document, location: { href: "app://workbuddy/task/old" }, window: { getSelection() { } } });
1279
+ client.cdp = async (_method, params) => { if (params.text)
1280
+ editor.innerText = params.text; };
1281
+ let bound;
1282
+ client.waitForReply = async (...args) => { bound = args[7]; return { text: "已完成" }; };
1283
+ client.connected = () => true;
1284
+ client.connect = async () => undefined;
1285
+ const plugin = new workbuddy_1.WorkbuddyPlugin({ client });
1286
+ await plugin.activate({ config: { transport: "cdp", dailyPoints: false }, logger: { info() { }, warn() { }, error() { } } });
1287
+ strict_1.default.equal((await plugin.handle({ task: "在线不?", sessionId: "logical" })).text, "已完成");
1288
+ strict_1.default.equal(bound, "new-conversation");
1289
+ });
1290
+ for (const mode of ["同步", "微任务", "异步", "切换", "超时", "目录中切换", "搜索延迟", "搜索回退", "搜索失败"]) {
1291
+ (0, node_test_1.default)(`WorkBuddy 新建逻辑会话后专家召唤${mode}分配 ID 时校验专家会话`, async () => {
1292
+ const client = new cdp_client_1.WorkbuddyCdpClient({ timeoutMs: 220 });
1293
+ let selected = "old-conversation";
1294
+ let editorId = selected;
1295
+ let messages = 1;
1296
+ let sentTo;
1297
+ let catalogOpen = false;
1298
+ let summoned = false;
1299
+ let searchReady = mode !== "搜索延迟" && mode !== "搜索回退" && mode !== "搜索失败";
1300
+ const listeners = new Map();
1301
+ const editor = { innerText: "", focus() { }, querySelector: () => null, cloneNode: () => ({ textContent: "", querySelectorAll: () => [] }), __reactFiberTest: { memoizedProps: { get viewConversationId() { return editorId; } } } };
1302
+ const create = { textContent: "新建任务", disabled: false, getAttribute: () => null, click() { selected = editorId = "new-conversation"; messages = 0; } };
1303
+ const send = { disabled: false, click() { sentTo = selected; } };
1304
+ const summon = { getAttribute: () => null, click() {
1305
+ strict_1.default.equal(selected, "new-conversation");
1306
+ summoned = true;
1307
+ catalogOpen = false;
1308
+ if (mode === "同步" || mode === "搜索延迟" || mode === "搜索回退")
1309
+ selected = editorId = "expert-created";
1310
+ if (mode === "微任务")
1311
+ void Promise.resolve().then(() => { selected = editorId = "expert-created"; });
1312
+ if (mode === "异步")
1313
+ setTimeout(() => { selected = "expert-created"; setTimeout(() => { editorId = selected; }, 60); }, 20);
1314
+ if (mode === "切换")
1315
+ setTimeout(() => { listeners.get("click")?.(); selected = editorId = "foreign"; }, 0);
1316
+ } };
1317
+ const card = { querySelector: (selector) => selector.includes("_role_") ? { textContent: searchReady ? "测试专家" : "其他专家" } : summon };
1318
+ const input = { value: "测试专家", dispatchEvent() { }, focus() { } };
1319
+ const entry = { textContent: "专家·技能·连接器", click() { catalogOpen = true; if (mode === "目录中切换")
1320
+ selected = "foreign"; if (mode === "搜索延迟")
1321
+ setTimeout(() => { searchReady = true; }, 20); } };
1322
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { setTimeout, clearTimeout, setInterval, clearInterval, location: { href: "app://workbuddy/task/old" }, HTMLInputElement: { prototype: {} }, Event: class {
1323
+ }, window: { getSelection() { } }, document: {
1324
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => selected }] : selector === '.cr-agent[data-message-id]' ? Array(messages).fill({}) : selector === "article[role=\"button\"]" ? [card] : selector.includes("expert_summon_click") ? [summon] : selector === 'button,[role="button"],a' ? [entry] : selector === 'button,[role="button"],[role="tab"]' ? [create] : [],
1325
+ querySelector: (selector) => selector.includes("contenteditable") ? catalogOpen ? null : editor : selector.includes("搜索专家") ? input : selector.includes('button[aria-label="发送"]') ? send : null,
1326
+ createRange: () => ({ selectNodeContents() { } }),
1327
+ addEventListener: (name, callback) => { listeners.set(name, callback); },
1328
+ removeEventListener: (name) => { listeners.delete(name); },
1329
+ } });
1330
+ client.cdp = async (_method, params) => { if (params.text) {
1331
+ if (catalogOpen) {
1332
+ input.value = params.text;
1333
+ if (mode === "搜索回退" && params.text === "测试")
1334
+ searchReady = true;
1335
+ }
1336
+ else
1337
+ editor.innerText = params.text;
1338
+ } };
1339
+ let bound;
1340
+ client.waitForReply = async (...args) => { bound = args[7]; return { text: "专家已回复" }; };
1341
+ client.connected = () => true;
1342
+ client.connect = async () => undefined;
1343
+ const plugin = new workbuddy_1.WorkbuddyPlugin({ client });
1344
+ await plugin.activate({ config: { transport: "cdp", dailyPoints: false }, logger: { info() { }, warn() { }, error() { } } });
1345
+ const response = plugin.handle({ task: "只要测试专家,帮我处理", sessionId: "logical" });
1346
+ if (mode === "切换" || mode === "超时" || mode === "目录中切换" || mode === "搜索失败") {
1347
+ await strict_1.default.rejects(response, /会话|未输入|未就绪|未找到/);
1348
+ strict_1.default.equal(sentTo, undefined);
1349
+ strict_1.default.equal(bound, undefined);
1350
+ }
1351
+ else {
1352
+ strict_1.default.equal((await response).text, "专家已回复");
1353
+ strict_1.default.equal(sentTo, "expert-created");
1354
+ strict_1.default.equal(bound, "expert-created");
1355
+ strict_1.default.equal(plugin.activeDesktopConversationId, "expert-created");
1356
+ }
1357
+ strict_1.default.equal(summoned, mode !== "目录中切换" && mode !== "搜索失败");
1358
+ strict_1.default.equal(listeners.size, 0);
1359
+ });
1360
+ }
1361
+ (0, node_test_1.default)("WorkBuddy 新建状态核验后切换到旧会话时不能输入首条消息", async () => {
1362
+ const client = new cdp_client_1.WorkbuddyCdpClient({ timeoutMs: 20 });
1363
+ let selected = "old";
1364
+ let switched = false;
1365
+ let inputs = 0;
1366
+ const editor = { innerText: "", focus() { }, querySelector: () => null, cloneNode: () => ({ textContent: "", querySelectorAll: () => [] }), __reactFiberTest: { memoizedProps: { get viewConversationId() { return selected; } } } };
1367
+ const create = { textContent: "新建任务", disabled: false, getAttribute: () => null, click() { selected = "created"; } };
1368
+ const document = {
1369
+ querySelectorAll: (selector) => selector === "[data-conversation-id]" ? [{ className: "_selected_", getAttribute: () => selected }] : selector.includes("cr-agent") || selector.includes("conversation-input-area") ? [] : [create],
1370
+ querySelector: () => editor,
1371
+ createRange: () => ({ selectNodeContents() { } }),
1372
+ };
1373
+ client.evaluate = async (expression) => {
1374
+ const result = (0, node_vm_1.runInNewContext)(expression, { document, location: { href: "app://task" }, window: { getSelection() { } } });
1375
+ if (expression.includes("clearedToUnselected") && result) {
1376
+ selected = "foreign";
1377
+ switched = true;
1378
+ }
1379
+ return result;
1380
+ };
1381
+ client.cdp = async () => { inputs++; };
1382
+ client.waitForReply = async () => ({ text: "不应发送" });
1383
+ await strict_1.default.rejects(async () => { await client.createTask(); await client.complete({ task: "在线不?", sessionId: "logical" }, undefined, false, null); }, /会话|输入|新任务/);
1384
+ strict_1.default.equal(switched, true);
1385
+ strict_1.default.equal(inputs, 0);
1386
+ });
1072
1387
  (0, node_test_1.default)("workbuddy CDP 等待新建任务入口完成渲染后再点击", async () => {
1073
1388
  let lookups = 0;
1074
1389
  let clicked = false;
@@ -1404,6 +1719,17 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1404
1719
  client.readArtifacts = async () => { throw Object.assign(new Error("文件已删除"), { code: "ENOENT" }); };
1405
1720
  await strict_1.default.rejects(client.waitForReply(new Set(), 2000), /WORKBUDDY_ARTIFACT_READ_FAILED.*读取.*ENOENT/);
1406
1721
  });
1722
+ (0, node_test_1.default)("WorkBuddy 回复正文排除已完成耗时,保留模型决策 JSON", async () => {
1723
+ const client = new cdp_client_1.WorkbuddyCdpClient();
1724
+ const decision = JSON.stringify({ role: "assistant", content: "测试标记", tool_calls: [] });
1725
+ const content = { innerText: `已完成 13s\n\n${decision}`, querySelectorAll: (selector) => selector.includes(".cr-process-row") ? [{ innerText: "已完成 13s" }] : [] };
1726
+ const message = { getAttribute: () => "reply", querySelector: (selector) => selector === "[data-assistant-content]" ? content : {} };
1727
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document: { querySelectorAll: () => [message], querySelector: () => null } });
1728
+ client.readInteraction = async () => undefined;
1729
+ client.readArtifacts = async () => [];
1730
+ const response = await client.waitForReply(new Set(), 2000);
1731
+ strict_1.default.equal(response.text, decision);
1732
+ });
1407
1733
  (0, node_test_1.default)("WorkBuddy 停止期间完成的积分检查不重新启动定时器", async () => {
1408
1734
  const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "wb-stop-points-"));
1409
1735
  let started;
@@ -1575,9 +1901,14 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1575
1901
  await strict_1.default.rejects(client.activateConversation("model", true), /用户草稿|未发送内容/);
1576
1902
  strict_1.default.equal(clicks, 0);
1577
1903
  });
1578
- (0, node_test_1.default)("WorkBuddy 运行中 CDP 消失后重新启动桥接实例,普通连接和页面错误不启动", async () => {
1904
+ (0, node_test_1.default)("WorkBuddy 运行中 CDP 消失后重新启动桥接实例,普通连接和页面错误不启动", async (t) => {
1579
1905
  const { mock } = await Promise.resolve().then(() => __importStar(require("node:test")));
1580
- const desktop = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/workbuddy/desktop-launch")));
1906
+ const desktopModule = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/workbuddy/desktop-launch")));
1907
+ const desktop = desktopModule.launchWorkbuddyDesktop ? desktopModule : desktopModule.default;
1908
+ if (typeof desktop?.launchWorkbuddyDesktop !== "function") {
1909
+ t.skip("当前测试加载器未暴露可替换的桌面启动函数");
1910
+ return;
1911
+ }
1581
1912
  let connected = true;
1582
1913
  let launches = 0;
1583
1914
  let pageError = false;
@@ -1589,15 +1920,22 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1589
1920
  direct: async () => { if (pageError)
1590
1921
  throw new Error("页面入口不可用"); return "恢复成功"; },
1591
1922
  };
1592
- const stub = mock.method(desktop, "launchWorkbuddyDesktop", (file, args, profile, port) => {
1593
- launches++;
1594
- strict_1.default.equal(file, process.execPath);
1595
- strict_1.default.ok(args.includes("--remote-debugging-port=9229"));
1596
- strict_1.default.match(profile, /desktop$/);
1597
- strict_1.default.equal(port, 9229);
1598
- connected = true;
1599
- return { unref() { }, exitCode: 0 };
1600
- });
1923
+ let stub;
1924
+ try {
1925
+ stub = mock.method(desktop, "launchWorkbuddyDesktop", (file, args, profile, port) => {
1926
+ launches++;
1927
+ strict_1.default.equal(file, process.execPath);
1928
+ strict_1.default.ok(args.includes("--remote-debugging-port=9229"));
1929
+ strict_1.default.match(profile, /desktop$/);
1930
+ strict_1.default.equal(port, 9229);
1931
+ connected = true;
1932
+ return { unref() { }, exitCode: 0 };
1933
+ });
1934
+ }
1935
+ catch {
1936
+ t.skip("当前测试加载器不支持替换桌面启动模块");
1937
+ return;
1938
+ }
1601
1939
  try {
1602
1940
  await plugin.connectOrStart({ executablePath: process.execPath }, { info() { }, warn() { }, error() { } }, 9229);
1603
1941
  strict_1.default.equal(launches, 0);
@@ -1620,3 +1958,8 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1620
1958
  await plugin.deactivate("stop");
1621
1959
  }
1622
1960
  });
1961
+ (0, node_test_1.default)("WorkBuddy 升级后 CDP 端口未监听时提示现有桌面恢复操作", async () => {
1962
+ const failure = new Error("fetch failed", { cause: Object.assign(new Error("拒绝连接"), { code: "ECONNREFUSED" }) });
1963
+ const client = new cdp_client_1.WorkbuddyCdpClient({ port: 19229, fetchImpl: async () => { throw failure; } });
1964
+ await strict_1.default.rejects(() => client.connect(), (error) => /19229/.test(error.message) && /重启桌面/.test(error.message) && error.cause === failure);
1965
+ });