feat(browser): complete camofox BrowserProvider with tabs, interaction, download capture and control lease

Contracts for browser API and browser.* agent capabilities; adapter implements
close/list/restore sessions, tabs + reconcile, history, click/type/select/scroll/press,
extract, waitForDownload (staged via DownloadSink, sha256), stale-ref guard and
automation/human actor check. testkit mock camofox gains an interactive site
(login redirect, captcha, click-driven page changes, downloads, PNG screenshots).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 20:17:46 +08:00
1 parent 62290d4477
commit c17d4b90bd
12 files changed
+1832 -101

No files matched your search

+9 -17
View File
@@ -84,14 +84,13 @@ async function rejectKind(p: Promise<unknown>): Promise<string> {
}
describe("能力声明", () => {
it("静态声明:已实现能力 supported、M2 能力 false 并注明,默认全部未验证", () => {
it("静态声明:全部能力 supported,verified 只来自配置;待核验项有注明", () => {
const c = camofoxStaticCapabilities({ snapshot: true, interact: true });
expect(c.navigate).toMatchObject({ supported: true, verified: false, source: "static" });
expect(c.snapshot).toMatchObject({ supported: true, verified: true });
// 未实现的能力即使配置了 verified 也不会被标为已验证
expect(c.interact).toMatchObject({ supported: false, verified: false });
expect(c.interact.note).toContain("M2");
expect(c.takeover.supported).toBe(false);
expect(c.interact).toMatchObject({ supported: true, verified: true });
expect(c.interact.note).toContain("待核验");
expect(c.takeover).toMatchObject({ supported: true, verified: false });
expect(c.upstreamVersion).toBeNull();
});
@@ -279,8 +278,8 @@ describe("串行锁", () => {
});
});
describe("M2 未实现分支", () => {
it("click/type/extract/waitForDownload/接管/关闭会话均抛 not_implemented", async () => {
describe("未知会话", () => {
it("交互/提取/下载/关闭对不存在的会话报 not_found;listSessions 只列内存镜像", async () => {
const { provider } = await setup();
const ref = { sessionId: "x" };
const calls: Promise<unknown>[] = [
@@ -288,18 +287,11 @@ describe("M2 未实现分支", () => {
provider.type(ref, { ref: "e1" }, { kind: "text", text: "x" }),
provider.extract(ref, {}),
provider.waitForDownload(ref, { timeoutMs: 10, maxBytes: 10 }),
provider.acquireControl({
sessionId: "x",
holderKind: "human",
holder: "admin",
reason: "登录验证",
ttlMs: 1000,
}),
provider.releaseControl("lease"),
provider.closeSession("x"),
provider.listSessions(),
];
for (const c of calls) expect(await rejectKind(c)).toBe("not_implemented");
for (const c of calls) expect(await rejectKind(c)).toBe("not_found");
expect(await rejectKind(provider.releaseControl("lease"))).toBe("not_found");
expect(await provider.listSessions()).toEqual([]);
});
});