Merge branch 'ag4'
Co-Authored-By: Claude Fable 5.1 <[email protected]> # Conflicts: # CHANGELOG.md
This commit is contained in:
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>;
|
||||
|
||||
@@ -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";
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"]),
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user