refactor: unify tool reason requirements

This commit is contained in:
2026-10-28 16:43:04 +08:00
parent 7d47a2df86
commit c3050b602b
20 changed files with 97 additions and 46 deletions
+17 -2
View File
@@ -32,10 +32,10 @@ describe("learning tools across process boundaries", () => {
'import memory from "./.opencode/tools/memory_manager.ts";',
'import skill from "./.opencode/tools/skill_manager.ts";',
'const ctx = { sessionID: "learning-bridge-session" };',
'const call = (action, content) => memory.execute({ action, scope: "user", reason: "test", content }, ctx).then(JSON.parse);',
'const call = (action, content) => memory.execute({ action, scope: "user", ...(action === "list" ? {} : { reason: "test" }), content }, ctx).then(JSON.parse);',
'const before = await call("add", "用户偏好简洁答复");',
'await call("list"); const added = await call("add", "用户偏好简洁答复");',
'const skills = JSON.parse(await skill.execute({ action: "list", skill_path: "__root__", reason: "test" }, ctx));',
'const skills = JSON.parse(await skill.execute({ action: "list", skill_path: "__root__" }, ctx));',
'process.stdout.write(JSON.stringify({ before, added, skills }));',
].join("\n")], { cwd: process.cwd(), env: { ...process.env, AGENT_INTERNAL_TOKEN: "TEST_ONLY", TJWATER_AGENT_INTERNAL_BASE_URL: baseUrl }, stdout: "pipe", stderr: "pipe" });
const [exitCode, stdout, stderr] = await Promise.all([child.exited, new Response(child.stdout).text(), new Response(child.stderr).text()]);
@@ -48,6 +48,21 @@ describe("learning tools across process boundaries", () => {
expect(await memoryStore.list("user", "owner")).toHaveLength(1);
expect(await memoryStore.list("user", "other-owner")).toHaveLength(0);
const post = (sessionId: string, input: unknown, token = "TEST_ONLY") => fetch(`${baseUrl}/internal/tools/memory-manager`, { method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": token }, body: JSON.stringify({ session_id: sessionId, input }) });
// Invalid mutation reasons must fail before either store is changed.
for (const reason of [undefined, "", " ", "x".repeat(2001)]) {
expect((await post("learning-bridge-session", {
action: "remove", scope: "user", target_id: "anything", reason,
})).status).toBe(400);
const skillResponse = await fetch(`${baseUrl}/internal/tools/skill-manager`, {
method: "POST",
headers: { "Content-Type": "application/json", "x-agent-internal-token": "TEST_ONLY" },
body: JSON.stringify({ session_id: "learning-bridge-session", input: {
action: "write_skill", skill_path: "test", content: "must not be written", reason,
} }),
});
expect(skillResponse.status).toBe(400);
}
expect(await memoryStore.list("user", "owner")).toHaveLength(1);
expect((await post("missing", { action: "list", scope: "user" })).status).toBe(404);
expect((await post("learning-bridge-session", { action: "list", scope: "user", actorKey: "other-owner" })).status).toBe(400);
expect((await post("learning-bridge-session", { action: "list", scope: "user" }, "wrong")).status).toBe(403);
+14
View File
@@ -0,0 +1,14 @@
import { describe, expect, it } from "bun:test";
import { optionalToolReason, toolReason } from "../../.opencode/tool-reason.js";
describe("tool reason contract", () => {
it("allows omitted purpose but requires a nonblank business reason", () => {
expect(optionalToolReason.parse(undefined)).toBeUndefined();
expect(toolReason.safeParse(undefined).success).toBeFalse();
for (const schema of [optionalToolReason, toolReason]) {
expect(schema.parse(" 核验压力异常 ")).toBe("核验压力异常");
expect(schema.safeParse(" ").success).toBeFalse();
expect(schema.safeParse("x".repeat(2001)).success).toBeFalse();
}
});
});