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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
9
|
+
const cdp_client_2 = require("../src-ts/plugins/chatgpt/cdp-client");
|
|
10
|
+
const cdp_client_3 = require("../src-ts/plugins/workbuddy/cdp-client");
|
|
11
|
+
const cdp_client_4 = require("../src-ts/plugins/doubao/cdp-client");
|
|
12
|
+
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
13
|
+
(0, node_test_1.default)("千问取消后保留生成中的任务,确认本轮空回复结束才收尾", async (t) => {
|
|
14
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
15
|
+
const client = new cdp_client_1.QwenCdpClient();
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
let submitted = false;
|
|
18
|
+
let generating = false;
|
|
19
|
+
let error;
|
|
20
|
+
client.ensureVisible = client.recoverDraft = client.waitFor = async () => undefined;
|
|
21
|
+
client.readArtifacts = async () => [];
|
|
22
|
+
client.cdp = async () => { submitted = true; };
|
|
23
|
+
client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
|
|
24
|
+
client.evaluate = async (expression) => {
|
|
25
|
+
if (expression === "location.href")
|
|
26
|
+
return "file:///index.html?chat=model-chat";
|
|
27
|
+
if (expression.startsWith("JSON.stringify(["))
|
|
28
|
+
return JSON.stringify(submitted ? [{ id: "current", text: "" }] : []);
|
|
29
|
+
if (expression.startsWith("Boolean(document.querySelector('button"))
|
|
30
|
+
return generating;
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
const running = client.complete("模型提示", 10, controller.signal).catch((value) => { error = value; });
|
|
34
|
+
await flush();
|
|
35
|
+
controller.abort();
|
|
36
|
+
for (let index = 0; index < 2; index++) {
|
|
37
|
+
t.mock.timers.tick(2000);
|
|
38
|
+
await flush();
|
|
39
|
+
}
|
|
40
|
+
strict_1.default.equal(error, undefined, "尚未观察到本轮生成时不能猜测任务结束");
|
|
41
|
+
generating = true;
|
|
42
|
+
t.mock.timers.tick(2000);
|
|
43
|
+
await flush();
|
|
44
|
+
strict_1.default.equal(error, undefined);
|
|
45
|
+
generating = false;
|
|
46
|
+
t.mock.timers.tick(2000);
|
|
47
|
+
await flush();
|
|
48
|
+
strict_1.default.equal(error, undefined, "单次未生成状态不能释放任务");
|
|
49
|
+
t.mock.timers.tick(2000);
|
|
50
|
+
await flush();
|
|
51
|
+
strict_1.default.match(String(error), /QWEN_REQUEST_CANCELLED.*已取消.*本轮.*结束/);
|
|
52
|
+
await running;
|
|
53
|
+
});
|
|
54
|
+
(0, node_test_1.default)("ChatGPT 取消后等待本轮生成结束,空回复不永久占用原任务", async (t) => {
|
|
55
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
56
|
+
const client = new cdp_client_2.ChatgptCdpClient();
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
let active = false;
|
|
59
|
+
let error;
|
|
60
|
+
client.waitFor = client.waitForGeneration = client.recoverDraft = async () => undefined;
|
|
61
|
+
client.activeSession = async () => ({ id: "model", host: "local", title: "模型会话" });
|
|
62
|
+
client.cdp = async () => undefined;
|
|
63
|
+
client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
|
|
64
|
+
client.evaluate = async (expression) => {
|
|
65
|
+
if (expression.includes("findLast((item, index)"))
|
|
66
|
+
return JSON.stringify({ submitted: true, active, reply: "", hasArtifacts: false, artifacts: [{ conversationId: "model", messageId: "current" }] });
|
|
67
|
+
if (expression.startsWith("JSON.stringify("))
|
|
68
|
+
return "[]";
|
|
69
|
+
if (expression.endsWith(".length"))
|
|
70
|
+
return 0;
|
|
71
|
+
return true;
|
|
72
|
+
};
|
|
73
|
+
const running = client.direct("模型提示", 10, undefined, controller.signal).catch((value) => { error = value; });
|
|
74
|
+
await flush();
|
|
75
|
+
controller.abort();
|
|
76
|
+
for (let index = 0; index < 2; index++) {
|
|
77
|
+
t.mock.timers.tick(800);
|
|
78
|
+
await flush();
|
|
79
|
+
}
|
|
80
|
+
strict_1.default.equal(error, undefined, "消息已提交但尚未开始生成时不能释放任务");
|
|
81
|
+
active = true;
|
|
82
|
+
t.mock.timers.tick(800);
|
|
83
|
+
await flush();
|
|
84
|
+
strict_1.default.equal(error, undefined);
|
|
85
|
+
active = false;
|
|
86
|
+
t.mock.timers.tick(800);
|
|
87
|
+
await flush();
|
|
88
|
+
strict_1.default.equal(error, undefined, "单次停止状态不能释放任务");
|
|
89
|
+
t.mock.timers.tick(800);
|
|
90
|
+
await flush();
|
|
91
|
+
strict_1.default.match(String(error), /ChatGPT.*已取消.*本轮.*结束/);
|
|
92
|
+
await running;
|
|
93
|
+
});
|
|
94
|
+
(0, node_test_1.default)("WorkBuddy 取消后等待本轮完成标记,空回复也能结束等待", async (t) => {
|
|
95
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
96
|
+
const client = new cdp_client_3.WorkbuddyCdpClient();
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
let finished = false;
|
|
99
|
+
let error;
|
|
100
|
+
client.evaluate = async () => JSON.stringify([{ id: "current", text: "", finished, stopped: false }]);
|
|
101
|
+
client.readInteraction = async () => undefined;
|
|
102
|
+
client.readArtifacts = async () => [];
|
|
103
|
+
client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
|
|
104
|
+
const running = client.waitForReply(new Set(), 10, undefined, undefined, "model", undefined, controller.signal).catch((value) => { error = value; });
|
|
105
|
+
controller.abort();
|
|
106
|
+
t.mock.timers.tick(1000);
|
|
107
|
+
await flush();
|
|
108
|
+
strict_1.default.equal(error, undefined);
|
|
109
|
+
finished = true;
|
|
110
|
+
t.mock.timers.tick(1000);
|
|
111
|
+
await flush();
|
|
112
|
+
strict_1.default.match(String(error), /WORKBUDDY_REQUEST_CANCELLED.*已取消.*本轮.*结束/);
|
|
113
|
+
await running;
|
|
114
|
+
});
|
|
115
|
+
(0, node_test_1.default)("豆包取消后的空 SSE 终态释放队列,下一轮不会接到前轮结果", async (t) => {
|
|
116
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
117
|
+
const client = new cdp_client_4.DoubaoCdpClient({ timeoutMs: 10 });
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
let requests = 0;
|
|
120
|
+
let finished = false;
|
|
121
|
+
let error;
|
|
122
|
+
client.connect = async () => undefined;
|
|
123
|
+
client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
|
|
124
|
+
client.evaluate = async (expression) => {
|
|
125
|
+
if (expression !== "window.__mab_doubao_result") {
|
|
126
|
+
requests++;
|
|
127
|
+
return "started";
|
|
128
|
+
}
|
|
129
|
+
if (requests === 1)
|
|
130
|
+
return finished ? JSON.stringify({ ok: true, reply: "", pending: false, conversationId: "123" }) : null;
|
|
131
|
+
return JSON.stringify({ ok: true, reply: "只属于下一轮的回复", conversationId: "456" });
|
|
132
|
+
};
|
|
133
|
+
const first = client.complete("取消提示", controller.signal, undefined, "").catch((value) => { error = value; });
|
|
134
|
+
await flush();
|
|
135
|
+
const second = client.complete("新请求", undefined, undefined, "");
|
|
136
|
+
controller.abort();
|
|
137
|
+
t.mock.timers.tick(1000);
|
|
138
|
+
await flush();
|
|
139
|
+
strict_1.default.equal(error, undefined);
|
|
140
|
+
strict_1.default.equal(requests, 1, "页面请求未结束时,下一轮不得提交");
|
|
141
|
+
finished = true;
|
|
142
|
+
t.mock.timers.tick(1000);
|
|
143
|
+
await flush();
|
|
144
|
+
strict_1.default.match(String(error), /豆包.*已取消.*本轮.*结束/);
|
|
145
|
+
strict_1.default.equal(requests, 2, "已确认空结果后应释放原队列");
|
|
146
|
+
t.mock.timers.tick(1000);
|
|
147
|
+
await flush();
|
|
148
|
+
strict_1.default.deepEqual(await second, { text: "只属于下一轮的回复", sessionId: "456" });
|
|
149
|
+
await first;
|
|
150
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
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 node_test_1 = require("node:test");
|
|
11
|
+
const coordination_1 = require("../src-ts/plugins/personal-assistant/coordination");
|
|
12
|
+
async function fixture(responses) {
|
|
13
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-coordination-"));
|
|
14
|
+
const logPath = (0, node_path_1.join)(dir, "args.jsonl");
|
|
15
|
+
const ecPath = (0, node_path_1.join)(dir, "ec");
|
|
16
|
+
const responder = `const fs=require('node:fs');const args=process.argv.slice(2);fs.appendFileSync(${JSON.stringify(logPath)},JSON.stringify(args)+'\\n');const value=(${responses.toString()})(args);process.stdout.write(JSON.stringify(value));`;
|
|
17
|
+
await (0, promises_1.writeFile)(ecPath, `#!/usr/bin/env node\n${responder}\n`);
|
|
18
|
+
await (0, promises_1.chmod)(ecPath, 0o755);
|
|
19
|
+
return {
|
|
20
|
+
coordinator: (0, coordination_1.createCoordinator)({ ecPath, selfAid: "self.agentid.cn", allowedAgents: [{ id: "worker", aids: ["worker.agentid.cn"] }] }),
|
|
21
|
+
args: async () => { try {
|
|
22
|
+
return (await (0, promises_1.readFile)(logPath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
} },
|
|
27
|
+
cleanup: () => (0, promises_1.rm)(dir, { recursive: true, force: true }),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
(0, node_test_1.test)("派活只允许白名单内匹配的 Agent 与 AID", async () => {
|
|
31
|
+
const value = await fixture(() => ({ thread_id: "thread-1" }));
|
|
32
|
+
try {
|
|
33
|
+
await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "other", aid: "worker.agentid.cn", task: "工作" }), /白名单/);
|
|
34
|
+
await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "worker", aid: "other.agentid.cn", task: "工作" }), /白名单/);
|
|
35
|
+
await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "worker; touch /tmp/no", aid: "worker.agentid.cn", task: "工作" }), /白名单/);
|
|
36
|
+
strict_1.default.deepEqual(await value.args(), []);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await value.cleanup();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, node_test_1.test)("创建 thread 后用 argv 派活,特殊字符作为普通任务文本传入", async () => {
|
|
43
|
+
const value = await fixture((args) => args[0] === "thread" ? { ok: true, thread_id: "thread-1" } : { ok: true, status: "completed" });
|
|
44
|
+
try {
|
|
45
|
+
const task = "检查 x; touch /tmp/no && $(whoami)";
|
|
46
|
+
const dispatched = await value.coordinator.dispatch({ agentId: "worker", aid: "worker.agentid.cn", task });
|
|
47
|
+
strict_1.default.deepEqual(dispatched, { status: "pending", agentId: "worker", aid: "worker.agentid.cn", threadId: "thread-1" });
|
|
48
|
+
strict_1.default.deepEqual(await value.args(), [
|
|
49
|
+
["thread", "create", "worker.agentid.cn", "--name", "个人助理派活-worker", "--format", "json"],
|
|
50
|
+
["msg", "send", "self.agentid.cn", "worker.agentid.cn", "--text", task, "--thread", "thread-1", "--return", "required", "--format", "json"],
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await value.cleanup();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
(0, node_test_1.test)("thread history 未明确标记完成时保持 pending,且不重发任务", async () => {
|
|
58
|
+
const value = await fixture((args) => {
|
|
59
|
+
if (args[0] === "thread" && args[1] === "create")
|
|
60
|
+
return { thread_id: "thread-1" };
|
|
61
|
+
if (args[0] === "msg")
|
|
62
|
+
return { ok: true, status: "completed" };
|
|
63
|
+
return { messages: [{ from: "worker.agentid.cn", text: "仍在处理" }] };
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
await value.coordinator.dispatch({ agentId: "worker", aid: "worker.agentid.cn", task: "执行任务" });
|
|
67
|
+
const result = await value.coordinator.getStatus({ aid: "worker.agentid.cn", threadId: "thread-1" });
|
|
68
|
+
strict_1.default.deepEqual(result, { status: "pending", aid: "worker.agentid.cn", threadId: "thread-1", history: { messages: [{ from: "worker.agentid.cn", text: "仍在处理" }] } });
|
|
69
|
+
const calls = await value.args();
|
|
70
|
+
strict_1.default.equal(calls.filter((args) => args[0] === "msg" && args[1] === "send").length, 1);
|
|
71
|
+
strict_1.default.deepEqual(calls.at(-1), ["thread", "history", "thread-1", "--format", "json"]);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
await value.cleanup();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = require("node:test");
|
|
8
|
+
const tools_1 = require("../src-ts/plugins/personal-assistant/tools");
|
|
9
|
+
const management = {
|
|
10
|
+
listAgents: async () => [{ id: "worker", displayName: "工作 Agent", status: "ready", available: true, reason: "", capabilities: ["chat"], aids: ["worker.agentid.cn"], callable: true }],
|
|
11
|
+
callAgent: async () => ({ text: "" }), listAunIdentities: async () => ({}), createAunIdentity: async () => ({}), bindAunIdentity: async () => ({}),
|
|
12
|
+
runSessionCommand: async () => "", summarizeToday: async () => ({ supported: false, text: "", sources: [], complete: false }),
|
|
13
|
+
};
|
|
14
|
+
(0, node_test_1.test)("扩展工具只向配置的 Provider 访问远程 Ability", async () => {
|
|
15
|
+
let calls = 0;
|
|
16
|
+
const remoteAbility = { discover: async () => { calls++; return []; }, call: async () => { calls++; return { status: "completed" }; }, getRequest: async () => { calls++; return { status: "pending" }; } };
|
|
17
|
+
await strict_1.default.rejects((0, tools_1.executeTool)("ability_discover", { providerAid: "other.agentid.cn" }, management, [], { allowedProviders: ["provider.agentid.cn"], remoteAbility: remoteAbility }));
|
|
18
|
+
strict_1.default.deepEqual(await (0, tools_1.executeTool)("ability_discover", { providerAid: "provider.agentid.cn" }, management, [], { allowedProviders: ["provider.agentid.cn"], remoteAbility: remoteAbility }), []);
|
|
19
|
+
strict_1.default.equal(calls, 1);
|
|
20
|
+
});
|
|
21
|
+
(0, node_test_1.test)("协调派活要求核心可用状态、Agent 白名单和绑定 AID 同时匹配", async () => {
|
|
22
|
+
const args = { agentId: "worker", aid: "worker.agentid.cn", task: "执行工作" };
|
|
23
|
+
await strict_1.default.rejects((0, tools_1.executeTool)("coordination_dispatch", args, management, [], { selfAid: "assistant.agentid.cn" }), /白名单/);
|
|
24
|
+
await strict_1.default.rejects((0, tools_1.executeTool)("coordination_dispatch", { ...args, aid: "other.agentid.cn" }, management, ["worker"], { selfAid: "assistant.agentid.cn" }), /不可派活/);
|
|
25
|
+
strict_1.default.deepEqual((0, tools_1.toolDefinitions)().map((tool) => tool.function.name).slice(-6), ["ability_discover", "ability_call", "ability_request_get", "coordination_dispatch", "coordination_status", "owner_context_read"]);
|
|
26
|
+
});
|
|
27
|
+
(0, node_test_1.test)("thread 状态查询只允许当前插件进程已派发的 thread", async () => {
|
|
28
|
+
await strict_1.default.rejects((0, tools_1.executeTool)("coordination_status", { aid: "worker.agentid.cn", threadId: "thread-1" }, management, [], { selfAid: "assistant.agentid.cn", threads: new Map() }), /不属于/);
|
|
29
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
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 node_test_1 = require("node:test");
|
|
11
|
+
const owner_context_1 = require("../src-ts/plugins/personal-assistant/owner-context");
|
|
12
|
+
async function fixture(responses) {
|
|
13
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-owner-context-"));
|
|
14
|
+
const logPath = (0, node_path_1.join)(dir, "args.jsonl");
|
|
15
|
+
const ecPath = (0, node_path_1.join)(dir, "ec");
|
|
16
|
+
const responder = `const fs=require('node:fs');const args=process.argv.slice(2);const log=${JSON.stringify(logPath)};const calls=fs.existsSync(log)?fs.readFileSync(log,'utf8').split('\\n').filter(Boolean).length:0;fs.appendFileSync(log,JSON.stringify(args)+'\\n');const value=(${responses.toString()})(args,calls);process.stdout.write(JSON.stringify(value));`;
|
|
17
|
+
await (0, promises_1.writeFile)(ecPath, `#!/usr/bin/env node\n${responder}\n`);
|
|
18
|
+
await (0, promises_1.chmod)(ecPath, 0o755);
|
|
19
|
+
return {
|
|
20
|
+
ecPath,
|
|
21
|
+
args: async () => (await (0, promises_1.readFile)(logPath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)),
|
|
22
|
+
cleanup: () => (0, promises_1.rm)(dir, { recursive: true, force: true }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
(0, node_test_1.test)("只用固定 Owner AID 和规范路径经 execFile 读取,内容标记为参考资料", async () => {
|
|
26
|
+
const fake = await fixture(() => ({ command: "cat", binary: false, text: "仅供参考" }));
|
|
27
|
+
try {
|
|
28
|
+
const store = (0, owner_context_1.createOwnerContextStore)({ ownerAid: "owner.agentid.cn", path: "/profile/context.md", ttlMs: 1000, ecPath: fake.ecPath });
|
|
29
|
+
const result = await store.get();
|
|
30
|
+
strict_1.default.equal(result.content, "仅供参考");
|
|
31
|
+
strict_1.default.equal(result.status, "fresh");
|
|
32
|
+
strict_1.default.equal(result.trust, "reference_only");
|
|
33
|
+
strict_1.default.deepEqual(await fake.args(), [["fs", "cat", "owner.agentid.cn:/profile/context.md", "--format", "json"]]);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await fake.cleanup();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
(0, node_test_1.test)("TTL 内复用缓存,refresh 强制读取新值", async () => {
|
|
40
|
+
let now = 10_000;
|
|
41
|
+
const fake = await fixture((_args, calls) => ({ command: "cat", binary: false, text: `版本${calls + 1}` }));
|
|
42
|
+
try {
|
|
43
|
+
const store = (0, owner_context_1.createOwnerContextStore)({ ownerAid: "owner.agentid.cn", path: "/profile/context.md", ttlMs: 1000, ecPath: fake.ecPath, now: () => now });
|
|
44
|
+
strict_1.default.equal((await store.get()).content, "版本1");
|
|
45
|
+
now += 500;
|
|
46
|
+
const cached = await store.get();
|
|
47
|
+
strict_1.default.equal(cached.content, "版本1");
|
|
48
|
+
strict_1.default.equal(cached.status, "cached");
|
|
49
|
+
strict_1.default.equal(cached.ageMs, 500);
|
|
50
|
+
strict_1.default.equal((await store.refresh()).content, "版本2");
|
|
51
|
+
strict_1.default.equal((await fake.args()).length, 2);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await fake.cleanup();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
(0, node_test_1.test)("刷新失败保留上次成功值并标记 stale", async () => {
|
|
58
|
+
let now = 10_000;
|
|
59
|
+
const fake = await fixture((_args, calls) => {
|
|
60
|
+
if (calls === 0)
|
|
61
|
+
return { command: "cat", binary: false, text: "最后一次有效资料" };
|
|
62
|
+
throw new Error("读取失败");
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
const store = (0, owner_context_1.createOwnerContextStore)({ ownerAid: "owner.agentid.cn", path: "/profile/context.md", ttlMs: 1000, ecPath: fake.ecPath, now: () => now });
|
|
66
|
+
const first = await store.get();
|
|
67
|
+
now += 1500;
|
|
68
|
+
const stale = await store.get();
|
|
69
|
+
strict_1.default.equal(first.content, stale.content);
|
|
70
|
+
strict_1.default.equal(stale.status, "stale");
|
|
71
|
+
strict_1.default.equal(stale.ageMs, 1500);
|
|
72
|
+
strict_1.default.equal((await fake.args()).length, 2);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await fake.cleanup();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
(0, node_test_1.test)("拒绝无效 AID、非绝对或非规范路径及无效 TTL", async () => {
|
|
79
|
+
const fake = await fixture(() => ({ command: "cat", binary: false, text: "unused" }));
|
|
80
|
+
try {
|
|
81
|
+
for (const options of [
|
|
82
|
+
{ ownerAid: "owner; touch /tmp/injected", path: "/profile/context.md", ttlMs: 1000 },
|
|
83
|
+
{ ownerAid: "owner.agentid.cn", path: "profile/context.md", ttlMs: 1000 },
|
|
84
|
+
{ ownerAid: "owner.agentid.cn", path: "/profile/../secret.md", ttlMs: 1000 },
|
|
85
|
+
{ ownerAid: "owner.agentid.cn", path: "/profile/context.md", ttlMs: 0 },
|
|
86
|
+
]) {
|
|
87
|
+
strict_1.default.throws(() => (0, owner_context_1.createOwnerContextStore)({ ...options, ecPath: fake.ecPath }));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await fake.cleanup();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
(0, node_test_1.test)("拒绝超限文本,且不缓存无效读取", async () => {
|
|
95
|
+
let now = 10_000;
|
|
96
|
+
const fake = await fixture((_args, calls) => ({ command: "cat", binary: false, text: calls === 0 ? "ok" : "过长" }));
|
|
97
|
+
try {
|
|
98
|
+
const store = (0, owner_context_1.createOwnerContextStore)({ ownerAid: "owner.agentid.cn", path: "/profile/context.md", ttlMs: 1000, maxBytes: 3, ecPath: fake.ecPath, now: () => now });
|
|
99
|
+
strict_1.default.equal((await store.get()).content, "ok");
|
|
100
|
+
now += 2000;
|
|
101
|
+
strict_1.default.equal((await store.get()).content, "ok");
|
|
102
|
+
strict_1.default.equal((await fake.args()).length, 2);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await fake.cleanup();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
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 node_test_1 = require("node:test");
|
|
11
|
+
const remote_ability_1 = require("../src-ts/plugins/personal-assistant/remote-ability");
|
|
12
|
+
async function fixture(responses, globals = {}) {
|
|
13
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-remote-ability-"));
|
|
14
|
+
const logPath = (0, node_path_1.join)(dir, "args.jsonl");
|
|
15
|
+
const ecPath = (0, node_path_1.join)(dir, "ec");
|
|
16
|
+
const defaultIndex = `const fs=require('node:fs');const logPath=${JSON.stringify(logPath)};const index=()=>({abilities:[{path:"status/read",definition_id:"status-v1",detail_ref:"ability/status.json",params_schema:{type:"object",additionalProperties:false,properties:{device:{type:"string"}},required:["device"]},result_schema:{type:"object"},effects:["read"],auth_required:false,require_confirm:false}]});const response=(overrides={})=>({type:"ability",role:"result",call_id:"call-fixed",operation_id:"op-fixed",ability_path:"status/read",definition_id:"status-v1",ok:true,execution_state:"completed",result:{online:true},...overrides});`;
|
|
17
|
+
const declarations = Object.entries(globals).map(([key, value]) => `const ${key}=${JSON.stringify(value)};`).join("");
|
|
18
|
+
const responder = `${defaultIndex}${declarations}globalThis.fs=fs;globalThis.logPath=logPath;const args=process.argv.slice(2);fs.appendFileSync(logPath,JSON.stringify(args)+'\\n');const value=(${responses.toString()})(args);process.stdout.write(JSON.stringify(value));`;
|
|
19
|
+
await (0, promises_1.writeFile)(ecPath, `#!/usr/bin/env node\n${responder}\n`);
|
|
20
|
+
await (0, promises_1.chmod)(ecPath, 0o755);
|
|
21
|
+
return {
|
|
22
|
+
client: new remote_ability_1.RemoteAbilityClient({ selfAid: "self.agentid.cn", ecPath }),
|
|
23
|
+
args: async () => (await (0, promises_1.readFile)(logPath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)),
|
|
24
|
+
cleanup: () => (0, promises_1.rm)(dir, { recursive: true, force: true }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function index(overrides = {}) {
|
|
28
|
+
return {
|
|
29
|
+
abilities: [{ path: "status/read", definition_id: "status-v1", detail_ref: "ability/status.json", params_schema: { type: "object", additionalProperties: false, properties: { device: { type: "string" } }, required: ["device"] }, result_schema: { type: "object" }, effects: ["read"], auth_required: false, require_confirm: false, ...overrides }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function response(overrides = {}) {
|
|
33
|
+
return { type: "ability", role: "result", call_id: "call-fixed", operation_id: "op-fixed", ability_path: "status/read", definition_id: "status-v1", ok: true, execution_state: "completed", result: { online: true }, ...overrides };
|
|
34
|
+
}
|
|
35
|
+
(0, node_test_1.test)("命令参数经 execFile 直传,远端 schema 和只读契约控制调用", async () => {
|
|
36
|
+
const fixtureValue = await fixture((args) => {
|
|
37
|
+
if (args[0] === "fs")
|
|
38
|
+
return { command: "cat", binary: false, text: JSON.stringify(args[2].endsWith("index.json") ? index() : index().abilities[0]) };
|
|
39
|
+
if (args[0] === "msg" && args[1] === "send") {
|
|
40
|
+
const payload = JSON.parse(args[args.indexOf("--payload") + 1]);
|
|
41
|
+
return { ok: true, request_state: "completed", request_id: payload.call_id, response: response({ call_id: payload.call_id, operation_id: payload.operation_id }) };
|
|
42
|
+
}
|
|
43
|
+
throw new Error("unexpected command");
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
const providerAid = "provider.agentid.cn";
|
|
47
|
+
await fixtureValue.client.discover(providerAid);
|
|
48
|
+
const unsafeText = "x; touch /tmp/remote-ability-injected";
|
|
49
|
+
const result = await fixtureValue.client.call(providerAid, "status/read", { device: unsafeText });
|
|
50
|
+
strict_1.default.equal(result.status, "completed");
|
|
51
|
+
strict_1.default.deepEqual(result.result, { online: true });
|
|
52
|
+
const args = await fixtureValue.args();
|
|
53
|
+
strict_1.default.deepEqual(args[0], ["fs", "cat", `${providerAid}:/ability/index.json`, "--format", "json"]);
|
|
54
|
+
strict_1.default.deepEqual(args[1], ["fs", "cat", `${providerAid}:/ability/status.json`, "--format", "json"]);
|
|
55
|
+
strict_1.default.equal(args[2][0], "msg");
|
|
56
|
+
strict_1.default.equal(args[2][1], "send");
|
|
57
|
+
strict_1.default.ok(args[2].includes("--payload"));
|
|
58
|
+
strict_1.default.ok(args[2].includes("--return"));
|
|
59
|
+
strict_1.default.ok(args[2].includes("required"));
|
|
60
|
+
strict_1.default.equal(args[2].includes("--shell"), false);
|
|
61
|
+
const payload = JSON.parse(args[2][args[2].indexOf("--payload") + 1]);
|
|
62
|
+
strict_1.default.equal(payload.params.device, unsafeText);
|
|
63
|
+
strict_1.default.match(payload.call_id, /^c-/);
|
|
64
|
+
strict_1.default.match(payload.operation_id, /^op-/);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await fixtureValue.cleanup();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
(0, node_test_1.test)("拒绝无效 AID、参数 schema 和非只读或需授权契约", async () => {
|
|
71
|
+
const fixtureValue = await fixture((args) => ({ command: "cat", binary: false, text: JSON.stringify(args[2].endsWith("index.json") ? index() : index().abilities[0]) }));
|
|
72
|
+
try {
|
|
73
|
+
await strict_1.default.rejects(fixtureValue.client.discover("bad aid"), /AID/);
|
|
74
|
+
await fixtureValue.client.discover("provider.agentid.cn");
|
|
75
|
+
await strict_1.default.rejects(fixtureValue.client.call("provider.agentid.cn", "status/read", {}), /schema/);
|
|
76
|
+
await strict_1.default.rejects(fixtureValue.client.call("provider.agentid.cn", "other", { device: "x" }), /能力/);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await fixtureValue.cleanup();
|
|
80
|
+
}
|
|
81
|
+
for (const denied of [{ effects: ["read", "write"] }, { auth_required: true }, { require_confirm: true }, { detail_ref: "../secret.json" }]) {
|
|
82
|
+
const entry = index(denied).abilities[0];
|
|
83
|
+
const fake = await fixture((args) => args[2].endsWith("index.json") ? { abilities: [entry] } : entry, { entry });
|
|
84
|
+
try {
|
|
85
|
+
await strict_1.default.rejects(fake.client.call("provider.agentid.cn", "status/read", { device: "x" }), /契约|引用|路径/);
|
|
86
|
+
strict_1.default.equal((await fake.args()).filter((args) => args[0] === "msg").length, 0);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await fake.cleanup();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
(0, node_test_1.test)("pending 不重发,request get 读取业务结果并校验关联字段", async () => {
|
|
94
|
+
const fixtureValue = await fixture((args) => {
|
|
95
|
+
if (args[0] === "fs")
|
|
96
|
+
return { command: "cat", binary: false, text: JSON.stringify(args[2].endsWith("index.json") ? index() : index().abilities[0]) };
|
|
97
|
+
if (args[1] === "send") {
|
|
98
|
+
const payload = JSON.parse(args[args.indexOf("--payload") + 1]);
|
|
99
|
+
return { ok: true, request_state: "pending", request_id: payload.call_id };
|
|
100
|
+
}
|
|
101
|
+
if (args[1] === "request" && args[2] === "get") {
|
|
102
|
+
const lines = globalThis.fs.readFileSync(globalThis.logPath, "utf8").trim().split(String.fromCharCode(10)).filter(Boolean);
|
|
103
|
+
const sent = JSON.parse(lines.find((line) => JSON.parse(line)[1] === "send"));
|
|
104
|
+
const payload = JSON.parse(sent[sent.indexOf("--payload") + 1]);
|
|
105
|
+
return { ok: true, request: { state: "completed", responsePayload: response({ call_id: payload.call_id, operation_id: payload.operation_id }) } };
|
|
106
|
+
}
|
|
107
|
+
throw new Error("unexpected command");
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const providerAid = "provider.agentid.cn";
|
|
111
|
+
const pending = await fixtureValue.client.call(providerAid, "status/read", { device: "phone" });
|
|
112
|
+
strict_1.default.equal(pending.status, "pending");
|
|
113
|
+
strict_1.default.equal((await fixtureValue.args()).filter((args) => args[1] === "send").length, 1);
|
|
114
|
+
const completed = await fixtureValue.client.getRequest(providerAid, pending.requestId);
|
|
115
|
+
strict_1.default.equal(completed.status, "completed");
|
|
116
|
+
strict_1.default.deepEqual(completed.result, { online: true });
|
|
117
|
+
strict_1.default.equal((await fixtureValue.args()).filter((args) => args[1] === "send").length, 1);
|
|
118
|
+
await strict_1.default.rejects(fixtureValue.client.getRequest(providerAid, "bad id"), /request_id/);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await fixtureValue.cleanup();
|
|
122
|
+
}
|
|
123
|
+
});
|