mu-agent-bridge 1.0.71 → 1.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/control-server.js +5 -2
- package/dist/src-ts/dsh-agent.js +25 -12
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +183 -9
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +68 -2
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/index.js +66 -10
- package/dist/src-ts/plugins/aun/messages.js +3 -0
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +77 -17
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
- package/dist/src-ts/plugins/doubao/index.js +36 -4
- package/dist/src-ts/plugins/dsh/index.js +78 -6
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +242 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
- package/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/dist/src-ts/session-management.js +105 -6
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +70 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-messages.test.js +6 -0
- package/dist/tests-ts/aun-plugin.test.js +85 -0
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +99 -0
- package/dist/tests-ts/control-server.test.js +36 -0
- package/dist/tests-ts/doubao-plugin.test.js +92 -0
- package/dist/tests-ts/dsh-agent.test.js +77 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +119 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +150 -0
- package/dist/tests-ts/personal-assistant-coordination.test.js +76 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +29 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +389 -0
- package/dist/tests-ts/platform-integration.test.js +124 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +280 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +105 -1
- package/dist/tests-ts/session-management.test.js +155 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +154 -2
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +68 -2
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +53 -3
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +66 -10
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +3 -0
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +77 -17
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +36 -4
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +25 -12
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +78 -6
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/personal-assistant/config.schema.json +22 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +242 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
|
@@ -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,6 +38,7 @@ 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");
|
|
42
43
|
(0, node_test_1.default)("ChatGPT 插件通过已连接 CDP 客户端转发请求", async () => {
|
|
43
44
|
const calls = [];
|
|
@@ -94,6 +95,35 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
|
94
95
|
await rm(dataDir, { recursive: true, force: true });
|
|
95
96
|
}
|
|
96
97
|
});
|
|
98
|
+
(0, node_test_1.default)("ChatGPT 模型来源为个人助理建立专用会话,不复用桌面当前会话", async () => {
|
|
99
|
+
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
100
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
101
|
+
const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
102
|
+
const dataDir = await mkdtemp(join(tmpdir(), "chatgpt-model-provider-"));
|
|
103
|
+
const existing = { id: "user-chat", host: "h", title: "用户会话" };
|
|
104
|
+
const dedicated = { id: "pa-chat", host: "h", title: "个人助理模型" };
|
|
105
|
+
let active = existing;
|
|
106
|
+
const calls = [];
|
|
107
|
+
const client = {
|
|
108
|
+
connect: async () => undefined, close() { }, connected: () => true,
|
|
109
|
+
listSessions: async () => [existing, dedicated],
|
|
110
|
+
activeSession: async () => active,
|
|
111
|
+
createSession: async () => { active = dedicated; return dedicated; },
|
|
112
|
+
switchSession: async (session) => { active = session; },
|
|
113
|
+
direct: async (text) => { calls.push(`${active.id}:${text}`); return "模型 JSON"; },
|
|
114
|
+
};
|
|
115
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client });
|
|
116
|
+
try {
|
|
117
|
+
await plugin.activate({ config: {}, dataDir, logger: { info() { }, warn() { }, error() { } } });
|
|
118
|
+
strict_1.default.deepEqual(await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-thread", timeoutMs: 5000, signal: new AbortController().signal }), { text: "模型 JSON" });
|
|
119
|
+
strict_1.default.deepEqual(calls, ["pa-chat:模型提示"]);
|
|
120
|
+
strict_1.default.equal(active.id, "user-chat");
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await plugin.deactivate();
|
|
124
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
97
127
|
(0, node_test_1.default)("ChatGPT 一个来源待确认的新会话不阻塞其他来源", async () => {
|
|
98
128
|
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
99
129
|
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
@@ -773,3 +803,72 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
|
773
803
|
});
|
|
774
804
|
strict_1.default.deepEqual(await client.direct("返回附件", 5000), { text: "", attachments: [attachment] });
|
|
775
805
|
});
|
|
806
|
+
(0, node_test_1.default)("ChatGPT 模型取消立即结束等待并保留原队列,普通消息恢复用户会话", async () => {
|
|
807
|
+
const user = { id: "user", host: "h", title: "用户任务" };
|
|
808
|
+
const model = { id: "model", host: "h", title: "模型会话" };
|
|
809
|
+
let active = user;
|
|
810
|
+
let release;
|
|
811
|
+
const calls = [];
|
|
812
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client: {
|
|
813
|
+
connect: async () => undefined, close() { }, connected: () => true,
|
|
814
|
+
listSessions: async () => [user, model], activeSession: async () => active,
|
|
815
|
+
createSession: async () => { calls.push("create"); active = model; return model; },
|
|
816
|
+
switchSession: async (session) => { active = session; },
|
|
817
|
+
direct: async (text) => { calls.push(`${active.id}:${text}`); if (text === "模型提示")
|
|
818
|
+
await new Promise(resolve => { release = resolve; }); return "回复"; },
|
|
819
|
+
} });
|
|
820
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
821
|
+
const controller = new AbortController();
|
|
822
|
+
let error;
|
|
823
|
+
const generating = plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-cancel", timeoutMs: 5000, signal: controller.signal }).catch((value) => { error = value; });
|
|
824
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
825
|
+
const normal = plugin.handle({ task: "用户续聊" });
|
|
826
|
+
controller.abort(new Error("取消模型等待"));
|
|
827
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
828
|
+
const canceledImmediately = Boolean(error);
|
|
829
|
+
const callsBeforeReply = [...calls];
|
|
830
|
+
release();
|
|
831
|
+
await generating;
|
|
832
|
+
await normal;
|
|
833
|
+
await plugin.deactivate();
|
|
834
|
+
strict_1.default.equal(canceledImmediately, true);
|
|
835
|
+
strict_1.default.match(error.stage, /generate/);
|
|
836
|
+
strict_1.default.deepEqual(callsBeforeReply, ["create", "model:模型提示"]);
|
|
837
|
+
strict_1.default.deepEqual(calls, ["create", "model:模型提示", "user:用户续聊"]);
|
|
838
|
+
});
|
|
839
|
+
(0, node_test_1.default)("ChatGPT 模型来源在断线时拒绝且不调用桌面恢复", async () => {
|
|
840
|
+
let connected = true;
|
|
841
|
+
let reconnects = 0;
|
|
842
|
+
let creates = 0;
|
|
843
|
+
const chat = { id: "user", host: "h", title: "用户任务" };
|
|
844
|
+
const plugin = new chatgpt_1.ChatgptPlugin({ client: {
|
|
845
|
+
connect: async () => undefined, close() { }, connected: () => connected,
|
|
846
|
+
listSessions: async () => [chat], activeSession: async () => chat,
|
|
847
|
+
createSession: async () => { creates++; return chat; }, switchSession: async () => undefined,
|
|
848
|
+
direct: async () => "回复",
|
|
849
|
+
} });
|
|
850
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
851
|
+
plugin.reconnect = async () => { reconnects++; connected = true; };
|
|
852
|
+
connected = false;
|
|
853
|
+
const error = await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-offline", timeoutMs: 5000, signal: new AbortController().signal }).then(() => undefined, (value) => value);
|
|
854
|
+
await plugin.deactivate();
|
|
855
|
+
strict_1.default.match(String(error), /连接|断开/);
|
|
856
|
+
strict_1.default.equal(reconnects, 0);
|
|
857
|
+
strict_1.default.equal(creates, 0);
|
|
858
|
+
});
|
|
859
|
+
(0, node_test_1.default)("ChatGPT 模型新建、切换和提交时保留用户草稿", async () => {
|
|
860
|
+
for (const operation of ["create", "switch", "direct"]) {
|
|
861
|
+
const client = new chatgpt_1.ChatgptCdpClient();
|
|
862
|
+
const editor = { innerText: "用户正在编辑" };
|
|
863
|
+
let edits = 0, clicks = 0;
|
|
864
|
+
client.recoverDraft = async () => { edits++; editor.innerText = ""; };
|
|
865
|
+
client.waitFor = async () => undefined;
|
|
866
|
+
client.activeSession = async () => undefined;
|
|
867
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, {
|
|
868
|
+
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++; } }] : [] },
|
|
869
|
+
});
|
|
870
|
+
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);
|
|
871
|
+
await strict_1.default.rejects(run(), /未发送内容/);
|
|
872
|
+
strict_1.default.deepEqual({ edits, clicks, text: editor.innerText }, { edits: 0, clicks: 0, text: "用户正在编辑" });
|
|
873
|
+
}
|
|
874
|
+
});
|
|
@@ -118,6 +118,20 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
118
118
|
await server.stop();
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
|
+
(0, node_test_1.default)("control server exposes dynamic model provider status without plugin configuration", async () => {
|
|
122
|
+
const modelProviders = [{ id: "qwenwork", displayName: "千问办公", status: "ready", available: true, reason: "" }];
|
|
123
|
+
const manager = { list: () => [], listModelProviders: () => modelProviders };
|
|
124
|
+
const server = new control_server_1.ControlServer({ manager, host: "127.0.0.1", port: 0 });
|
|
125
|
+
const address = await server.start();
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(`http://${address.host}:${address.port}/api/model-providers`);
|
|
128
|
+
strict_1.default.equal(response.status, 200);
|
|
129
|
+
strict_1.default.deepEqual(await response.json(), { providers: modelProviders });
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await server.stop();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
121
135
|
(0, node_test_1.default)("信任管理 API 支持查询、变更和拒绝无效配置", async () => {
|
|
122
136
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-trust-api-"));
|
|
123
137
|
const previous = process.env.AGENT_BRIDGE_TRUSTED_PACKAGES;
|
|
@@ -182,6 +196,28 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
182
196
|
await server.stop();
|
|
183
197
|
}
|
|
184
198
|
});
|
|
199
|
+
(0, node_test_1.default)("版本和下载 API 使用已安装插件的真实 npm 包名", async () => {
|
|
200
|
+
const calls = [];
|
|
201
|
+
const record = { id: "personal-assistant", packageName: "agent-bridge-pa", version: "1.0.0" };
|
|
202
|
+
const status = { packageName: "agent-bridge-pa", state: "update-available" };
|
|
203
|
+
const manager = {
|
|
204
|
+
list: () => [record], versions: { get: (name) => name === "agent-bridge-pa" ? status : undefined },
|
|
205
|
+
packageInfo: () => ({ packageName: record.packageName }),
|
|
206
|
+
downloadPackage: async (...args) => { calls.push(args); return "下载目录"; },
|
|
207
|
+
};
|
|
208
|
+
const server = new control_server_1.ControlServer({ manager, host: "127.0.0.1", port: 0 });
|
|
209
|
+
const address = await server.start();
|
|
210
|
+
try {
|
|
211
|
+
const base = `http://${address.host}:${address.port}`;
|
|
212
|
+
strict_1.default.deepEqual((await (await fetch(`${base}/api/versions`)).json()).plugins[0].versionStatus, status);
|
|
213
|
+
const response = await fetch(`${base}/api/plugins/personal-assistant/download`, { method: "POST", body: JSON.stringify({ version: "1.0.1" }) });
|
|
214
|
+
strict_1.default.equal(response.status, 200);
|
|
215
|
+
strict_1.default.deepEqual(calls, [["agent-bridge-pa", "1.0.1"]]);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
await server.stop();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
185
221
|
(0, node_test_1.default)("control server dispatches plugin HTTP routes and unregisters them on stop", async () => {
|
|
186
222
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-http-plugin-"));
|
|
187
223
|
try {
|
|
@@ -123,6 +123,27 @@ function deferred() {
|
|
|
123
123
|
await legacy.deactivate();
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
|
+
(0, node_test_1.default)("豆包模型来源由服务端创建独立会话,不切换用户桌面", async () => {
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
const calls = [];
|
|
129
|
+
const client = {
|
|
130
|
+
async connect() { }, async close() { }, connected: () => true, direct: async () => "不应使用普通入口",
|
|
131
|
+
createSession: async () => { calls.push("create"); },
|
|
132
|
+
complete: async (text, signal, _items, conversationId) => {
|
|
133
|
+
calls.push([text, signal, conversationId]);
|
|
134
|
+
return { text: "模型 JSON", sessionId: "314159" };
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const plugin = new doubao_1.DoubaoPlugin(client);
|
|
138
|
+
try {
|
|
139
|
+
await plugin.activate({ config: {} });
|
|
140
|
+
strict_1.default.deepEqual(await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-thread", timeoutMs: 5000, signal: controller.signal }), { text: "模型 JSON" });
|
|
141
|
+
strict_1.default.deepEqual(calls, [["模型提示", controller.signal, ""]]);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await plugin.deactivate();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
126
147
|
(0, node_test_1.default)("豆包在访问页面前明确拒绝视频与不支持的文件类型", async () => {
|
|
127
148
|
const client = new cdp_client_1.DoubaoCdpClient();
|
|
128
149
|
for (const [filename, contentType, error] of [["clip.mp4", "video/mp4", /不支持视频/], ["archive.zip", "application/zip", /不支持.*文件类型/]]) {
|
|
@@ -754,3 +775,74 @@ for (const late of ["成功", "失败"]) {
|
|
|
754
775
|
await plugin.deactivate();
|
|
755
776
|
}
|
|
756
777
|
});
|
|
778
|
+
(0, node_test_1.default)("豆包模型排队取消不新建会话,生成取消后仍等待底层结束", async () => {
|
|
779
|
+
let release;
|
|
780
|
+
const calls = [];
|
|
781
|
+
const plugin = new doubao_1.DoubaoPlugin({
|
|
782
|
+
connect: async () => undefined, close: async () => undefined, connected: () => true,
|
|
783
|
+
direct: async () => "回复", createSession: async () => { calls.push("create"); },
|
|
784
|
+
complete: async (text) => { calls.push(text); if (text === "模型提示")
|
|
785
|
+
await new Promise(resolve => { release = resolve; }); return { text: "回复", sessionId: "123" }; },
|
|
786
|
+
});
|
|
787
|
+
await plugin.activate({ config: {} });
|
|
788
|
+
const activeController = new AbortController();
|
|
789
|
+
let error;
|
|
790
|
+
const generating = plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-active", timeoutMs: 5000, signal: activeController.signal }).catch((value) => { error = value; });
|
|
791
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
792
|
+
const queuedController = new AbortController();
|
|
793
|
+
let queuedError;
|
|
794
|
+
const queued = plugin.modelProvider.complete({ prompt: "不应发送", sessionId: "pa-queued", timeoutMs: 5000, signal: queuedController.signal }).catch((value) => { queuedError = value; });
|
|
795
|
+
activeController.abort(new Error("取消生成等待"));
|
|
796
|
+
queuedController.abort(new Error("取消排队等待"));
|
|
797
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
798
|
+
const rejectedImmediately = Boolean(error && queuedError);
|
|
799
|
+
const before = [...calls];
|
|
800
|
+
release();
|
|
801
|
+
await Promise.all([generating, queued]);
|
|
802
|
+
await plugin.handle({ task: "普通任务" });
|
|
803
|
+
await plugin.deactivate();
|
|
804
|
+
strict_1.default.equal(rejectedImmediately, true);
|
|
805
|
+
strict_1.default.match(error.stage, /generate/);
|
|
806
|
+
strict_1.default.match(queuedError.stage, /queue/);
|
|
807
|
+
strict_1.default.deepEqual(before, ["模型提示"]);
|
|
808
|
+
strict_1.default.deepEqual(calls, ["模型提示", "普通任务"]);
|
|
809
|
+
});
|
|
810
|
+
(0, node_test_1.default)("豆包模型断线时不调用可能重启桌面的恢复入口", async () => {
|
|
811
|
+
let connected = true;
|
|
812
|
+
let reconnects = 0;
|
|
813
|
+
let creates = 0;
|
|
814
|
+
const plugin = new doubao_1.DoubaoPlugin({
|
|
815
|
+
connect: async () => undefined, close: async () => undefined, connected: () => connected,
|
|
816
|
+
direct: async () => "回复", createSession: async () => { creates++; }, complete: async () => ({ text: "回复", sessionId: "123" }),
|
|
817
|
+
});
|
|
818
|
+
await plugin.activate({ config: {} });
|
|
819
|
+
plugin.reconnect = async () => { reconnects++; connected = true; };
|
|
820
|
+
connected = false;
|
|
821
|
+
const error = await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-offline", timeoutMs: 5000, signal: new AbortController().signal }).then(() => undefined, (value) => value);
|
|
822
|
+
await plugin.deactivate();
|
|
823
|
+
strict_1.default.ok(error);
|
|
824
|
+
strict_1.default.equal(reconnects, 0);
|
|
825
|
+
strict_1.default.equal(creates, 0);
|
|
826
|
+
});
|
|
827
|
+
(0, node_test_1.default)("豆包取消后仍读取真实页面请求的迟到结果,不提前释放内部队列", async (t) => {
|
|
828
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
829
|
+
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 10 });
|
|
830
|
+
const controller = new AbortController();
|
|
831
|
+
let polled = false;
|
|
832
|
+
let settled = false;
|
|
833
|
+
client.connect = async () => undefined;
|
|
834
|
+
client.evaluate = async (expression) => {
|
|
835
|
+
if (expression === "window.__mab_doubao_result") {
|
|
836
|
+
polled = true;
|
|
837
|
+
return JSON.stringify({ ok: true, reply: "迟到回复", conversationId: "123" });
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
const running = client.complete("模型提示", controller.signal, undefined, "").then((value) => { settled = true; return value; });
|
|
841
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
842
|
+
controller.abort(new Error("停止等待"));
|
|
843
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
844
|
+
strict_1.default.equal(settled, false);
|
|
845
|
+
t.mock.timers.tick(1000);
|
|
846
|
+
strict_1.default.deepEqual(await running, { text: "迟到回复", sessionId: "123" });
|
|
847
|
+
strict_1.default.equal(polled, true);
|
|
848
|
+
});
|
|
@@ -77,6 +77,18 @@ function fakeSpawn(_file, _args, _options) {
|
|
|
77
77
|
strict_1.default.equal(captured.includes("--session-id"), false);
|
|
78
78
|
strict_1.default.equal(response.sessionId, "session-1");
|
|
79
79
|
});
|
|
80
|
+
(0, node_test_1.default)("DSH 模型入口只发送决策提示,不追加普通附件任务指令", async () => {
|
|
81
|
+
let input = "";
|
|
82
|
+
const spawn = (file, args, options) => {
|
|
83
|
+
const child = fakeSpawn(file, args, options);
|
|
84
|
+
const end = child.stdin.end;
|
|
85
|
+
child.stdin.end = (task) => { input = task; end(task); };
|
|
86
|
+
return child;
|
|
87
|
+
};
|
|
88
|
+
const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn });
|
|
89
|
+
await agent.send({ task: "只返回结构化决策" }, true);
|
|
90
|
+
strict_1.default.equal(input, "只返回结构化决策");
|
|
91
|
+
});
|
|
80
92
|
(0, node_test_1.default)("DSH 新会话缺少真实 ID 时拒绝用逻辑 ID 冒充创建成功", async () => {
|
|
81
93
|
const spawn = () => {
|
|
82
94
|
const child = fakeSpawn("", [], {});
|
|
@@ -89,6 +101,25 @@ function fakeSpawn(_file, _args, _options) {
|
|
|
89
101
|
const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn });
|
|
90
102
|
await strict_1.default.rejects(agent.send({ task: "首条消息", sessionId: "logical-id", newSession: true }), /未返回真实新会话标识/);
|
|
91
103
|
});
|
|
104
|
+
(0, node_test_1.default)("DSH 取消时只终止本次启动的 CLI 子进程", async () => {
|
|
105
|
+
let child;
|
|
106
|
+
let killed = 0;
|
|
107
|
+
const spawn = () => {
|
|
108
|
+
child = new node_events_1.EventEmitter();
|
|
109
|
+
child.stdin = { end: () => undefined };
|
|
110
|
+
child.stdout = new node_events_1.EventEmitter();
|
|
111
|
+
child.stderr = new node_events_1.EventEmitter();
|
|
112
|
+
child.kill = () => { killed++; queueMicrotask(() => child.emit("close", null)); return true; };
|
|
113
|
+
return child;
|
|
114
|
+
};
|
|
115
|
+
const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn, defaultTimeoutMs: 20 });
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
const task = agent.send({ task: "长任务", signal: controller.signal });
|
|
118
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
119
|
+
controller.abort(new Error("模型请求已取消"));
|
|
120
|
+
await strict_1.default.rejects(task, /模型请求已取消/);
|
|
121
|
+
strict_1.default.equal(killed, 1);
|
|
122
|
+
});
|
|
92
123
|
(0, node_test_1.default)("DSH 附件先落盘供真实工具读取,成功 present 后回传原字节与 MIME 并清理临时目录", async () => {
|
|
93
124
|
const { readFile, copyFile, access } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
94
125
|
const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
@@ -255,3 +286,49 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
255
286
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
256
287
|
}
|
|
257
288
|
});
|
|
289
|
+
(0, node_test_1.default)("DSH 模型排队取消立即返回,下一请求在原 CLI 关闭后继续", async () => {
|
|
290
|
+
const plugin = new dsh_1.DshPlugin();
|
|
291
|
+
let release;
|
|
292
|
+
const sent = [];
|
|
293
|
+
plugin.status = "ready";
|
|
294
|
+
plugin.agent = { send: async (request) => { sent.push(request); if (request.task === "用户任务")
|
|
295
|
+
await new Promise(resolve => { release = resolve; }); return { text: "回复", sessionId: "desktop-session" }; } };
|
|
296
|
+
const running = plugin.handle({ task: "用户任务" });
|
|
297
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
298
|
+
const controller = new AbortController();
|
|
299
|
+
let error;
|
|
300
|
+
const model = plugin.modelProvider.complete({ prompt: "不应发送", sessionId: "pa-queued", timeoutMs: 5000, signal: controller.signal }).catch((value) => { error = value; });
|
|
301
|
+
controller.abort(new Error("排队取消"));
|
|
302
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
303
|
+
const canceledImmediately = Boolean(error);
|
|
304
|
+
release();
|
|
305
|
+
await Promise.all([running, model]);
|
|
306
|
+
await plugin.handle({ task: "用户续聊" });
|
|
307
|
+
await plugin.deactivate();
|
|
308
|
+
strict_1.default.equal(canceledImmediately, true);
|
|
309
|
+
strict_1.default.match(error.stage, /queue/);
|
|
310
|
+
strict_1.default.deepEqual(sent, [{ task: "用户任务" }, { task: "用户续聊" }]);
|
|
311
|
+
});
|
|
312
|
+
(0, node_test_1.default)("DSH 模型会话在重载后恢复,普通任务不写入模型映射", async () => {
|
|
313
|
+
const directory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "dsh-model-session-"));
|
|
314
|
+
const executablePath = (0, node_path_1.join)(directory, "DeepSeek Harness.exe");
|
|
315
|
+
await (0, promises_1.writeFile)(executablePath, "");
|
|
316
|
+
const requests = [];
|
|
317
|
+
try {
|
|
318
|
+
for (let round = 0; round < 2; round++) {
|
|
319
|
+
const plugin = new dsh_1.DshPlugin();
|
|
320
|
+
await plugin.activate({ config: { executablePath }, dataDir: directory });
|
|
321
|
+
plugin.agent = { send: async (request) => { requests.push(request); return { text: "模型回复", sessionId: "native-model-session" }; } };
|
|
322
|
+
await plugin.handle({ task: "普通用户任务", sessionId: "ordinary-session" });
|
|
323
|
+
await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-logical", timeoutMs: 1000, signal: new AbortController().signal });
|
|
324
|
+
await plugin.deactivate();
|
|
325
|
+
}
|
|
326
|
+
strict_1.default.equal(requests[1].newSession, true);
|
|
327
|
+
strict_1.default.equal(requests[3].sessionId, "native-model-session");
|
|
328
|
+
strict_1.default.equal(requests[3].newSession, undefined);
|
|
329
|
+
strict_1.default.deepEqual(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "model-provider-sessions.json"), "utf8")), { "pa-logical": "native-model-session" });
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
await (0, promises_1.rm)(directory, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
@@ -231,7 +231,7 @@ const feishu_1 = require("../src-ts/plugins/feishu");
|
|
|
231
231
|
const manifest = JSON.parse(response.body);
|
|
232
232
|
strict_1.default.deepEqual(manifest.commands.map((command) => command.name), ["status", "list", "agents", "configure", "bind", "unbind"]);
|
|
233
233
|
strict_1.default.equal(manifest.protocolVersion, 1);
|
|
234
|
-
strict_1.default.equal(manifest.version, "1.0.
|
|
234
|
+
strict_1.default.equal(manifest.version, "1.0.7");
|
|
235
235
|
const run = async (...argv) => {
|
|
236
236
|
response.statusCode = 0;
|
|
237
237
|
await postCommands(node_stream_1.Readable.from([JSON.stringify({ protocolVersion: 1, argv })]), response);
|
|
@@ -48,7 +48,7 @@ const node_util_1 = require("node:util");
|
|
|
48
48
|
strict_1.default.match(setup, /Get-Command npm\.cmd/);
|
|
49
49
|
strict_1.default.match(setup, /\$npmCommand\.Source prefix --global/);
|
|
50
50
|
strict_1.default.match(setup, /@\(\$nodeDirectory, \$npmDirectory, \$npmPrefix\)/);
|
|
51
|
-
strict_1.default.match(setup, /mu-agent-bridge
|
|
51
|
+
strict_1.default.match(setup, /mu-agent-bridge@\{1\} --engine-strict/);
|
|
52
52
|
strict_1.default.match(setup, /--prefer-online/);
|
|
53
53
|
strict_1.default.match(setup, /npm 安装遇到版本尚未同步或文件占用.*重试/);
|
|
54
54
|
strict_1.default.doesNotMatch(setup, /mu-agent-bridge@\d+\.\d+\.\d+/);
|
|
@@ -16,7 +16,7 @@ const fixture = String.raw `
|
|
|
16
16
|
const fs = require('node:fs'); const {randomUUID}=require('node:crypto');
|
|
17
17
|
const args=process.argv.slice(2);
|
|
18
18
|
if(args.includes('--version')) { console.log('1.0.0'); process.exit(0); }
|
|
19
|
-
if(args.includes('--help')) { console.log('exec resume --json --print --output-format --resume'); process.exit(0); }
|
|
19
|
+
if(args.includes('--help')) { console.log('exec resume --json --print --output-format --resume --json-schema --tools --strict-mcp-config --mcp-config --no-session-persistence --settings --setting-sources --output-schema --ephemeral --sandbox --disable --config --ignore-rules'); process.exit(0); }
|
|
20
20
|
let input=''; process.stdin.setEncoding('utf8'); process.stdin.on('data',x=>input+=x);
|
|
21
21
|
process.stdin.on('end',()=>{
|
|
22
22
|
fs.appendFileSync('calls.jsonl',JSON.stringify({args,input})+'\n');
|
|
@@ -25,6 +25,8 @@ process.stdin.on('end',()=>{
|
|
|
25
25
|
if(input==='permission') {console.error('permission denied EACCES'); process.exit(9);}
|
|
26
26
|
if(input==='structured-error') { if(args[0]==='exec') console.log(JSON.stringify({type:'turn.failed',error:{message:'E_AUTH_42 登录失败'}})); else console.log(JSON.stringify({type:'result',is_error:true,errors:['E_AUTH_42 登录失败']})); return; }
|
|
27
27
|
if(input==='malformed') {console.log('{}'); return;}
|
|
28
|
+
if(args.includes('--json-schema')) {console.log(JSON.stringify({type:'result',session_id:randomUUID(),is_error:false,result:'',...(input==='missing-structured'?{}:{structured_output:{text:'模型回复'}})}));return;}
|
|
29
|
+
if(args.includes('--output-schema')) { const id=randomUUID(); console.log(JSON.stringify({type:'thread.started',thread_id:id})); console.log(JSON.stringify({type:'item.completed',item:{type:'agent_message',text:JSON.stringify({text:'模型回复'})}})); console.log(JSON.stringify({type:'turn.completed'})); return; }
|
|
28
30
|
const codex=args[0]==='exec'; const resume=codex?args.indexOf('resume'):args.indexOf('--resume');
|
|
29
31
|
let id=resume>=0?args[resume+1]:randomUUID();
|
|
30
32
|
if(input==='wrong-session') id=randomUUID();
|
|
@@ -108,6 +110,122 @@ for (const [name, Constructor] of [["Codex", codex_cli_1.default], ["Claude", cl
|
|
|
108
110
|
await strict_1.default.rejects(plugin.handle({ task: "recall" }), /未启动|停止/);
|
|
109
111
|
});
|
|
110
112
|
}
|
|
113
|
+
(0, node_test_1.default)("Claude 无工具模型请求使用默认模型及结构化输出,不保存聊天映射", async (t) => {
|
|
114
|
+
const { root, context } = await setup(t);
|
|
115
|
+
const plugin = new claude_cli_1.default();
|
|
116
|
+
await plugin.activate(context);
|
|
117
|
+
try {
|
|
118
|
+
const modelResponseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
|
|
119
|
+
const response = await plugin.handle({ task: "模型请求", modelResponseSchema });
|
|
120
|
+
strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
|
|
121
|
+
strict_1.default.equal(response.sessionId, undefined);
|
|
122
|
+
const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
|
|
123
|
+
strict_1.default.equal(call.args[call.args.indexOf("--tools") + 1], "");
|
|
124
|
+
strict_1.default.ok(call.args.includes("--strict-mcp-config"));
|
|
125
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--mcp-config") + 1]), { mcpServers: {} });
|
|
126
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--settings") + 1]), { disableAllHooks: true });
|
|
127
|
+
strict_1.default.ok(call.args.includes("--no-session-persistence"));
|
|
128
|
+
strict_1.default.ok(!call.args.includes("--model"));
|
|
129
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--json-schema") + 1]), modelResponseSchema);
|
|
130
|
+
await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
|
|
131
|
+
await strict_1.default.rejects(plugin.handle({ task: "missing-structured", modelResponseSchema }), /结构化/);
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const pending = strict_1.default.rejects(plugin.handle({ task: "hang", modelResponseSchema, signal: controller.signal }), /取消/);
|
|
134
|
+
setTimeout(() => controller.abort(), 100);
|
|
135
|
+
await pending;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await plugin.deactivate("stop");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
(0, node_test_1.default)("Claude 独立模型来源解析 prompt Schema,不保存管理会话", async (t) => {
|
|
142
|
+
const { root, context } = await setup(t);
|
|
143
|
+
const plugin = new claude_cli_1.default();
|
|
144
|
+
strict_1.default.equal(plugin.modelProvider, undefined);
|
|
145
|
+
await plugin.activate(context);
|
|
146
|
+
try {
|
|
147
|
+
const responseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
|
|
148
|
+
const provider = plugin.modelProvider;
|
|
149
|
+
strict_1.default.ok(provider);
|
|
150
|
+
plugin.handle = async () => { throw new Error("模型来源不能委托普通 handle"); };
|
|
151
|
+
const response = await provider.complete({
|
|
152
|
+
prompt: JSON.stringify({ instructions: "只返回结构化结果", responseSchema }),
|
|
153
|
+
sessionId: "pa-model",
|
|
154
|
+
timeoutMs: 2_000,
|
|
155
|
+
signal: new AbortController().signal,
|
|
156
|
+
});
|
|
157
|
+
strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
|
|
158
|
+
await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
|
|
159
|
+
await strict_1.default.rejects(provider.complete({ prompt: JSON.stringify({ instructions: "缺少 Schema" }), sessionId: "pa-model", timeoutMs: 2_000, signal: new AbortController().signal }), /Schema/);
|
|
160
|
+
const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
|
|
161
|
+
strict_1.default.equal(call.args[call.args.indexOf("--tools") + 1], "");
|
|
162
|
+
strict_1.default.ok(call.args.includes("--strict-mcp-config"));
|
|
163
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--mcp-config") + 1]), { mcpServers: {} });
|
|
164
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--settings") + 1]), { disableAllHooks: true });
|
|
165
|
+
strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--json-schema") + 1]), responseSchema);
|
|
166
|
+
strict_1.default.ok(call.args.includes("--no-session-persistence"));
|
|
167
|
+
strict_1.default.ok(!call.args.includes("--model"));
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
await plugin.deactivate("stop");
|
|
171
|
+
}
|
|
172
|
+
strict_1.default.equal(plugin.modelProvider, undefined);
|
|
173
|
+
});
|
|
174
|
+
(0, node_test_1.default)("Codex CLI 提供隔离模型来源,且普通对话仍可用", async (t) => {
|
|
175
|
+
const { root, context } = await setup(t);
|
|
176
|
+
const plugin = new codex_cli_1.default();
|
|
177
|
+
strict_1.default.ok(plugin.manifest.capabilities.includes("model-provider"));
|
|
178
|
+
strict_1.default.equal(plugin.modelProvider, undefined);
|
|
179
|
+
await plugin.activate(context);
|
|
180
|
+
try {
|
|
181
|
+
strict_1.default.equal(plugin.modelInput, true);
|
|
182
|
+
const responseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
|
|
183
|
+
const provider = plugin.modelProvider;
|
|
184
|
+
strict_1.default.ok(provider);
|
|
185
|
+
const response = await provider.complete({
|
|
186
|
+
prompt: JSON.stringify({ instructions: "只返回结构化结果", responseSchema }),
|
|
187
|
+
sessionId: "pa-codex-model",
|
|
188
|
+
timeoutMs: 2_000,
|
|
189
|
+
signal: new AbortController().signal,
|
|
190
|
+
});
|
|
191
|
+
strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
|
|
192
|
+
const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
|
|
193
|
+
strict_1.default.equal(call.args[0], "exec");
|
|
194
|
+
strict_1.default.ok(call.args.includes("--ephemeral"));
|
|
195
|
+
strict_1.default.deepEqual(call.args.slice(call.args.indexOf("--sandbox"), call.args.indexOf("--sandbox") + 2), ["--sandbox", "read-only"]);
|
|
196
|
+
for (const feature of ["shell_tool", "apps", "plugins", "hooks"]) {
|
|
197
|
+
const index = call.args.indexOf(feature);
|
|
198
|
+
strict_1.default.ok(index > 0);
|
|
199
|
+
strict_1.default.equal(call.args[index - 1], "--disable");
|
|
200
|
+
}
|
|
201
|
+
strict_1.default.ok(call.args.includes("--ignore-rules"));
|
|
202
|
+
strict_1.default.ok(call.args.includes("mcp_servers={}"));
|
|
203
|
+
strict_1.default.ok(call.args.includes("--output-schema"));
|
|
204
|
+
strict_1.default.ok(!call.args.includes("resume"));
|
|
205
|
+
strict_1.default.ok(!call.args.includes("--model"));
|
|
206
|
+
await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
|
|
207
|
+
strict_1.default.equal((await plugin.handle({ task: "普通 Codex 请求" })).text, "普通 Codex 请求");
|
|
208
|
+
const calls = (await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim().split("\n").map(line => JSON.parse(line));
|
|
209
|
+
strict_1.default.ok(calls.some(entry => entry.args[0] === "exec" && entry.args.includes("--json")));
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await plugin.deactivate("stop");
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
(0, node_test_1.default)("CLI 模型失败保留退出码但不记录外部错误夹带的提示正文", async (t) => {
|
|
216
|
+
const { context } = await setup(t);
|
|
217
|
+
const records = [];
|
|
218
|
+
const plugin = new codex_cli_1.default();
|
|
219
|
+
await plugin.activate({ ...context, logger: { info() { }, warn() { }, error: (_message, fields) => records.push(fields) } });
|
|
220
|
+
plugin.run = async () => { throw new Error("codex CLI 退出码 5:请检查 CLI 配置及网络;外部回显的私密用户提示"); };
|
|
221
|
+
try {
|
|
222
|
+
await strict_1.default.rejects(plugin.modelProvider.complete({ prompt: JSON.stringify({ responseSchema: { type: "object" } }), sessionId: "model", timeoutMs: 1000, signal: new AbortController().signal }), error => /退出码 5/.test(String(error)) && !/私密用户提示/.test(String(error)));
|
|
223
|
+
strict_1.default.doesNotMatch(JSON.stringify(records), /私密用户提示/);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
await plugin.deactivate("stop");
|
|
227
|
+
}
|
|
228
|
+
});
|
|
111
229
|
(0, node_test_1.default)("配置无效和 CLI 未安装时不冒充就绪", async (t) => {
|
|
112
230
|
const { context } = await setup(t);
|
|
113
231
|
const plugin = new codex_cli_1.default();
|