feat: advance browser agent integration workflows

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent af5a4db694
commit 0c8e1c7b69
215 changed files with 35137 additions and 5442 deletions
+178
View File
@@ -0,0 +1,178 @@
import { describe, expect, it } from 'vitest';
import {
BRIDGE_CAPABILITIES,
canonicalCapabilityCatalogPayload,
capabilityBaseScope,
getBridgeCapabilityCatalog,
} from './capabilities';
describe('versioned Bridge capability catalog', () => {
it('derives every advertised method from one schema-backed catalog', async () => {
const catalog = await getBridgeCapabilityCatalog();
expect(catalog.version).toBe(1);
expect(catalog.schemaDialect).toBe('http://json-schema.org/draft-07/schema#');
expect(catalog.hash).toMatch(/^[a-f0-9]{64}$/);
expect(catalog.capabilities.map((capability) => capability.method)).toEqual(BRIDGE_CAPABILITIES);
expect(new Set(catalog.capabilities.map((capability) => capability.method)).size).toBe(BRIDGE_CAPABILITIES.length);
expect(new TextEncoder().encode(JSON.stringify(catalog)).byteLength).toBeLessThan(1024 * 1024);
});
it('publishes the real validation constraints and target semantics', async () => {
const catalog = await getBridgeCapabilityCatalog();
const evalCapability = catalog.capabilities.find((capability) => capability.method === 'browser.eval');
expect(evalCapability).toMatchObject({
domain: 'page',
access: 'dangerous',
scopes: ['browser.page.eval.expression'],
targetMode: 'document',
defaultTimeoutMs: 60_000,
});
expect(evalCapability?.conditionalScopes).toContainEqual({
scope: 'browser.page.eval.program',
when: 'mode=program',
});
expect(JSON.stringify(evalCapability?.paramsSchema)).toContain('"mode"');
expect(JSON.stringify(evalCapability?.paramsSchema)).toContain('"program"');
expect(capabilityBaseScope('browser.profile.validate')).toBe('browser.transform.execute');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.transform.recovery.capture')).toMatchObject({
access: 'dangerous',
scopes: ['browser.transform.manage', 'browser.debugger.control', 'browser.callable.execute'],
targetMode: 'profile',
});
expect(capabilityBaseScope('browser.transform.recovery.validate')).toBe('browser.transform.execute');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.isolation.proof')).toMatchObject({
domain: 'isolation',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.isolation.incognito.open')).toMatchObject({
domain: 'isolation',
access: 'dangerous',
scopes: ['browser.isolation.manage'],
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.capture')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.attest')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.context.attestation.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.capture')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.logical.bind')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: expect.arrayContaining(['browser.network.sensitive.read', 'browser.transform.execute']),
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.candidates')).toMatchObject({
domain: 'authorization',
access: 'read',
scopes: ['browser.network.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'document',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.resource.get')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: ['browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.packet.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: ['browser.network.replay', 'browser.network.sensitive.read', 'browser.isolation.read', 'browser.cookies.read', 'browser.storage.read'],
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.transform.inspect')).toMatchObject({
domain: 'authorization',
access: 'sensitive-read',
scopes: expect.arrayContaining(['browser.transform.read']),
targetMode: 'none',
});
expect(catalog.capabilities.find((capability) => capability.method === 'browser.authorization.baseline.transform.compile')).toMatchObject({
domain: 'authorization',
access: 'dangerous',
scopes: expect.arrayContaining(['browser.network.replay', 'browser.transform.execute']),
targetMode: 'none',
});
expect(capabilityBaseScope('browser.authorization.baseline.compile')).toBe('browser.network.replay');
expect(capabilityBaseScope('browser.authorization.baseline.packet.compile')).toBe('browser.network.replay');
expect(capabilityBaseScope('browser.authorization.baseline.transform.compile')).toBe('browser.network.replay');
expect(catalog.capabilities.find((capability) => capability.method === 'browser.isolation.container.open')).toBeUndefined();
expect(capabilityBaseScope('missing.capability')).toBeUndefined();
});
it('keeps canonical payloads stable and excludes the transmitted hash', async () => {
const catalog = await getBridgeCapabilityCatalog();
const payload = canonicalCapabilityCatalogPayload({
version: catalog.version,
schemaDialect: catalog.schemaDialect,
capabilities: catalog.capabilities,
});
expect(payload).not.toContain(catalog.hash);
expect(payload).toBe(canonicalCapabilityCatalogPayload({
capabilities: catalog.capabilities,
schemaDialect: catalog.schemaDialect,
version: catalog.version,
}));
});
it('matches the Go canonical JSON and SHA-256 test vector', async () => {
const payload = canonicalCapabilityCatalogPayload({
version: 1,
schemaDialect: 'http://json-schema.org/draft-07/schema#',
capabilities: [{
method: 'browser.tabs',
domain: 'page',
access: 'read',
summary: 'List tabs',
scopes: ['browser.tabs.read'],
targetMode: 'none',
defaultTimeoutMs: 20_000,
paramsSchema: { type: 'object', properties: {}, additionalProperties: false },
}],
});
expect(payload).toBe(
'{"capabilities":[{"access":"read","defaultTimeoutMs":20000,"domain":"page","method":"browser.tabs","paramsSchema":{"additionalProperties":false,"properties":{},"type":"object"},"scopes":["browser.tabs.read"],"summary":"List tabs","targetMode":"none"}],"schemaDialect":"http://json-schema.org/draft-07/schema#","version":1}',
);
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(payload));
const hash = [...new Uint8Array(digest)]
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('');
expect(hash).toBe('82bc1210338e773137b95359ca0b39c0443bae3d9fdae33f97942cf82119006f');
});
});