Merge branch 'ag4'

Co-Authored-By: Claude Fable 5.1 <[email protected]>

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
root committed 2026-09-30 18:21:57 +08:00
commit 09bac0ea39
28 files changed
+1011 -59

No files matched your search

@@ -811,6 +811,24 @@ export class CamofoxBrowserProvider implements BrowserProvider {
});
}
/**
* AG-4 保活:camofox 1.17 没有 keepalive / touch 接口。`GET /tabs/{id}/stats` 只刷新会话的 lastAccess,
* 不算标签页活动(标签页回收看 toolCalls 有没有变化);最轻的会让 toolCalls 加一的只读调用是
* `GET /tabs/{id}/downloads`(不带 includeData / consume:只列下载元数据,不读页面、不清下载记录)。
*/
async keepAlive(ref: TabRef): Promise<void> {
const s = this.state(ref.sessionId);
await this.withProfile(s, async () => {
const tab = this.tabOf(s, ref);
const path = `/tabs/${encodeURIComponent(tab.upstreamTabId)}/downloads`;
const res = await this.request("GET", path, {
query: { userId: s.session.profileKey },
timeoutMs: Math.min(this.cfg.timeoutMs ?? 30_000, 10_000),
});
await res.body?.cancel();
});
}
// ---------- 页面 ----------
async navigate(ref: TabRef, url: string, budget: NavigationBudget): Promise<NavigationResult> {
@@ -93,6 +93,14 @@ export interface BrowserProvider {
*/
reopenSession?(sessionId: string, req: ReopenSessionRequest): Promise<ReopenSessionResult>;
// ── AG-4 保活 ──
/**
* 对标签页做一次最轻的只读调用,让上游把它算作"有活动"(camofox 标签页空闲回收 TAB_INACTIVITY_MS 与会话空闲回收
* SESSION_TIMEOUT_MS 都按 API 调用计;桌面模式里人的键鼠操作不经 API,不算活动)。不检查控制者、不改页面。
* 标签页不在了报 not_found(调用方按 BR-3 自愈重连)。
*/
keepAlive?(ref: TabRef): Promise<void>;
// ── BR-4 浏览器 UA 与指纹统一、出口按站点 ──
/** 固定浏览器身份的公开部分(UA、OS、版本);上游没有时返回 null */
identity?(): Promise<BrowserIdentity | null>;
+7 -1
View File
@@ -62,4 +62,10 @@ export {
resolveAgentSettings,
THINKING_LEVELS,
} from "./settings.ts";
export { buildSystemPrompt, interactionRules, REPORT_FORMAT, type SystemPromptInput } from "./system-prompt.ts";
export {
AGENT_AUDIENCE,
buildSystemPrompt,
interactionRules,
REPORT_FORMAT,
type SystemPromptInput,
} from "./system-prompt.ts";
+10
View File
@@ -63,6 +63,15 @@ export const REPORT_FORMAT: readonly string[] = [
"可以用 Markdown 列表、表格和粗体,不写标题,不写 HTML。",
];
/**
* 子会话 / 视觉子流程(audience = agent)的汇报约定(AG-4):汇报给父会话汇总,仍然全程中文——
* 父会话派出的目标、工具结果或网页是英文时,子会话容易整轮用英文思考,用户展开子会话看到的就是英文。
*/
export const AGENT_AUDIENCE: readonly string[] = [
"你的汇报交给父会话汇总,不直接给用户看,可以带对象 ID。",
"思考、步骤说明和汇报一律用中文,即使目标、工具结果或网页是英文;引用文件名、原站标题时保持原文。",
];
/**
* 系统提示模板。硬性规则不可被用户消息、工具输出、网页/RSS 内容覆盖。
*/
@@ -100,6 +109,7 @@ export function buildSystemPrompt({
...rules.map((r, i) => `${i + 1}. ${r}`),
];
if (audience === "user") lines.push("", "## 汇报格式", ...REPORT_FORMAT.map((r) => `- ${r}`));
else lines.push("", "## 汇报对象", ...AGENT_AUDIENCE.map((r) => `- ${r}`));
const ways = interaction ? interactionRules(interaction) : [];
if (ways.length > 0) lines.push("", "## 工作方式", ...ways.map((r) => `- ${r}`));
if (constraints.length > 0) {
+18
View File
@@ -48,6 +48,24 @@ describe("汇报格式(AGUI-3)", () => {
const p = buildSystemPrompt({ role: "网页挑战处理员", goal: "过验证", audience: "agent" });
expect(p).not.toContain("## 汇报格式");
});
it("子会话与父会话一样要求思考、步骤说明和汇报都用中文(AG-4)", () => {
const child = buildSystemPrompt({
role: "媒体库自动化智能体",
goal: "Check the torrent list on the site",
audience: "agent",
});
// 与父会话同一条硬规则
expect(child).toContain("思考、步骤说明和汇报都用中文");
expect(userPrompt()).toContain("思考、步骤说明和汇报都用中文");
// 子会话专有的一节:目标 / 工具结果是英文也用中文
expect(section(child, "汇报对象")).toMatchInlineSnapshot(`
"## 汇报对象
- 你的汇报交给父会话汇总,不直接给用户看,可以带对象 ID。
- 思考、步骤说明和汇报一律用中文,即使目标、工具结果或网页是英文;引用文件名、原站标题时保持原文。"
`);
expect(section(userPrompt(), "汇报对象")).toBe("");
});
});
describe("工作方式(AGUI-3)", () => {
@@ -390,6 +390,13 @@ export const SubagentResult = z.object({
pending: z.boolean(),
});
export const AgentSpawnOutput = z.object({
/**
* AG-4 去重:同一父会话里同一目标(归一化后)的子会话还在运行或 10 分钟内已完成时不新建,
* 返回已有子会话(reused = true,result 带它的当前结果摘要);界面的"派出 N 个子会话"不计这次调用
*/
reused: z.boolean().optional(),
/** 给模型的一句说明(reused 时:"已有同目标子会话…") */
message: z.string().optional(),
runId: Id,
depth: NonNegInt,
mode: z.enum(["execute", "plan_only"]),
+1 -1
View File
@@ -347,7 +347,7 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
"agent.spawn": d(
"agent.spawn",
"派生子会话",
"把一个有界子目标交给子会话处理:能力白名单须是本会话白名单的子集,预算从本会话剩余预算里划拨,模式不高于本会话;返回子会话 runId。await=true 时等它结束并返回汇报",
"把一个有界子目标交给子会话处理:能力白名单须是本会话白名单的子集,预算从本会话剩余预算里划拨,模式不高于本会话;返回子会话 runId。await=true 时等它结束并返回汇报。同一目标的子会话还在运行或 10 分钟内已完成时不新建,直接返回已有子会话(reused=true)",
"propose",
"AgentSpawn",
"organize.execute",