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
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findCli = findCli;
4
+ exports.callCli = callCli;
5
+ const node_child_process_1 = require("node:child_process");
6
+ const node_fs_1 = require("node:fs");
7
+ const node_os_1 = require("node:os");
8
+ const node_path_1 = require("node:path");
9
+ const node_util_1 = require("node:util");
10
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
11
+ function findCli(configured) {
12
+ if (configured)
13
+ return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
14
+ const roots = [process.env.CODEBUDDY_NPM_DIR, (0, node_path_1.join)(process.env.APPDATA ?? (0, node_path_1.join)((0, node_os_1.homedir)(), "AppData", "Roaming"), "npm", "node_modules", "@tencent-ai", "codebuddy-code"), (0, node_path_1.join)((0, node_os_1.homedir)(), ".npm", "node_modules", "@tencent-ai", "codebuddy-code")];
15
+ return roots.filter((root) => Boolean(root)).map((root) => (0, node_path_1.join)(root, "bin", "codebuddy")).find(node_fs_1.existsSync);
16
+ }
17
+ async function callCli(cliPath, text, sessionId, timeoutMs, model) {
18
+ const id = (sessionId.replace(/[^A-Za-z0-9_-]/g, "-").replace(/^-+|-+$/g, "") || "wb").slice(0, 80);
19
+ const args = [cliPath, "-p", text, "--session-id", id, "--output-format", "text"];
20
+ if (model)
21
+ args.push("--model", model);
22
+ let stdout;
23
+ let stderr;
24
+ try {
25
+ ({ stdout, stderr } = await execFileAsync(process.execPath, args, { cwd: (0, node_path_1.dirname)((0, node_path_1.dirname)(cliPath)), timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, encoding: "utf8", windowsHide: true }));
26
+ }
27
+ catch (error) {
28
+ const failure = error;
29
+ if (failure.killed)
30
+ throw new Error("CodeBuddy 回复超时");
31
+ throw new Error(`CodeBuddy 调用失败:${failure.stderr?.trim().slice(-300) || failure.message}`);
32
+ }
33
+ if (/Authentication required\. Please use \/login command to sign in to your account/.test(`${stdout}\n${stderr}`))
34
+ throw new Error("CodeBuddy CLI 尚未登录,请先执行 /login");
35
+ const reply = stdout.split(/\r?\n/).filter((line) => !(line.startsWith("[") && line.slice(0, 40).includes("]["))).join("\n").trim();
36
+ if (!reply)
37
+ throw new Error("CodeBuddy 无输出");
38
+ return reply;
39
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.launchWorkbuddyDesktop = launchWorkbuddyDesktop;
4
+ const node_child_process_1 = require("node:child_process");
5
+ function launchWorkbuddyDesktop(executable, args, profileDirectory, port) {
6
+ // WorkBuddy 会覆盖 Chromium 的用户目录参数,必须使用它自己的环境变量隔离实例。
7
+ const env = { ...process.env, WORKBUDDY_USER_DATA_DIR: profileDirectory, WORKBUDDY_REMOTE_DEBUGGING_PORT: String(port) };
8
+ delete env.ELECTRON_RUN_AS_NODE;
9
+ return (0, node_child_process_1.spawn)(executable, [...args, "--lang=zh-CN", "--accept-lang=zh-CN,zh"], { env, detached: true, stdio: "ignore", windowsHide: false });
10
+ }
@@ -0,0 +1,567 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkbuddyPlugin = exports.workbuddyManifest = void 0;
4
+ exports.extractExpertQuery = extractExpertQuery;
5
+ exports.findExecutable = findExecutable;
6
+ const node_fs_1 = require("node:fs");
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const agent_1 = require("../../agent");
11
+ const agent_2 = require("../../agent");
12
+ const attachments_1 = require("../../attachments");
13
+ const plugin_1 = require("../../plugin");
14
+ const cdp_client_1 = require("./cdp-client");
15
+ const cli_client_1 = require("./cli-client");
16
+ const session_history_1 = require("./session-history");
17
+ const desktop_launch_1 = require("../desktop-launch");
18
+ const desktop_launch_2 = require("./desktop-launch");
19
+ exports.workbuddyManifest = {
20
+ id: "workbuddy", version: "1.1.35", displayName: "WorkBuddy", type: "agent",
21
+ entry: "dist/index.js", apiVersion: "1", process: "in-process",
22
+ capabilities: ["chat", "health", "model-provider"], configSchema: "config.schema.json",
23
+ };
24
+ function extractExpertQuery(task) {
25
+ const text = task.normalize("NFKC");
26
+ const match = text.match(/^(?:请)?(?:只要|调用|使用|帮我找|找|用|选择|切换到)\s*[“「"]?([^,。!?,!?\n“「"]{2,30}?专家(?:团)?)(?!技能)/)
27
+ ?? text.match(/^([A-Za-z\u4e00-\u9fff]{2,24}?专家(?:团)?)(?=[,。!,!::\s]|$)/);
28
+ return match?.[1]?.trim() || undefined;
29
+ }
30
+ function findExecutable(configured) {
31
+ if (configured)
32
+ return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
33
+ const local = process.env.LOCALAPPDATA ?? (0, node_path_1.join)((0, node_os_1.homedir)(), "AppData", "Local");
34
+ return [
35
+ (0, node_path_1.join)(local, "Programs", "WorkBuddy", "WorkBuddy.exe"),
36
+ (0, node_path_1.join)(local, "Programs", "WorkBuddyAI", "WorkBuddyAI.exe"),
37
+ "D:\\Programs\\WorkBuddy\\WorkBuddy.exe",
38
+ "D:\\Programs\\WorkBuddyAI\\WorkBuddyAI.exe",
39
+ ].find(node_fs_1.existsSync);
40
+ }
41
+ class WorkbuddyPlugin {
42
+ options;
43
+ manifest = exports.workbuddyManifest;
44
+ get modelProvider() {
45
+ if (this.transport === "cli" || (this.transport !== "cdp" && this.cliPath) || !this.client?.createTask || !this.client.complete)
46
+ return undefined;
47
+ return { complete: (request) => this.completeModelProvider(request) };
48
+ }
49
+ client;
50
+ process;
51
+ reconnect;
52
+ cliPath;
53
+ model;
54
+ transport = "auto";
55
+ replyTimeoutLimitMs;
56
+ timeoutMs = cdp_client_1.WORKBUDDY_MAX_WAIT_MS;
57
+ queue = Promise.resolve();
58
+ pending = 0;
59
+ activeSessionId;
60
+ /** 重载后待恢复的会话身份。 */
61
+ recoverableSessionId;
62
+ recoverableConversationId;
63
+ sessionBindings = new Map();
64
+ activeDesktopConversationId;
65
+ desktopSessionValid = true;
66
+ knownSessions = new Set();
67
+ sessionDataDir;
68
+ sessionWrites = Promise.resolve();
69
+ dailyPointsTimer;
70
+ dailyPointsActive = false;
71
+ dailyPointsDataDir;
72
+ dailyPointsLogger;
73
+ failure;
74
+ expertSession = false;
75
+ activeExpert;
76
+ sessionOperations = [];
77
+ sessionHistory;
78
+ constructor(options = {}) {
79
+ this.options = options;
80
+ this.replyTimeoutLimitMs = options.replyTimeoutLimitMs ?? cdp_client_1.WORKBUDDY_MAX_WAIT_MS;
81
+ }
82
+ get attachmentInput() { return Boolean(this.client?.attachmentInput && this.client.complete && this.transport !== "cli" && (!this.cliPath || this.transport === "cdp")); }
83
+ async activate(context) {
84
+ const config = context.config;
85
+ if (config.transport !== undefined && !["auto", "cli", "cdp"].includes(String(config.transport)))
86
+ throw new Error("transport 只能是 auto、cli 或 cdp");
87
+ this.transport = config.transport ?? "auto";
88
+ this.timeoutMs = Math.min(typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 3_600_000, this.replyTimeoutLimitMs);
89
+ this.model = typeof config.model === "string" && config.model.trim() ? config.model : undefined;
90
+ this.cliPath = (0, cli_client_1.findCli)(typeof config.cliPath === "string" ? config.cliPath : undefined);
91
+ this.failure = undefined;
92
+ this.activeSessionId = undefined;
93
+ this.recoverableSessionId = undefined;
94
+ this.recoverableConversationId = undefined;
95
+ this.sessionBindings.clear();
96
+ this.activeDesktopConversationId = undefined;
97
+ this.activeExpert = undefined;
98
+ this.expertSession = false;
99
+ this.desktopSessionValid = true;
100
+ this.knownSessions.clear();
101
+ this.sessionDataDir = context.dataDir;
102
+ this.dailyPointsDataDir = context.dataDir;
103
+ this.dailyPointsLogger = context.logger;
104
+ let savedBinding;
105
+ if (this.sessionDataDir) {
106
+ const saved = await this.loadKnownSessions(this.sessionDataDir);
107
+ for (const sessionId of saved.sessionIds)
108
+ this.knownSessions.add(sessionId);
109
+ for (const [sessionId, binding] of Object.entries(saved.bindings ?? {}))
110
+ this.sessionBindings.set(sessionId, binding);
111
+ savedBinding = saved.active;
112
+ }
113
+ this.sessionOperations = [];
114
+ this.sessionHistory = new session_history_1.WorkbuddySessionHistoryProvider({
115
+ scope: `workbuddy:cdp:${(0, plugin_1.resolveRuntimePort)(config, context.runtime, "workbuddy", "WORKBUDDY_CDP_PORT", this.options.cdpPort ?? 9229)}`,
116
+ supported: () => this.transport !== "cli" && !(this.transport !== "cdp" && this.cliPath) && Boolean(this.client?.connected() && this.client.conversationId && this.client.readHistorySnapshot && this.client.historyAvailability),
117
+ client: () => this.client,
118
+ run: async (work) => {
119
+ if (this.transport === "cli" || (this.transport !== "cdp" && this.cliPath))
120
+ return { supported: false, reason: "WorkBuddy CLI 模式不支持原生历史读取" };
121
+ if (!this.client?.connected() || !this.client.conversationId || !this.client.readHistorySnapshot || !this.client.historyAvailability)
122
+ return { supported: false, reason: "WorkBuddy 当前客户端未连接或不提供只读历史接口" };
123
+ if (this.pending || this.client?.pendingInteractionSessionId?.())
124
+ return { supported: false, reason: "WorkBuddy 当前忙或正在等待交互,暂不读取历史" };
125
+ return this.enqueue(async () => {
126
+ if (this.client?.pendingInteractionSessionId?.())
127
+ return { supported: false, reason: "WorkBuddy 正在等待交互,暂不读取历史" };
128
+ return work();
129
+ });
130
+ },
131
+ });
132
+ if (this.transport === "cli") {
133
+ if (!this.cliPath)
134
+ throw new Error("未找到 CodeBuddy CLI,请配置 cliPath");
135
+ return;
136
+ }
137
+ const port = (0, plugin_1.resolveRuntimePort)(config, context.runtime, "workbuddy", "WORKBUDDY_CDP_PORT", this.options.cdpPort ?? 9229);
138
+ this.client = this.options.client ?? new cdp_client_1.WorkbuddyCdpClient({ port });
139
+ try {
140
+ await this.connectOrStart(config, context.logger, port, context.dataDir);
141
+ }
142
+ catch (error) {
143
+ this.failure = error instanceof Error ? error.message : String(error);
144
+ this.client.close();
145
+ this.client = undefined;
146
+ if (this.transport === "cdp" || !this.cliPath)
147
+ throw error;
148
+ }
149
+ this.sessionOperations = (this.transport === "cdp" || !this.cliPath) && this.client?.createTask ? ["new"] : [];
150
+ if (savedBinding && this.sessionOperations.length && await this.client?.conversationId?.() === savedBinding.conversationId) {
151
+ this.activeSessionId = savedBinding.sessionId;
152
+ this.activeDesktopConversationId = savedBinding.conversationId;
153
+ this.activeExpert = savedBinding.expert;
154
+ }
155
+ else if (savedBinding && this.sessionOperations.length) {
156
+ this.recoverableSessionId = savedBinding.sessionId;
157
+ this.recoverableConversationId = savedBinding.conversationId;
158
+ }
159
+ if (config.dailyPoints !== false && this.client?.claimDailyPoints) {
160
+ const hour = typeof config.dailyPointsHour === "number" ? config.dailyPointsHour : 9;
161
+ const minute = typeof config.dailyPointsMinute === "number" ? config.dailyPointsMinute : 0;
162
+ if (!Number.isInteger(hour) || hour < 0 || hour > 23 || !Number.isInteger(minute) || minute < 0 || minute > 59)
163
+ throw new Error("积分领取时间无效");
164
+ this.scheduleDailyPoints(hour, minute);
165
+ }
166
+ }
167
+ async deactivate(_reason) {
168
+ this.dailyPointsActive = false;
169
+ if (this.dailyPointsTimer)
170
+ clearTimeout(this.dailyPointsTimer);
171
+ this.dailyPointsTimer = undefined;
172
+ await this.queue;
173
+ await this.sessionWrites.catch(() => { });
174
+ if (_reason !== "reload")
175
+ await (0, desktop_launch_1.terminateOwnedProcess)(this.process).catch(() => undefined);
176
+ this.client?.close();
177
+ this.client = undefined;
178
+ this.process = undefined;
179
+ this.reconnect = undefined;
180
+ }
181
+ scheduleDailyPoints(hour, minute) {
182
+ this.dailyPointsActive = true;
183
+ const now = new Date();
184
+ const scheduled = new Date(now);
185
+ scheduled.setHours(hour, minute, 0, 0);
186
+ const due = scheduled <= now;
187
+ const arm = () => {
188
+ if (!this.dailyPointsActive)
189
+ return;
190
+ const next = new Date();
191
+ next.setHours(hour, minute, 0, 0);
192
+ if (next <= new Date())
193
+ next.setDate(next.getDate() + 1);
194
+ this.dailyPointsTimer = setTimeout(() => { void run(); }, Math.max(1, next.getTime() - Date.now()));
195
+ };
196
+ const run = async () => {
197
+ if (!this.dailyPointsDataDir || !this.client?.claimDailyPoints)
198
+ return;
199
+ const date = new Date().toLocaleDateString("sv-SE");
200
+ const file = (0, node_path_1.join)(this.dailyPointsDataDir, "daily-points.json");
201
+ try {
202
+ const state = JSON.parse(await (0, promises_1.readFile)(file, "utf8"));
203
+ if (state.date === date) {
204
+ arm();
205
+ return;
206
+ }
207
+ }
208
+ catch (error) {
209
+ if (error.code !== "ENOENT") {
210
+ this.dailyPointsLogger?.error("WorkBuddy 每日积分状态读取失败", { error: error instanceof Error ? error.message : String(error) });
211
+ arm();
212
+ return;
213
+ }
214
+ }
215
+ try {
216
+ await (0, promises_1.mkdir)(this.dailyPointsDataDir, { recursive: true });
217
+ const result = await this.enqueue(() => this.client.claimDailyPoints());
218
+ if (result.clicked.some(label => /(签到|领取(?:积分|奖励)?|收下奖励)/.test(label))) {
219
+ await (0, promises_1.writeFile)(file, JSON.stringify({ date }), { mode: 0o600 });
220
+ }
221
+ this.dailyPointsLogger?.info("WorkBuddy 每日免费积分领取检查完成", result);
222
+ }
223
+ catch (error) {
224
+ this.dailyPointsLogger?.error("WorkBuddy 每日积分领取失败", { error: error instanceof Error ? error.message : String(error) });
225
+ }
226
+ finally {
227
+ arm();
228
+ }
229
+ };
230
+ if (due) {
231
+ this.dailyPointsTimer = setTimeout(() => { void run(); }, 1);
232
+ }
233
+ else {
234
+ this.dailyPointsTimer = setTimeout(() => { void run(); }, Math.max(1, scheduled.getTime() - Date.now()));
235
+ }
236
+ }
237
+ async health() {
238
+ if (this.client?.connected())
239
+ return { status: "ready", details: { transport: "cdp" } };
240
+ if (this.transport !== "cdp" && this.cliPath && (0, node_fs_1.existsSync)(this.cliPath))
241
+ return { status: "ready", details: { transport: "cli" } };
242
+ return { status: "failed", message: this.failure ?? "WorkBuddy CDP 未连接;桌面升级后请在后台使用“重启桌面”恢复连接", details: { transport: this.transport } };
243
+ }
244
+ async handle(request) {
245
+ if (!request.task.trim())
246
+ throw new Error("消息内容不能为空");
247
+ (0, attachments_1.validateAttachments)(request.attachments);
248
+ const run = this.enqueue(async () => {
249
+ request.signal?.throwIfAborted();
250
+ try {
251
+ const response = await this.complete(request);
252
+ await this.synchronizeSession();
253
+ return response;
254
+ }
255
+ catch (error) {
256
+ // 失败时桌面可能已切换,保留原绑定并要求重新校验或恢复。
257
+ this.desktopSessionValid = false;
258
+ throw error;
259
+ }
260
+ });
261
+ const response = await run;
262
+ return { ...response, sessionId: request.sessionId ?? response.sessionId };
263
+ }
264
+ async synchronizeSession() {
265
+ if (this.activeSessionId && this.desktopSessionValid && this.client?.conversationId) {
266
+ try {
267
+ const conversationId = await this.client.conversationId();
268
+ if (this.activeDesktopConversationId && conversationId !== this.activeDesktopConversationId) {
269
+ this.desktopSessionValid = false;
270
+ throw new Error("WorkBuddy 桌面任务已切换,已保留原会话绑定");
271
+ }
272
+ this.activeDesktopConversationId = conversationId;
273
+ if (this.activeDesktopConversationId)
274
+ this.sessionBindings.set(this.activeSessionId, { conversationId: this.activeDesktopConversationId, expert: this.activeExpert });
275
+ await this.persistKnownSessions();
276
+ }
277
+ catch (error) {
278
+ this.dailyPointsLogger?.warn("WorkBuddy 会话状态同步失败,已保留本轮回复", { stage: "完成后读取会话标识或保存映射", error: error instanceof Error ? error.message : String(error) });
279
+ }
280
+ }
281
+ }
282
+ async completeModelProvider(request) {
283
+ let stage = "WorkBuddy 模型排队(queue)";
284
+ try {
285
+ request.signal.throwIfAborted();
286
+ const run = this.enqueue(async () => {
287
+ request.signal.throwIfAborted();
288
+ const client = this.client;
289
+ if (!client?.connected() || !client.complete)
290
+ throw new Error("WorkBuddy CDP 未连接,模型请求不会启动或重启桌面");
291
+ const availability = await client.historyAvailability?.();
292
+ if (availability?.waiting || availability?.generating || availability?.draft)
293
+ throw new Error("WorkBuddy 当前有用户草稿、待确认操作或正在生成回复,模型请求未切换会话");
294
+ stage = "连接 WorkBuddy 模型(connect)";
295
+ const previous = { sessionId: this.activeSessionId, conversationId: await client.conversationId?.(), expert: this.activeExpert, expertSession: this.expertSession, valid: this.desktopSessionValid };
296
+ try {
297
+ stage = "等待 WorkBuddy 模型生成(generate)";
298
+ const response = await this.complete({ task: request.prompt, sessionId: request.sessionId, timeoutMs: request.timeoutMs, signal: request.signal }, true);
299
+ return { text: response.text };
300
+ }
301
+ finally {
302
+ await this.synchronizeSession();
303
+ if (client.connected()) {
304
+ if (previous.conversationId) {
305
+ if (!client.activateConversation)
306
+ throw new Error("WorkBuddy 无法恢复用户原会话");
307
+ await client.activateConversation(previous.conversationId, true);
308
+ }
309
+ else if (client.conversationId)
310
+ await client.createTask();
311
+ }
312
+ this.activeSessionId = previous.sessionId;
313
+ this.activeDesktopConversationId = previous.conversationId;
314
+ this.activeExpert = previous.expert;
315
+ this.expertSession = previous.expertSession;
316
+ this.desktopSessionValid = previous.valid;
317
+ await this.persistKnownSessions();
318
+ }
319
+ });
320
+ return await (0, agent_1.abortable)(run, request.signal);
321
+ }
322
+ catch (error) {
323
+ if (error instanceof agent_2.AgentOperationError)
324
+ throw error;
325
+ throw new agent_2.AgentOperationError("WORKBUDDY_MODEL_FAILED", stage, error);
326
+ }
327
+ }
328
+ async controlSession(request) {
329
+ if (this.pending)
330
+ throw new Error("WorkBuddy 当前忙,无法创建新会话");
331
+ if (request.operation !== "new")
332
+ throw new Error(`WorkBuddy 不支持会话操作:${String(request.operation)}`);
333
+ const sessionId = request.sessionId?.trim();
334
+ if (!sessionId)
335
+ throw new Error("WorkBuddy 新会话缺少 sessionId");
336
+ if (this.transport === "cli" || (this.transport !== "cdp" && this.cliPath) || !this.client?.createTask)
337
+ throw new Error("WorkBuddy 当前传输不支持新建会话");
338
+ if (!this.client.connected())
339
+ await this.reconnect?.();
340
+ if (!this.client.connected())
341
+ throw new Error("WorkBuddy CDP 未连接");
342
+ return this.enqueue(async () => {
343
+ if (this.knownSessions.has(sessionId))
344
+ throw new Error("WorkBuddy 不支持恢复旧 WorkBuddy 会话");
345
+ await this.createSession(sessionId, request.name);
346
+ return { text: "WorkBuddy 已创建新会话", sessionId };
347
+ });
348
+ }
349
+ async complete(request, modelRequest = false) {
350
+ let stage = "连接 WorkBuddy 桌面";
351
+ try {
352
+ const timeout = Math.min(request.timeoutMs ?? this.timeoutMs, this.replyTimeoutLimitMs);
353
+ const expert = modelRequest || this.options.expertSelection === false ? undefined : extractExpertQuery(request.task);
354
+ const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
355
+ const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
356
+ if (hasAttachments && !this.attachmentInput)
357
+ throw new Error("WorkBuddy 当前传输不支持附件输入");
358
+ if (!usesCli && !this.client?.connected()) {
359
+ if (modelRequest)
360
+ throw new Error("WorkBuddy CDP 已断开,模型请求不会启动或重启桌面");
361
+ await this.reconnect?.();
362
+ }
363
+ const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
364
+ if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
365
+ throw new Error("WorkBuddy 正在等待另一个会话的选择");
366
+ if (usesCli && request.sessionId)
367
+ throw new Error("CodeBuddy CLI 会话隔离未验证,不能处理带 sessionId 的请求");
368
+ if (!usesCli && !request.sessionId && this.activeSessionId)
369
+ throw new Error("WorkBuddy 已绑定逻辑会话,请使用相同 sessionId 或先执行 /new");
370
+ if (!usesCli && !request.sessionId && !this.desktopSessionValid)
371
+ throw new Error("WorkBuddy 桌面任务状态未知,请先创建新会话");
372
+ stage = "准备或恢复 WorkBuddy 会话";
373
+ if (!usesCli)
374
+ await this.ensureSession(request.sessionId, modelRequest);
375
+ stage = "执行 WorkBuddy 桌面任务";
376
+ if (expert) {
377
+ if (!this.client?.connected())
378
+ throw new Error(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
379
+ if (request.sessionId && this.activeExpert !== undefined && this.activeExpert !== expert)
380
+ throw new Error("WorkBuddy 切换专家需要先执行 /new,避免丢失当前会话历史");
381
+ const select = request.sessionId && this.activeExpert === expert ? undefined : expert;
382
+ if (request.sessionId)
383
+ this.activeExpert = expert;
384
+ this.expertSession = true;
385
+ try {
386
+ if (this.client.complete)
387
+ return await this.client.complete({ ...request, timeoutMs: timeout }, select, false, request.sessionId ? this.activeDesktopConversationId ?? null : undefined);
388
+ return { text: await this.client.direct(request.task, timeout, select) };
389
+ }
390
+ catch (error) {
391
+ if (select)
392
+ this.desktopSessionValid = false;
393
+ throw error;
394
+ }
395
+ }
396
+ if (usesCli)
397
+ return { text: await (0, cli_client_1.callCli)(this.cliPath, request.task, "local-test", timeout, this.model) };
398
+ if (!this.client?.connected())
399
+ throw new Error("WorkBuddy CDP 未连接");
400
+ const reset = !request.sessionId && this.expertSession;
401
+ if (!request.sessionId)
402
+ this.expertSession = false;
403
+ if (request.sessionId && this.activeExpert === undefined)
404
+ this.activeExpert = null;
405
+ try {
406
+ if (this.client.complete)
407
+ return await this.client.complete({ ...request, timeoutMs: timeout }, undefined, reset, request.sessionId ? this.activeDesktopConversationId ?? null : undefined);
408
+ return { text: await this.client.direct(request.task, timeout, undefined, reset) };
409
+ }
410
+ catch (error) {
411
+ if (reset)
412
+ this.desktopSessionValid = false;
413
+ throw error;
414
+ }
415
+ }
416
+ catch (error) {
417
+ if (error instanceof agent_2.AgentOperationError || error?.stage)
418
+ throw error;
419
+ throw new agent_2.AgentOperationError("WORKBUDDY_TASK_FAILED", stage, error);
420
+ }
421
+ }
422
+ async ensureSession(sessionId, modelRequest = false) {
423
+ if (!sessionId)
424
+ return;
425
+ if (!this.client?.connected())
426
+ throw new Error("WorkBuddy CDP 未连接");
427
+ if (!this.client.createTask)
428
+ throw new Error("WorkBuddy 客户端不支持新建会话");
429
+ if (this.activeSessionId === sessionId && this.desktopSessionValid) {
430
+ if (this.activeDesktopConversationId && await this.client.conversationId?.() !== this.activeDesktopConversationId) {
431
+ this.desktopSessionValid = false;
432
+ throw new Error("WorkBuddy 桌面任务已切换,请先创建新会话");
433
+ }
434
+ return;
435
+ }
436
+ if (this.knownSessions.has(sessionId)) {
437
+ const binding = this.sessionBindings.get(sessionId);
438
+ const conversationId = binding?.conversationId ?? (this.recoverableSessionId === sessionId ? this.recoverableConversationId : undefined);
439
+ if (!conversationId) {
440
+ this.dailyPointsLogger?.warn("WorkBuddy 会话缺少桌面绑定,已保留记录且未创建新任务", { sessionId });
441
+ throw new Error("WorkBuddy 会话缺少桌面任务标识,无法安全恢复上下文;请确认后使用 /new 创建新会话");
442
+ }
443
+ if (!this.client.activateConversation)
444
+ throw new Error("WorkBuddy 不支持恢复旧 WorkBuddy 会话");
445
+ await this.client.activateConversation(conversationId, modelRequest);
446
+ this.activeSessionId = sessionId;
447
+ this.activeDesktopConversationId = conversationId;
448
+ this.activeExpert = binding?.expert;
449
+ this.desktopSessionValid = true;
450
+ this.recoverableSessionId = undefined;
451
+ this.recoverableConversationId = undefined;
452
+ return;
453
+ }
454
+ await this.createSession(sessionId);
455
+ }
456
+ async createSession(sessionId, name) {
457
+ // 点击失败时桌面可能已经切换,保留逻辑绑定但禁止向未知任务续发。
458
+ this.desktopSessionValid = false;
459
+ this.activeSessionId = undefined;
460
+ this.activeDesktopConversationId = undefined;
461
+ this.knownSessions.add(sessionId);
462
+ await this.persistKnownSessions();
463
+ try {
464
+ await this.client.createTask(name);
465
+ }
466
+ catch (error) {
467
+ this.knownSessions.delete(sessionId);
468
+ try {
469
+ await this.persistKnownSessions();
470
+ }
471
+ catch (rollbackError) {
472
+ throw new AggregateError([error, rollbackError], "WorkBuddy 新任务失败且会话记录回滚失败", { cause: error });
473
+ }
474
+ throw error;
475
+ }
476
+ this.desktopSessionValid = true;
477
+ this.expertSession = false;
478
+ this.activeExpert = undefined;
479
+ this.activeSessionId = sessionId;
480
+ this.recoverableSessionId = undefined;
481
+ this.recoverableConversationId = undefined;
482
+ }
483
+ async loadKnownSessions(directory) {
484
+ try {
485
+ const parsed = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "known-sessions.json"), "utf8"));
486
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
487
+ throw new Error("记录根节点无效");
488
+ const sessionIds = parsed.sessionIds;
489
+ if (!Array.isArray(sessionIds) || sessionIds.some((sessionId) => typeof sessionId !== "string" || !sessionId.trim()))
490
+ throw new Error("sessionIds 无效");
491
+ const active = parsed.active;
492
+ if (active !== undefined && (!active || typeof active.sessionId !== "string" || !sessionIds.includes(active.sessionId) || typeof active.conversationId !== "string" || !active.conversationId.trim() || (active.expert !== undefined && active.expert !== null && typeof active.expert !== "string")))
493
+ throw new Error("活动会话绑定无效");
494
+ const activeBinding = active;
495
+ const bindingsValue = parsed.bindings;
496
+ if (bindingsValue !== undefined && (!bindingsValue || typeof bindingsValue !== "object" || Array.isArray(bindingsValue) || Object.entries(bindingsValue).some(([sessionId, binding]) => !sessionIds.includes(sessionId) || !binding || typeof binding !== "object" || typeof binding.conversationId !== "string" || !binding.conversationId.trim() || (binding.expert !== undefined && binding.expert !== null && typeof binding.expert !== "string"))))
497
+ throw new Error("会话绑定列表无效");
498
+ const bindings = (bindingsValue ?? {});
499
+ if (activeBinding && !bindings[activeBinding.sessionId])
500
+ bindings[activeBinding.sessionId] = { conversationId: activeBinding.conversationId, expert: activeBinding.expert };
501
+ return { sessionIds: new Set(sessionIds), ...(activeBinding ? { active: activeBinding } : {}), bindings };
502
+ }
503
+ catch (error) {
504
+ if (error.code === "ENOENT")
505
+ return { sessionIds: new Set() };
506
+ if (error instanceof SyntaxError || error instanceof Error)
507
+ throw new Error(`WorkBuddy 已知会话记录无效:${error.message}`);
508
+ throw new Error("WorkBuddy 已知会话记录无效");
509
+ }
510
+ }
511
+ async persistKnownSessions() {
512
+ if (!this.sessionDataDir)
513
+ return;
514
+ const write = this.sessionWrites.catch(() => { }).then(async () => {
515
+ await (0, promises_1.mkdir)(this.sessionDataDir, { recursive: true });
516
+ const file = (0, node_path_1.join)(this.sessionDataDir, "known-sessions.json");
517
+ const active = this.desktopSessionValid && this.activeSessionId && this.activeDesktopConversationId ? { sessionId: this.activeSessionId, conversationId: this.activeDesktopConversationId, expert: this.activeExpert } : undefined;
518
+ if (active)
519
+ this.sessionBindings.set(active.sessionId, { conversationId: active.conversationId, expert: active.expert });
520
+ await (0, promises_1.writeFile)(`${file}.tmp`, `${JSON.stringify({ sessionIds: [...this.knownSessions].sort(), active, bindings: Object.fromEntries(this.sessionBindings) })}\n`, { mode: 0o600 });
521
+ await (0, promises_1.rename)(`${file}.tmp`, file);
522
+ });
523
+ this.sessionWrites = write;
524
+ await write;
525
+ }
526
+ enqueue(work) {
527
+ this.pending++;
528
+ const run = this.queue.then(work, work);
529
+ this.queue = run.then(() => { this.pending--; }, () => { this.pending--; });
530
+ return run;
531
+ }
532
+ async connectOrStart(config, logger, port, dataDir) {
533
+ const executable = findExecutable(typeof config.executablePath === "string" ? config.executablePath : this.options.executablePath);
534
+ const profile = (0, node_path_1.join)(dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "workbuddy"), "desktop");
535
+ const launch = (file, args) => (0, desktop_launch_2.launchWorkbuddyDesktop)(file, args, profile, port);
536
+ const timeoutMs = typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000;
537
+ const connectOrLaunch = async () => {
538
+ try {
539
+ await this.client.connect();
540
+ return;
541
+ }
542
+ catch (first) {
543
+ if (config.autoStart === false || this.options.client)
544
+ throw first;
545
+ if (!executable)
546
+ throw new Error("未找到 WorkBuddy.exe,请配置 executablePath");
547
+ logger.info(`WorkBuddy CDP ${port} 不可用,启动带调试参数的桥接实例`);
548
+ await (0, desktop_launch_1.launchManagedAndWait)(executable, [`--remote-debugging-port=${port}`], profile, () => this.client.connect(), timeoutMs, child => { this.process = child; }, launch);
549
+ }
550
+ };
551
+ this.reconnect = async () => {
552
+ try {
553
+ await this.client.connect();
554
+ }
555
+ catch (error) {
556
+ const failure = error;
557
+ // 只有调试端口拒绝连接才重新启动;页面或协议错误不得触碰桌面进程。
558
+ if (failure.code !== "ECONNREFUSED" && failure.cause?.code !== "ECONNREFUSED")
559
+ throw error;
560
+ await connectOrLaunch();
561
+ }
562
+ };
563
+ await connectOrLaunch();
564
+ }
565
+ }
566
+ exports.WorkbuddyPlugin = WorkbuddyPlugin;
567
+ exports.default = WorkbuddyPlugin;