返回 DeepSeek-Reasonix
tool-subject.test.ts
根目录 / desktop / frontend / src / __tests__ / tool-subject.test.ts
1 // Run: tsx src/__tests__/tool-subject.test.ts
2
3 import { subjectOf } from "../lib/tools";
4 import { toolGroupKind } from "../components/ToolGroup";
5 import { historyMessagesToItems, isReadOnlyTool } from "../lib/useController";
6 import type { HistoryMessage } from "../lib/types";
7
8 let passed = 0;
9 let failed = 0;
10
11 function eq(a: unknown, b: unknown, label: string) {
12 if (a === b) {
13 process.stdout.write(` PASS ${label}\n`);
14 passed += 1;
15 } else {
16 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
17 failed += 1;
18 }
19 }
20
21 console.log("\ntool subject contract");
22
23 eq(subjectOf("task", JSON.stringify({ description: "audit docs" })), "audit docs", "task subject uses description");
24 eq(subjectOf("run_skill", JSON.stringify({ name: "code-reviewer", arguments: "review this branch" })), "code-reviewer", "run_skill subject uses skill name");
25 eq(
26 subjectOf("use_capability", JSON.stringify({ action: "call", capability_id: "mcp-tool:github/search_issues" })),
27 "mcp-tool:github/search_issues",
28 "use_capability subject uses capability_id",
29 );
30 eq(subjectOf("use_capability", JSON.stringify({ action: "list" })), "list", "use_capability list falls back to action");
31
32 const capabilityArgs = JSON.stringify({ action: "call", capability_id: "mcp-tool:db/write" });
33 eq(
34 toolGroupKind({ kind: "tool", id: "reader", name: "use_capability", args: capabilityArgs, readOnly: true, status: "done" }),
35 "explore",
36 "resolved read-only MCP groups as research",
37 );
38 eq(
39 toolGroupKind({ kind: "tool", id: "writer", name: "use_capability", args: capabilityArgs, readOnly: false, status: "done" }),
40 "modify",
41 "resolved writer MCP stays in the modify group",
42 );
43
44 const history = historyMessagesToItems([{
45 role: "assistant",
46 content: "",
47 toolCalls: [
48 {
49 id: "old",
50 name: "use_capability",
51 arguments: capabilityArgs,
52 },
53 {
54 id: "reader",
55 name: "use_capability",
56 arguments: capabilityArgs,
57 resolvedName: "mcp__db__read",
58 capabilityId: "mcp-tool:db/read",
59 resolvedReadOnly: true,
60 },
61 {
62 id: "writer",
63 name: "use_capability",
64 arguments: capabilityArgs,
65 resolvedName: "mcp__db__write",
66 capabilityId: "mcp-tool:db/write",
67 resolvedReadOnly: false,
68 },
69 ],
70 }] as HistoryMessage[], "h-").items.filter((item) => item.kind === "tool");
71 eq(history[0]?.kind === "tool" ? history[0].readOnly : true, false, "old proxy history fails closed as visible");
72 eq(history[1]?.kind === "tool" ? history[1].readOnly : false, true, "resolved reader history restores read-only classification");
73 eq(history[2]?.kind === "tool" ? history[2].readOnly : true, false, "resolved writer history restores writer classification");
74 eq(history[2]?.kind === "tool" ? history[2].resolvedName : "", "mcp__db__write", "history keeps resolved MCP target");
75 eq(isReadOnlyTool("read_file"), true, "legacy built-in reader remains read-only");
76 eq(isReadOnlyTool("grep"), true, "legacy search reader remains read-only");
77 eq(isReadOnlyTool("use_capability"), false, "legacy unresolved proxy remains fail-closed");
78 eq(isReadOnlyTool("unknown_tool"), false, "unknown history tool remains fail-closed");
79
80 if (failed) {
81 process.stdout.write(`\n${failed} failed, ${passed} passed\n`);
82 process.exit(1);
83 }
84 process.stdout.write(`\n${passed} passed\n`);
85
85 lines TYPESCRIPT