Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/app/service/agent/core/compact_prompt.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { describe, expect, it } from "vitest";
import { extractSummary, buildCompactUserPrompt } from "./compact_prompt";
import { COMPACT_SYSTEM_PROMPT, extractSummary, buildCompactUserPrompt } from "./compact_prompt";

describe("COMPACT_SYSTEM_PROMPT", () => {
it("要求忠实记录失败、歧义和更正历史", () => {
expect(COMPACT_SYSTEM_PROMPT).toContain("**Fidelity requirement:**");
expect(COMPACT_SYSTEM_PROMPT).toContain("If a step failed, record the failure");
expect(COMPACT_SYSTEM_PROMPT).toContain("If a result was ambiguous, record the ambiguity");
expect(COMPACT_SYSTEM_PROMPT).toContain("record both");
expect(COMPACT_SYSTEM_PROMPT).toContain("Do not revise history");
});
});

describe("extractSummary", () => {
it("extracts content from <summary> tags", () => {
Expand Down
4 changes: 3 additions & 1 deletion src/app/service/agent/core/compact_prompt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const COMPACT_SYSTEM_PROMPT = `You are a conversation summarizer. Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. This summary will replace the conversation history, enabling efficient task resumption in a new context window.`;
export const COMPACT_SYSTEM_PROMPT = `You are a conversation summarizer. Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. This summary will replace the conversation history, enabling efficient task resumption in a new context window.

**Fidelity requirement:** Summarize what actually happened — not what was intended or what would have been ideal. If a step failed, record the failure. If a result was ambiguous, record the ambiguity. If the agent's approach was wrong and corrected, record both. Do not revise history to make the prior work look cleaner than it was. An accurate summary of a messy situation is more useful than a polished summary of a fictional one.`;

export function buildCompactUserPrompt(customInstruction?: string): string {
let prompt = `Write a structured, concise, and actionable continuation summary of the conversation so far. First analyze the conversation in <analysis> tags, then write the summary in <summary> tags.
Expand Down
144 changes: 144 additions & 0 deletions src/app/service/agent/core/sub_agent_types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,40 @@ import { describe, it, expect } from "vitest";
import { resolveSubAgentType, getExcludeToolsForType, SUB_AGENT_TYPES } from "./sub_agent_types";

describe("Sub-Agent 类型系统", () => {
describe("内置类型提示词", () => {
it.concurrent.each(["researcher", "page_operator", "general"])(
"%s 包含思考风格、认识论纪律与情绪校准",
(typeName) => {
const prompt = SUB_AGENT_TYPES[typeName].systemPromptAddition;

expect(prompt).toContain("**Thinking style:**");
expect(prompt).toContain("**Epistemic discipline — strictly required:**");
expect(prompt).toContain("**Emotional calibration:**");
}
);
});

describe("resolveSubAgentType", () => {
it.concurrent("返回指定的内置类型", () => {
expect(resolveSubAgentType("researcher")).toBe(SUB_AGENT_TYPES.researcher);
expect(resolveSubAgentType("page_operator")).toBe(SUB_AGENT_TYPES.page_operator);
expect(resolveSubAgentType("general")).toBe(SUB_AGENT_TYPES.general);
expect(resolveSubAgentType("data_processor")).toBe(SUB_AGENT_TYPES.data_processor);
expect(resolveSubAgentType("form_filler")).toBe(SUB_AGENT_TYPES.form_filler);
expect(resolveSubAgentType("content_writer")).toBe(SUB_AGENT_TYPES.content_writer);
expect(resolveSubAgentType("script_engineer")).toBe(SUB_AGENT_TYPES.script_engineer);
});

it.each([
"summarizer",
"data_validator",
"diff_checker",
"page_extractor",
"file_converter",
"action_reviewer",
"script_auditor",
])("返回 %s 辅助类型", (typeName) => {
expect(resolveSubAgentType(typeName)).toBe(SUB_AGENT_TYPES[typeName]);
});

it.concurrent("未知类型抛错(防止攻击者传 xxx 获得更宽权限)", () => {
Expand Down Expand Up @@ -121,5 +150,120 @@ describe("Sub-Agent 类型系统", () => {
expect(excluded).toContain("execute_script");
expect(excluded).not.toContain("web_fetch");
});

it.concurrent.each([
["data_processor", ["execute_script", "opfs_read", "opfs_write"], ["web_fetch", "web_search", "get_tab_content"]],
[
"form_filler",
["get_tab_content", "activate_tab", "read_form_field", "fill_form_field"],
["execute_script", "web_fetch", "web_search", "open_tab"],
],
["content_writer", ["execute_script", "opfs_read", "opfs_write"], ["web_fetch", "get_tab_content", "open_tab"]],
["script_engineer", ["execute_script", "opfs_read", "web_fetch"], ["web_search", "get_tab_content", "open_tab"]],
])("%s 仅保留职责所需工具", (typeName, includedTools, excludedTools) => {
const excluded = getExcludeToolsForType(SUB_AGENT_TYPES[typeName as string], allTools);

for (const tool of includedTools) expect(excluded).not.toContain(tool);
for (const tool of excludedTools) expect(excluded).toContain(tool);
expect(excluded).toContain("ask_user");
expect(excluded).toContain("agent");
});
});

describe("专项类型提示词", () => {
it.concurrent.each([
["data_processor", "## Role: Data Processor", "All input data must be passed"],
["form_filler", "## Role: Form Filler", "Never click submit"],
["content_writer", "## Role: Content Writer", "Do not introduce facts"],
["script_engineer", "## Role: Script Engineer", "cannot install scripts into ScriptCat directly"],
])("%s 包含角色边界", (typeName, role, boundary) => {
const prompt = SUB_AGENT_TYPES[typeName as string].systemPromptAddition;

expect(prompt).toContain(role);
expect(prompt).toContain(boundary);
});
});

describe("辅助类型职责边界", () => {
const availableTools = [
"web_fetch",
"web_search",
"opfs_read",
"opfs_write",
"opfs_list",
"opfs_delete",
"execute_script",
"get_tab_content",
"list_tabs",
"open_tab",
"close_tab",
"activate_tab",
"ask_user",
"agent",
];
it.each([
["summarizer", "## Role: Summarizer", "do not rewrite"],
["data_validator", "## Role: Data Validator", "do not modify"],
["diff_checker", "## Role: Diff Checker", "what changed"],
["page_extractor", "## Role: Page Extractor", "read-only"],
["file_converter", "## Role: File Converter", "schema"],
["action_reviewer", "## Role: Action Reviewer", "cannot execute"],
["script_auditor", "## Role: Script Auditor", "static analysis"],
])("%s 包含独立角色边界", (typeName, role, boundary) => {
const config = SUB_AGENT_TYPES[typeName];
expect(config.systemPromptAddition).toContain(role);
expect(config.systemPromptAddition.toLowerCase()).toContain(boundary.toLowerCase());
});

it.each(["summarizer", "data_validator", "diff_checker", "file_converter", "action_reviewer", "script_auditor"])(
"%s 只能在 sandbox 执行脚本",
(typeName) => {
expect(SUB_AGENT_TYPES[typeName].executeScriptTargets).toEqual(["sandbox"]);
}
);

it("page_extractor 不应具有任意脚本或交互能力", () => {
const tools = SUB_AGENT_TYPES.page_extractor.allowedTools;
expect(tools).toEqual(expect.arrayContaining(["get_tab_content", "open_tab", "close_tab", "web_fetch"]));
expect(tools).not.toEqual(expect.arrayContaining(["execute_script", "activate_tab"]));
});

it("file_converter 不应具有删除文件权限", () => {
expect(SUB_AGENT_TYPES.file_converter.allowedTools).not.toContain("opfs_delete");
});

it.each([
["summarizer", ["execute_script", "opfs_read", "opfs_write"], ["web_fetch", "get_tab_content"]],
["data_validator", ["execute_script", "opfs_read"], ["opfs_write", "web_fetch"]],
["diff_checker", ["execute_script", "opfs_read", "opfs_write"], ["web_fetch", "get_tab_content"]],
["page_extractor", ["web_fetch", "open_tab", "get_tab_content", "close_tab"], ["execute_script", "list_tabs"]],
["file_converter", ["execute_script", "opfs_read", "opfs_write"], ["opfs_delete", "web_fetch"]],
["action_reviewer", ["execute_script", "opfs_read"], ["opfs_write", "get_tab_content"]],
["script_auditor", ["execute_script", "opfs_read"], ["opfs_write", "get_tab_content"]],
])("%s 的最终工具集合遵守职责边界", (typeName, included, forbidden) => {
const excluded = getExcludeToolsForType(SUB_AGENT_TYPES[typeName], availableTools);
for (const tool of included) expect(excluded).not.toContain(tool);
for (const tool of forbidden) expect(excluded).toContain(tool);
});
});

describe("专项类型 execute_script 运行环境", () => {
it.concurrent.each([
["data_processor", "sandbox", "page"],
["content_writer", "sandbox", "page"],
["script_engineer", "sandbox", "page"],
])("%s 限制 execute_script 运行环境", (typeName, allowedTarget, deniedTarget) => {
const config = SUB_AGENT_TYPES[typeName as string];

expect(config.executeScriptTargets).toContain(allowedTarget);
expect(config.executeScriptTargets).not.toContain(deniedTarget);
});

it("form_filler 不应获得任意脚本执行能力", () => {
expect(SUB_AGENT_TYPES.form_filler.allowedTools).not.toContain("execute_script");
expect(SUB_AGENT_TYPES.form_filler.allowedTools).toEqual(
expect.arrayContaining(["read_form_field", "fill_form_field"])
);
});
});
});
Loading
Loading