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.
- package/README.md +17 -10
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +12 -30
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +31 -13
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +351 -50
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +155 -21
- package/dist/src-ts/plugins/aun/messages.js +4 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/dist/src-ts/plugins/doubao/index.js +152 -8
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +172 -21
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +132 -14
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +73 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +20 -0
- package/dist/tests-ts/aun-plugin.test.js +292 -19
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
- package/dist/tests-ts/control-server.test.js +56 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +773 -12
- package/dist/tests-ts/dsh-agent.test.js +157 -8
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +146 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +663 -0
- package/dist/tests-ts/platform-integration.test.js +183 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +356 -4
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +159 -4
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +23 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -161,6 +161,33 @@ const message = (text, extra = {}) => ({
|
|
|
161
161
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
|
+
(0, node_test_1.default)("逻辑会话 ID 在 Agent 调用前可见且续用,调用失败不保存新绑定", async () => {
|
|
165
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-session-logical-callback-"));
|
|
166
|
+
try {
|
|
167
|
+
const sessions = new channel_sessions_1.ChannelSessions(root);
|
|
168
|
+
let firstId = "";
|
|
169
|
+
await sessions.run(message("首次"), "agent", async (_sessionId, _newSession, logicalSessionId) => {
|
|
170
|
+
strict_1.default.match(logicalSessionId, /^[0-9a-f-]{36}$/i);
|
|
171
|
+
firstId = logicalSessionId;
|
|
172
|
+
return { text: "完成" };
|
|
173
|
+
});
|
|
174
|
+
await sessions.run(message("继续"), "agent", async (_sessionId, _newSession, logicalSessionId) => {
|
|
175
|
+
strict_1.default.equal(logicalSessionId, firstId);
|
|
176
|
+
return { text: "继续完成" };
|
|
177
|
+
});
|
|
178
|
+
const other = message("另一个话题", { threadId: "topic-2" });
|
|
179
|
+
let failedId = "";
|
|
180
|
+
await strict_1.default.rejects(sessions.run(other, "agent", async (_sessionId, _newSession, logicalSessionId) => {
|
|
181
|
+
failedId = logicalSessionId;
|
|
182
|
+
strict_1.default.notEqual(failedId, firstId);
|
|
183
|
+
throw new Error("Agent失败");
|
|
184
|
+
}), /Agent失败/);
|
|
185
|
+
strict_1.default.equal((await sessions.current(other, "agent")).logicalSessionId === failedId, false);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
164
191
|
(0, node_test_1.default)("切换插件的普通调用失败时保留原活动会话和后端续聊目标", async () => {
|
|
165
192
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-session-plugin-failure-"));
|
|
166
193
|
try {
|
|
@@ -38,7 +38,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
40
40
|
const node_test_1 = __importDefault(require("node:test"));
|
|
41
|
+
const node_vm_1 = require("node:vm");
|
|
41
42
|
const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
43
|
+
const desktopLaunch = __importStar(require("../src-ts/plugins/desktop-launch"));
|
|
44
|
+
(0, node_test_1.default)("ChatGPT 升级清理保留桌面,普通停止仍关闭桥接持有的进程", async (t) => {
|
|
45
|
+
const terminated = [];
|
|
46
|
+
t.mock.method(desktopLaunch, "terminateOwnedProcess", async (child) => { if (child)
|
|
47
|
+
terminated.push(child); });
|
|
48
|
+
let closed = 0;
|
|
49
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client: { connect: async () => undefined, close: () => { closed++; }, connected: () => true, direct: async () => "回复" } });
|
|
50
|
+
const context = { config: {}, logger: { info() { }, warn() { }, error() { } } };
|
|
51
|
+
const owned = { pid: 12345 };
|
|
52
|
+
await plugin.activate(context);
|
|
53
|
+
plugin.ownedProcess = owned;
|
|
54
|
+
await plugin.deactivate("reload", { preserveDesktop: true });
|
|
55
|
+
strict_1.default.equal(closed, 1);
|
|
56
|
+
strict_1.default.deepEqual(terminated, []);
|
|
57
|
+
await plugin.activate(context);
|
|
58
|
+
plugin.ownedProcess = owned;
|
|
59
|
+
await plugin.deactivate();
|
|
60
|
+
strict_1.default.deepEqual(terminated, [owned]);
|
|
61
|
+
});
|
|
42
62
|
(0, node_test_1.default)("ChatGPT 插件通过已连接 CDP 客户端转发请求", async () => {
|
|
43
63
|
const calls = [];
|
|
44
64
|
const plugin = new chatgpt_1.ChatgptPlugin({ client: { connect: async () => undefined, close() { }, connected: () => true, direct: async (text) => { calls.push(text); return "回复"; } } });
|
|
@@ -94,6 +114,35 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
|
94
114
|
await rm(dataDir, { recursive: true, force: true });
|
|
95
115
|
}
|
|
96
116
|
});
|
|
117
|
+
(0, node_test_1.default)("ChatGPT 模型来源为个人助理建立专用会话,不复用桌面当前会话", async () => {
|
|
118
|
+
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
119
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
120
|
+
const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
121
|
+
const dataDir = await mkdtemp(join(tmpdir(), "chatgpt-model-provider-"));
|
|
122
|
+
const existing = { id: "user-chat", host: "h", title: "用户会话" };
|
|
123
|
+
const dedicated = { id: "pa-chat", host: "h", title: "个人助理模型" };
|
|
124
|
+
let active = existing;
|
|
125
|
+
const calls = [];
|
|
126
|
+
const client = {
|
|
127
|
+
connect: async () => undefined, close() { }, connected: () => true,
|
|
128
|
+
listSessions: async () => [existing, dedicated],
|
|
129
|
+
activeSession: async () => active,
|
|
130
|
+
createSession: async () => { active = dedicated; return dedicated; },
|
|
131
|
+
switchSession: async (session) => { active = session; },
|
|
132
|
+
direct: async (text) => { calls.push(`${active.id}:${text}`); return "模型 JSON"; },
|
|
133
|
+
};
|
|
134
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client });
|
|
135
|
+
try {
|
|
136
|
+
await plugin.activate({ config: {}, dataDir, logger: { info() { }, warn() { }, error() { } } });
|
|
137
|
+
strict_1.default.deepEqual(await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-thread", timeoutMs: 5000, signal: new AbortController().signal }), { text: "模型 JSON" });
|
|
138
|
+
strict_1.default.deepEqual(calls, ["pa-chat:模型提示"]);
|
|
139
|
+
strict_1.default.equal(active.id, "user-chat");
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await plugin.deactivate();
|
|
143
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
97
146
|
(0, node_test_1.default)("ChatGPT 一个来源待确认的新会话不阻塞其他来源", async () => {
|
|
98
147
|
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
99
148
|
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
@@ -773,3 +822,72 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
|
773
822
|
});
|
|
774
823
|
strict_1.default.deepEqual(await client.direct("返回附件", 5000), { text: "", attachments: [attachment] });
|
|
775
824
|
});
|
|
825
|
+
(0, node_test_1.default)("ChatGPT 模型取消立即结束等待并保留原队列,普通消息恢复用户会话", async () => {
|
|
826
|
+
const user = { id: "user", host: "h", title: "用户任务" };
|
|
827
|
+
const model = { id: "model", host: "h", title: "模型会话" };
|
|
828
|
+
let active = user;
|
|
829
|
+
let release;
|
|
830
|
+
const calls = [];
|
|
831
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client: {
|
|
832
|
+
connect: async () => undefined, close() { }, connected: () => true,
|
|
833
|
+
listSessions: async () => [user, model], activeSession: async () => active,
|
|
834
|
+
createSession: async () => { calls.push("create"); active = model; return model; },
|
|
835
|
+
switchSession: async (session) => { active = session; },
|
|
836
|
+
direct: async (text) => { calls.push(`${active.id}:${text}`); if (text === "模型提示")
|
|
837
|
+
await new Promise(resolve => { release = resolve; }); return "回复"; },
|
|
838
|
+
} });
|
|
839
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
840
|
+
const controller = new AbortController();
|
|
841
|
+
let error;
|
|
842
|
+
const generating = plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-cancel", timeoutMs: 5000, signal: controller.signal }).catch((value) => { error = value; });
|
|
843
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
844
|
+
const normal = plugin.handle({ task: "用户续聊" });
|
|
845
|
+
controller.abort(new Error("取消模型等待"));
|
|
846
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
847
|
+
const canceledImmediately = Boolean(error);
|
|
848
|
+
const callsBeforeReply = [...calls];
|
|
849
|
+
release();
|
|
850
|
+
await generating;
|
|
851
|
+
await normal;
|
|
852
|
+
await plugin.deactivate();
|
|
853
|
+
strict_1.default.equal(canceledImmediately, true);
|
|
854
|
+
strict_1.default.match(error.stage, /generate/);
|
|
855
|
+
strict_1.default.deepEqual(callsBeforeReply, ["create", "model:模型提示"]);
|
|
856
|
+
strict_1.default.deepEqual(calls, ["create", "model:模型提示", "user:用户续聊"]);
|
|
857
|
+
});
|
|
858
|
+
(0, node_test_1.default)("ChatGPT 模型来源在断线时拒绝且不调用桌面恢复", async () => {
|
|
859
|
+
let connected = true;
|
|
860
|
+
let reconnects = 0;
|
|
861
|
+
let creates = 0;
|
|
862
|
+
const chat = { id: "user", host: "h", title: "用户任务" };
|
|
863
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client: {
|
|
864
|
+
connect: async () => undefined, close() { }, connected: () => connected,
|
|
865
|
+
listSessions: async () => [chat], activeSession: async () => chat,
|
|
866
|
+
createSession: async () => { creates++; return chat; }, switchSession: async () => undefined,
|
|
867
|
+
direct: async () => "回复",
|
|
868
|
+
} });
|
|
869
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
870
|
+
plugin.reconnect = async () => { reconnects++; connected = true; };
|
|
871
|
+
connected = false;
|
|
872
|
+
const error = await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-offline", timeoutMs: 5000, signal: new AbortController().signal }).then(() => undefined, (value) => value);
|
|
873
|
+
await plugin.deactivate();
|
|
874
|
+
strict_1.default.match(String(error), /连接|断开/);
|
|
875
|
+
strict_1.default.equal(reconnects, 0);
|
|
876
|
+
strict_1.default.equal(creates, 0);
|
|
877
|
+
});
|
|
878
|
+
(0, node_test_1.default)("ChatGPT 模型新建、切换和提交时保留用户草稿", async () => {
|
|
879
|
+
for (const operation of ["create", "switch", "direct"]) {
|
|
880
|
+
const client = new chatgpt_1.ChatgptCdpClient();
|
|
881
|
+
const editor = { innerText: "用户正在编辑" };
|
|
882
|
+
let edits = 0, clicks = 0;
|
|
883
|
+
client.recoverDraft = async () => { edits++; editor.innerText = ""; };
|
|
884
|
+
client.waitFor = async () => undefined;
|
|
885
|
+
client.activeSession = async () => undefined;
|
|
886
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, {
|
|
887
|
+
document: { querySelector: (selector) => selector.includes("contenteditable") ? editor : null, querySelectorAll: (selector) => selector.includes("sidebar-thread-id") || selector.startsWith("button,") ? [{ textContent: "新聊天", getAttribute: (key) => key.endsWith("thread-id") ? "model" : key.endsWith("host-id") ? "h" : null, click() { clicks++; } }] : [] },
|
|
888
|
+
});
|
|
889
|
+
const run = () => operation === "create" ? client.createSession(undefined, true) : operation === "switch" ? client.switchSession({ id: "model", host: "h", title: "模型" }, true) : client.directTurn("模型提示", 1, undefined, "slot", "out", Date.now(), undefined, true);
|
|
890
|
+
await strict_1.default.rejects(run(), /未发送内容/);
|
|
891
|
+
strict_1.default.deepEqual({ edits, clicks, text: editor.innerText }, { edits: 0, clicks: 0, text: "用户正在编辑" });
|
|
892
|
+
}
|
|
893
|
+
});
|
|
@@ -47,7 +47,27 @@ const promises_1 = require("node:fs/promises");
|
|
|
47
47
|
const node_os_1 = require("node:os");
|
|
48
48
|
const node_path_1 = require("node:path");
|
|
49
49
|
const node_crypto_1 = require("node:crypto");
|
|
50
|
+
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
50
51
|
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
52
|
+
(0, node_test_1.default)("ABWeb 后台升级调用当前安装的统一升级器并记录启动错误", async (t) => {
|
|
53
|
+
const logs = [];
|
|
54
|
+
const calls = [];
|
|
55
|
+
const child = Object.assign(new node_events_1.EventEmitter(), { unref() { } });
|
|
56
|
+
t.mock.method(node_child_process_1.default, "spawn", (...args) => { calls.push(args); return child; });
|
|
57
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
58
|
+
const server = new control_server_1.ControlServer({ manager: { log: (...args) => logs.push(args) } });
|
|
59
|
+
const request = { method: "POST", url: "/api/update", headers: {}, socket: { remoteAddress: "127.0.0.1" } };
|
|
60
|
+
const response = { statusCode: 0, setHeader() { }, end() { } };
|
|
61
|
+
await server.handle(request, response);
|
|
62
|
+
strict_1.default.equal(response.statusCode, 202);
|
|
63
|
+
strict_1.default.equal(calls.length, 0);
|
|
64
|
+
t.mock.timers.tick(1500);
|
|
65
|
+
strict_1.default.equal(calls.length, 1);
|
|
66
|
+
strict_1.default.equal(calls[0][0], process.execPath);
|
|
67
|
+
strict_1.default.deepEqual(calls[0][1], [(0, node_path_1.join)(__dirname, "../src-ts/cli.js"), "update"]);
|
|
68
|
+
child.emit("error", new Error("无法启动升级进程"));
|
|
69
|
+
strict_1.default.ok(logs.some(line => JSON.stringify(line).includes("无法启动升级进程")));
|
|
70
|
+
});
|
|
51
71
|
(0, node_test_1.default)("桌面重启请求交给 mab 后台执行并保留失败原因", async () => {
|
|
52
72
|
const calls = [];
|
|
53
73
|
const manager = { restartDesktop: async (id) => { calls.push(id); if (id !== "workbuddy")
|
|
@@ -118,6 +138,20 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
118
138
|
await server.stop();
|
|
119
139
|
}
|
|
120
140
|
});
|
|
141
|
+
(0, node_test_1.default)("control server exposes dynamic model provider status without plugin configuration", async () => {
|
|
142
|
+
const modelProviders = [{ id: "qwenwork", displayName: "千问办公", status: "ready", available: true, reason: "" }];
|
|
143
|
+
const manager = { list: () => [], listModelProviders: () => modelProviders };
|
|
144
|
+
const server = new control_server_1.ControlServer({ manager, host: "127.0.0.1", port: 0 });
|
|
145
|
+
const address = await server.start();
|
|
146
|
+
try {
|
|
147
|
+
const response = await fetch(`http://${address.host}:${address.port}/api/model-providers`);
|
|
148
|
+
strict_1.default.equal(response.status, 200);
|
|
149
|
+
strict_1.default.deepEqual(await response.json(), { providers: modelProviders });
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await server.stop();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
121
155
|
(0, node_test_1.default)("信任管理 API 支持查询、变更和拒绝无效配置", async () => {
|
|
122
156
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-trust-api-"));
|
|
123
157
|
const previous = process.env.AGENT_BRIDGE_TRUSTED_PACKAGES;
|
|
@@ -182,6 +216,28 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
182
216
|
await server.stop();
|
|
183
217
|
}
|
|
184
218
|
});
|
|
219
|
+
(0, node_test_1.default)("版本和下载 API 使用已安装插件的真实 npm 包名", async () => {
|
|
220
|
+
const calls = [];
|
|
221
|
+
const record = { id: "personal-assistant", packageName: "agent-bridge-pa", version: "1.0.0" };
|
|
222
|
+
const status = { packageName: "agent-bridge-pa", state: "update-available" };
|
|
223
|
+
const manager = {
|
|
224
|
+
list: () => [record], versions: { get: (name) => name === "agent-bridge-pa" ? status : undefined },
|
|
225
|
+
packageInfo: () => ({ packageName: record.packageName }),
|
|
226
|
+
downloadPackage: async (...args) => { calls.push(args); return "下载目录"; },
|
|
227
|
+
};
|
|
228
|
+
const server = new control_server_1.ControlServer({ manager, host: "127.0.0.1", port: 0 });
|
|
229
|
+
const address = await server.start();
|
|
230
|
+
try {
|
|
231
|
+
const base = `http://${address.host}:${address.port}`;
|
|
232
|
+
strict_1.default.deepEqual((await (await fetch(`${base}/api/versions`)).json()).plugins[0].versionStatus, status);
|
|
233
|
+
const response = await fetch(`${base}/api/plugins/personal-assistant/download`, { method: "POST", body: JSON.stringify({ version: "1.0.1" }) });
|
|
234
|
+
strict_1.default.equal(response.status, 200);
|
|
235
|
+
strict_1.default.deepEqual(calls, [["agent-bridge-pa", "1.0.1"]]);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
await server.stop();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
185
241
|
(0, node_test_1.default)("control server dispatches plugin HTTP routes and unregisters them on stop", async () => {
|
|
186
242
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-http-plugin-"));
|
|
187
243
|
try {
|
|
@@ -4,85 +4,254 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
8
|
const promises_1 = require("node:fs/promises");
|
|
8
9
|
const node_os_1 = require("node:os");
|
|
9
10
|
const node_path_1 = require("node:path");
|
|
10
11
|
const node_test_1 = __importDefault(require("node:test"));
|
|
12
|
+
const node_util_1 = require("node:util");
|
|
11
13
|
const core_updater_1 = require("../src-ts/core-updater");
|
|
12
|
-
|
|
14
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
15
|
+
async function fixture(directory = "测试目录/package", version = "1.0.0") {
|
|
13
16
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-core-update-"));
|
|
14
|
-
const packageRoot = (0, node_path_1.join)(root,
|
|
15
|
-
const globalRoot = (0, node_path_1.join)(root, "node_modules");
|
|
17
|
+
const packageRoot = (0, node_path_1.join)(root, directory);
|
|
16
18
|
await (0, promises_1.mkdir)((0, node_path_1.join)(packageRoot, "dist", "src-ts"), { recursive: true });
|
|
17
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version
|
|
18
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "dist", "src-ts", "cli.js"),
|
|
19
|
-
|
|
19
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version }));
|
|
20
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "dist", "src-ts", "cli.js"), `console.log(${JSON.stringify(version)});`);
|
|
21
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "dist", "obsolete.js"), "旧发布文件");
|
|
22
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "local-config.json"), "保留本地配置");
|
|
23
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(packageRoot, "node_modules", "local-tool"), { recursive: true });
|
|
24
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "node_modules", "local-tool", "index.js"), "保留本地依赖");
|
|
25
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(packageRoot, "plugins", "local-plugin"), { recursive: true });
|
|
26
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(packageRoot, "plugins", "dsh", "9.0.0"), { recursive: true });
|
|
27
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "plugins", "local-plugin", "plugin.json"), "本地插件");
|
|
28
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "plugins", "dsh", "9.0.0", "plugin.json"), "高版本插件");
|
|
29
|
+
return { root, packageRoot, stateDir: (0, node_path_1.join)(root, "state") };
|
|
20
30
|
}
|
|
21
|
-
(
|
|
31
|
+
async function stagePackage(args, version = "1.1.0") {
|
|
32
|
+
const prefixIndex = args.indexOf("--prefix");
|
|
33
|
+
strict_1.default.notEqual(prefixIndex, -1, "安装应在暂存目录执行");
|
|
34
|
+
strict_1.default.ok(args.includes("--install-strategy=nested"), "依赖需要随核心一起迁移");
|
|
35
|
+
const candidate = (0, node_path_1.join)(args[prefixIndex + 1], "node_modules", "mu-agent-bridge");
|
|
36
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(candidate, "dist", "src-ts"), { recursive: true });
|
|
37
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(candidate, "node_modules", "fixture-dependency"), { recursive: true });
|
|
38
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(candidate, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version }));
|
|
39
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(candidate, "dist", "src-ts", "cli.js"), 'console.log(require("../../package.json").version + ":" + require("fixture-dependency"));');
|
|
40
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(candidate, "node_modules", "fixture-dependency", "index.js"), 'module.exports="依赖可用";');
|
|
41
|
+
if (process.platform !== "win32") {
|
|
42
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(candidate, "node_modules", ".bin"));
|
|
43
|
+
await (0, promises_1.symlink)("../fixture-dependency/index.js", (0, node_path_1.join)(candidate, "node_modules", ".bin", "fixture"));
|
|
44
|
+
}
|
|
45
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(candidate, "LICENSE"), "新发布文件");
|
|
46
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(candidate, "plugins", "dsh"), { recursive: true });
|
|
47
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(candidate, "plugins", "dsh", "plugin.json"), "新版内置插件");
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
for (const directory of ["node_modules/mu-agent-bridge", "测试目录/package"]) {
|
|
51
|
+
(0, node_test_1.default)(`${directory} 的低版本升级后仍由原路径运行新核心及依赖`, async () => {
|
|
52
|
+
const value = await fixture(directory);
|
|
53
|
+
const events = [];
|
|
54
|
+
try {
|
|
55
|
+
const updated = await (0, core_updater_1.updateCoreAtStartup)({
|
|
56
|
+
...value,
|
|
57
|
+
npm: async (args) => {
|
|
58
|
+
events.push(args[0]);
|
|
59
|
+
if (args[0] === "root")
|
|
60
|
+
return (0, node_path_1.join)(value.root, "node_modules");
|
|
61
|
+
if (args[0] === "view")
|
|
62
|
+
return '"1.1.0"';
|
|
63
|
+
return stagePackage(args);
|
|
64
|
+
},
|
|
65
|
+
stopDaemon: async () => { events.push("stop"); },
|
|
66
|
+
startDaemon: async () => {
|
|
67
|
+
events.push("start");
|
|
68
|
+
const { stdout } = await execFileAsync(process.execPath, [(0, node_path_1.join)(value.packageRoot, "dist", "src-ts", "cli.js")]);
|
|
69
|
+
strict_1.default.equal(stdout.trim(), "1.1.0:依赖可用");
|
|
70
|
+
},
|
|
71
|
+
waitForHealth: async () => true,
|
|
72
|
+
});
|
|
73
|
+
strict_1.default.equal(updated, true);
|
|
74
|
+
strict_1.default.deepEqual(events, ["view", "install", "stop", "start"]);
|
|
75
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "local-config.json"), "utf8"), "保留本地配置");
|
|
76
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "node_modules", "local-tool", "index.js"), "utf8"), "保留本地依赖");
|
|
77
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "plugins", "local-plugin", "plugin.json"), "utf8"), "本地插件");
|
|
78
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "plugins", "dsh", "9.0.0", "plugin.json"), "utf8"), "高版本插件");
|
|
79
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "plugins", "dsh", "plugin.json"), "utf8"), "新版内置插件");
|
|
80
|
+
if (process.platform !== "win32")
|
|
81
|
+
strict_1.default.equal(await (0, promises_1.readlink)((0, node_path_1.join)(value.packageRoot, "node_modules", ".bin", "fixture")), "../fixture-dependency/index.js");
|
|
82
|
+
await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "dist", "obsolete.js")), { code: "ENOENT" });
|
|
83
|
+
strict_1.default.deepEqual(await (0, promises_1.readdir)(value.stateDir), []);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
for (const currentVersion of ["1.1.0", "9.0.0"]) {
|
|
90
|
+
(0, node_test_1.default)(`${directory} 的当前版本 ${currentVersion} 不被 npmjs 的相同或较低版本覆盖`, async () => {
|
|
91
|
+
const value = await fixture(directory, currentVersion);
|
|
92
|
+
const calls = [];
|
|
93
|
+
try {
|
|
94
|
+
const updated = await (0, core_updater_1.updateCoreAtStartup)({
|
|
95
|
+
...value,
|
|
96
|
+
npm: async (args) => { calls.push(args[0]); return args[0] === "root" ? (0, node_path_1.join)(value.root, "node_modules") : '"1.1.0"'; },
|
|
97
|
+
stopDaemon: async () => { strict_1.default.fail("不应停止当前后台"); },
|
|
98
|
+
startDaemon: async () => { strict_1.default.fail("不应启动其他版本"); },
|
|
99
|
+
waitForHealth: async () => true,
|
|
100
|
+
});
|
|
101
|
+
strict_1.default.equal(updated, false);
|
|
102
|
+
strict_1.default.deepEqual(calls, ["view"]);
|
|
103
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, currentVersion);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
(0, node_test_1.default)("新 daemon 健康检查失败时恢复原路径的旧核心及文件", async () => {
|
|
22
112
|
const value = await fixture();
|
|
23
113
|
const events = [];
|
|
114
|
+
let healthChecks = 0;
|
|
24
115
|
try {
|
|
25
116
|
const updated = await (0, core_updater_1.updateCoreAtStartup)({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stateDir: (0, node_path_1.join)(value.root, "state"),
|
|
29
|
-
npm: async (args) => {
|
|
30
|
-
events.push(args.join(" "));
|
|
31
|
-
if (args[0] === "view")
|
|
32
|
-
return '"1.1.0"';
|
|
33
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.1.0" }));
|
|
34
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "dist", "src-ts", "cli.js"), "new");
|
|
35
|
-
return "";
|
|
36
|
-
},
|
|
117
|
+
...value,
|
|
118
|
+
npm: async (args) => args[0] === "root" ? (0, node_path_1.join)(value.root, "node_modules") : args[0] === "view" ? '"1.1.0"' : stagePackage(args),
|
|
37
119
|
stopDaemon: async () => { events.push("stop"); },
|
|
38
|
-
startDaemon: async () => {
|
|
39
|
-
|
|
120
|
+
startDaemon: async () => {
|
|
121
|
+
const { stdout } = await execFileAsync(process.execPath, [(0, node_path_1.join)(value.packageRoot, "dist", "src-ts", "cli.js")]);
|
|
122
|
+
events.push(stdout.trim());
|
|
123
|
+
},
|
|
124
|
+
waitForHealth: async () => ++healthChecks > 1,
|
|
40
125
|
});
|
|
41
|
-
strict_1.default.equal(updated,
|
|
42
|
-
strict_1.default.
|
|
43
|
-
strict_1.default.
|
|
126
|
+
strict_1.default.equal(updated, false);
|
|
127
|
+
strict_1.default.deepEqual(events, ["stop", "1.1.0:依赖可用", "stop", "1.0.0"]);
|
|
128
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "dist", "obsolete.js"), "utf8"), "旧发布文件");
|
|
129
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "local-config.json"), "utf8"), "保留本地配置");
|
|
130
|
+
await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "LICENSE")), { code: "ENOENT" });
|
|
44
131
|
}
|
|
45
132
|
finally {
|
|
46
133
|
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
47
134
|
}
|
|
48
135
|
});
|
|
49
|
-
(0, node_test_1.default)("
|
|
136
|
+
(0, node_test_1.default)("并发检查遇到已有进程的升级锁时中止,避免启动入口继续覆盖后台", async () => {
|
|
50
137
|
const value = await fixture();
|
|
51
|
-
const
|
|
138
|
+
const lock = (0, node_path_1.join)(value.stateDir, "core-update.lock");
|
|
139
|
+
await (0, promises_1.mkdir)(lock, { recursive: true });
|
|
140
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(lock, String(process.pid)), "");
|
|
141
|
+
try {
|
|
142
|
+
await strict_1.default.rejects((0, core_updater_1.updateCoreAtStartup)({
|
|
143
|
+
...value,
|
|
144
|
+
npm: async () => strict_1.default.fail("锁冲突时不能调用 npm"),
|
|
145
|
+
stopDaemon: async () => strict_1.default.fail("锁冲突时不能停止后台"),
|
|
146
|
+
startDaemon: async () => strict_1.default.fail("锁冲突时不能启动后台"),
|
|
147
|
+
waitForHealth: async () => false,
|
|
148
|
+
}), /已有核心升级检查正在运行/);
|
|
149
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.0.0");
|
|
150
|
+
strict_1.default.deepEqual(await (0, promises_1.readdir)(lock), [String(process.pid)]);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
(0, node_test_1.default)("回滚后的旧后台仍不健康时清理本次进程并保留备份", async () => {
|
|
157
|
+
const value = await fixture();
|
|
158
|
+
const stops = [];
|
|
159
|
+
let launches = 0;
|
|
160
|
+
try {
|
|
161
|
+
await strict_1.default.rejects((0, core_updater_1.updateCoreAtStartup)({
|
|
162
|
+
...value,
|
|
163
|
+
npm: async (args) => args[0] === "view" ? '"1.1.0"' : stagePackage(args),
|
|
164
|
+
stopDaemon: async (pid) => { stops.push(pid); },
|
|
165
|
+
startDaemon: async () => ({ pid: 1000 + ++launches }),
|
|
166
|
+
waitForHealth: async () => false,
|
|
167
|
+
}), /旧 daemon 恢复后的健康检查失败/);
|
|
168
|
+
strict_1.default.deepEqual(stops, [undefined, 1001, 1002]);
|
|
169
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.0.0");
|
|
170
|
+
const remaining = await (0, promises_1.readdir)(value.stateDir);
|
|
171
|
+
strict_1.default.equal(remaining.length, 1);
|
|
172
|
+
strict_1.default.match(remaining[0], /^mab-core-update-/);
|
|
173
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.stateDir, remaining[0], "backup", "package.json"), "utf8")).version, "1.0.0");
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
(0, node_test_1.default)("退出进程遗留的升级锁由下一次检查清理", async () => {
|
|
180
|
+
const value = await fixture();
|
|
181
|
+
const lock = (0, node_path_1.join)(value.stateDir, "core-update.lock");
|
|
182
|
+
await (0, promises_1.mkdir)(lock, { recursive: true });
|
|
183
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(lock, "2147483647"), "");
|
|
184
|
+
let views = 0;
|
|
52
185
|
try {
|
|
53
186
|
const updated = await (0, core_updater_1.updateCoreAtStartup)({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return '"1.1.0"';
|
|
60
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.1.0" }));
|
|
61
|
-
return "";
|
|
62
|
-
},
|
|
63
|
-
stopDaemon: async () => { events.push("stop"); },
|
|
64
|
-
startDaemon: async () => { events.push("start"); },
|
|
65
|
-
waitForHealth: async () => { events.push("health"); return false; },
|
|
187
|
+
...value,
|
|
188
|
+
npm: async () => { views++; return '"1.0.0"'; },
|
|
189
|
+
stopDaemon: async () => strict_1.default.fail("版本相同时不能停止后台"),
|
|
190
|
+
startDaemon: async () => strict_1.default.fail("版本相同时不能启动后台"),
|
|
191
|
+
waitForHealth: async () => false,
|
|
66
192
|
});
|
|
67
193
|
strict_1.default.equal(updated, false);
|
|
68
|
-
strict_1.default.equal(
|
|
69
|
-
strict_1.default.deepEqual(
|
|
194
|
+
strict_1.default.equal(views, 1);
|
|
195
|
+
strict_1.default.deepEqual(await (0, promises_1.readdir)(value.stateDir), []);
|
|
70
196
|
}
|
|
71
197
|
finally {
|
|
72
198
|
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
73
199
|
}
|
|
74
200
|
});
|
|
75
|
-
(0, node_test_1.default)("
|
|
201
|
+
(0, node_test_1.default)("两个检查并发时只有锁持有者可以调用 npm", async () => {
|
|
76
202
|
const value = await fixture();
|
|
77
|
-
let
|
|
203
|
+
let release;
|
|
204
|
+
let entered;
|
|
205
|
+
const pending = new Promise(done => { release = done; });
|
|
206
|
+
const started = new Promise(done => { entered = done; });
|
|
207
|
+
let checks = 0;
|
|
208
|
+
const options = {
|
|
209
|
+
...value,
|
|
210
|
+
npm: async () => { checks++; entered(); await pending; return '"1.0.0"'; },
|
|
211
|
+
stopDaemon: async () => strict_1.default.fail("版本相同时不能停止后台"),
|
|
212
|
+
startDaemon: async () => strict_1.default.fail("版本相同时不能启动后台"),
|
|
213
|
+
waitForHealth: async () => false,
|
|
214
|
+
};
|
|
215
|
+
const first = (0, core_updater_1.updateCoreAtStartup)(options);
|
|
78
216
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
strict_1.default.equal(
|
|
82
|
-
strict_1.default.equal(local, false);
|
|
83
|
-
strict_1.default.equal(stops, 0);
|
|
217
|
+
await started;
|
|
218
|
+
await strict_1.default.rejects((0, core_updater_1.updateCoreAtStartup)(options), /已有核心升级检查正在运行/);
|
|
219
|
+
strict_1.default.equal(checks, 1);
|
|
84
220
|
}
|
|
85
221
|
finally {
|
|
222
|
+
release();
|
|
223
|
+
await first;
|
|
86
224
|
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
87
225
|
}
|
|
88
226
|
});
|
|
227
|
+
for (const failure of ["下载失败", "包版本不匹配"]) {
|
|
228
|
+
(0, node_test_1.default)(`${failure} 时保留原后台和安装内容`, async () => {
|
|
229
|
+
const value = await fixture();
|
|
230
|
+
const logs = [];
|
|
231
|
+
try {
|
|
232
|
+
const updated = await (0, core_updater_1.updateCoreAtStartup)({
|
|
233
|
+
...value,
|
|
234
|
+
npm: async (args) => {
|
|
235
|
+
if (args[0] === "root")
|
|
236
|
+
return (0, node_path_1.join)(value.root, "node_modules");
|
|
237
|
+
if (args[0] === "view")
|
|
238
|
+
return '"1.1.0"';
|
|
239
|
+
if (failure === "下载失败")
|
|
240
|
+
throw new Error(failure);
|
|
241
|
+
return stagePackage(args, "1.0.9");
|
|
242
|
+
},
|
|
243
|
+
stopDaemon: async () => { strict_1.default.fail("下载和验证完成前不能停止后台"); },
|
|
244
|
+
startDaemon: async () => { strict_1.default.fail("不应启动无效包"); },
|
|
245
|
+
waitForHealth: async () => true,
|
|
246
|
+
log: (_level, message, fields) => { logs.push(message + JSON.stringify(fields)); },
|
|
247
|
+
});
|
|
248
|
+
strict_1.default.equal(updated, false);
|
|
249
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.0.0");
|
|
250
|
+
strict_1.default.ok(logs.some(line => /下载失败|版本不匹配/.test(line)), JSON.stringify(logs));
|
|
251
|
+
strict_1.default.deepEqual(await (0, promises_1.readdir)(value.stateDir), []);
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
await (0, promises_1.rm)(value.root, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|