mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(protocol): integrate with vom
This commit is contained in:
@@ -26,20 +26,20 @@ function handshake(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("computeConnectedState (protocol-based compat)", () => {
|
describe("computeConnectedState (protocol-based compat)", () => {
|
||||||
it("returns connected when daemon protocol equals extension protocol", () => {
|
it("returns connected when protocol strings match", () => {
|
||||||
expect(computeConnectedState(handshake("1.1", "1.0"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
|
expect(computeConnectedState(handshake("1.0", "1.0"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
|
||||||
kind: "connected",
|
kind: "connected",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns version_skew when daemon protocol minor is newer", () => {
|
it("returns version_skew when daemon protocol minor is newer", () => {
|
||||||
expect(computeConnectedState(handshake("1.2", "1.0"))).toEqual({
|
expect(computeConnectedState(handshake("1.1", "1.0"))).toEqual({
|
||||||
kind: "version_skew",
|
kind: "version_skew",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns version_skew when daemon protocol string differs but floor is satisfied", () => {
|
it("returns version_skew when daemon protocol string differs but floor is satisfied", () => {
|
||||||
expect(computeConnectedState(handshake("1.1.0", "1.0"))).toEqual({
|
expect(computeConnectedState(handshake("1", "1.0"))).toEqual({
|
||||||
kind: "version_skew",
|
kind: "version_skew",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,7 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("rejects when extension is below daemon min_compatible_protocol", () => {
|
it("rejects when extension is below daemon min_compatible_protocol", () => {
|
||||||
const result = computeConnectedState(handshake("1.1", "1.5"));
|
const result = computeConnectedState(handshake("1.0", "1.5"));
|
||||||
expect(result.kind).toBe("rejected");
|
expect(result.kind).toBe("rejected");
|
||||||
if (result.kind === "rejected") {
|
if (result.kind === "rejected") {
|
||||||
expect(result.reason).toContain("min_compatible_protocol");
|
expect(result.reason).toContain("min_compatible_protocol");
|
||||||
@@ -65,7 +65,7 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
|||||||
const result = computeConnectedState({
|
const result = computeConnectedState({
|
||||||
server: "browser-skill-daemon",
|
server: "browser-skill-daemon",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
protocol_version: "1.1",
|
protocol_version: "1.0",
|
||||||
min_compatible_peer: "0.1.0",
|
min_compatible_peer: "0.1.0",
|
||||||
});
|
});
|
||||||
expect(result).toEqual({ kind: "connected" });
|
expect(result).toEqual({ kind: "connected" });
|
||||||
@@ -79,14 +79,8 @@ describe("computeConnectedState (protocol-based compat)", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns version_skew when daemon protocol is 1.0 and floor is satisfied", () => {
|
|
||||||
expect(computeConnectedState(handshake("1.0", "1.0"))).toEqual({
|
|
||||||
kind: "version_skew",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects malformed daemon min_compatible_protocol with a daemon-floor reason", () => {
|
it("rejects malformed daemon min_compatible_protocol with a daemon-floor reason", () => {
|
||||||
const result = computeConnectedState(handshake("1.1", "not-a-protocol"));
|
const result = computeConnectedState(handshake("1.0", "not-a-protocol"));
|
||||||
expect(result.kind).toBe("rejected");
|
expect(result.kind).toBe("rejected");
|
||||||
if (result.kind === "rejected") {
|
if (result.kind === "rejected") {
|
||||||
expect(result.reason).toContain("daemon min_compatible_protocol");
|
expect(result.reason).toContain("daemon min_compatible_protocol");
|
||||||
@@ -248,11 +242,11 @@ describe("ConnectionController connectionEnabled", () => {
|
|||||||
const second = transport.send.mock.calls[1]?.[0] as { id: string };
|
const second = transport.send.mock.calls[1]?.[0] as { id: string };
|
||||||
expect(second.id).not.toBe(first.id);
|
expect(second.id).not.toBe(first.id);
|
||||||
|
|
||||||
transport.emitMessage({ id: first.id, result: handshake("1.1", "1.0") });
|
transport.emitMessage({ id: first.id, result: handshake("1.0", "1.0") });
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(controller.snapshot().state).not.toBe("connected");
|
expect(controller.snapshot().state).not.toBe("connected");
|
||||||
|
|
||||||
transport.emitMessage({ id: second.id, result: handshake("1.1", "1.0") });
|
transport.emitMessage({ id: second.id, result: handshake("1.0", "1.0") });
|
||||||
await vi.waitFor(() => expect(controller.snapshot().state).toBe("connected"));
|
await vi.waitFor(() => expect(controller.snapshot().state).toBe("connected"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { DraftTraceStep, PageRef, SelectedOption, Step } from "@/transport/types";
|
import type { DraftTraceStep, PageRefV2, SelectedOptionV2, StepV2 } from "@/transport/types";
|
||||||
|
|
||||||
const CLIPBOARD_KEYS = new Set(["a", "c", "v", "x", "A", "C", "V", "X"]);
|
const CLIPBOARD_KEYS = new Set(["a", "c", "v", "x", "A", "C", "V", "X"]);
|
||||||
const MODIFIER_ONLY_KEYS = new Set(["Meta", "Control", "Alt", "Shift", "OS", "Hyper", "Super"]);
|
const MODIFIER_ONLY_KEYS = new Set(["Meta", "Control", "Alt", "Shift", "OS", "Hyper", "Super"]);
|
||||||
@@ -60,7 +60,7 @@ function collectUrls(steps: DraftTraceStep[], startUrl?: string): string[] {
|
|||||||
function buildPageRegistry(
|
function buildPageRegistry(
|
||||||
steps: DraftTraceStep[],
|
steps: DraftTraceStep[],
|
||||||
startUrl?: string,
|
startUrl?: string,
|
||||||
): { pages: PageRef[]; urlToId: Map<string, string> } {
|
): { pages: PageRefV2[]; urlToId: Map<string, string> } {
|
||||||
const urls = collectUrls(steps, startUrl);
|
const urls = collectUrls(steps, startUrl);
|
||||||
const urlToId = new Map<string, string>();
|
const urlToId = new Map<string, string>();
|
||||||
const pages = urls.map((url, index) => {
|
const pages = urls.map((url, index) => {
|
||||||
@@ -90,19 +90,19 @@ function pageUrlForDraft(step: DraftTraceStep, fallbackUrl?: string): string | u
|
|||||||
function effectForNavigation(
|
function effectForNavigation(
|
||||||
navigatedTo: string | undefined,
|
navigatedTo: string | undefined,
|
||||||
urlToId: Map<string, string>,
|
urlToId: Map<string, string>,
|
||||||
): Step["effect"] {
|
): StepV2["effect"] {
|
||||||
if (!navigatedTo) return undefined;
|
if (!navigatedTo) return undefined;
|
||||||
const pageId = urlToId.get(navigatedTo);
|
const pageId = urlToId.get(navigatedTo);
|
||||||
if (!pageId) return undefined;
|
if (!pageId) return undefined;
|
||||||
return { navigated_to: pageId };
|
return { navigated_to: pageId };
|
||||||
}
|
}
|
||||||
|
|
||||||
function withEffect(step: Step, effect: Step["effect"]): Step {
|
function withEffect(step: StepV2, effect: StepV2["effect"]): StepV2 {
|
||||||
if (!effect) return step;
|
if (!effect) return step;
|
||||||
return { ...step, effect };
|
return { ...step, effect };
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSelection(values: string[], labels?: string[]): SelectedOption[] {
|
function toSelection(values: string[], labels?: string[]): SelectedOptionV2[] {
|
||||||
return values.map((value, index) => ({
|
return values.map((value, index) => ({
|
||||||
value,
|
value,
|
||||||
...(labels?.[index] ? { label: labels[index] } : {}),
|
...(labels?.[index] ? { label: labels[index] } : {}),
|
||||||
@@ -114,7 +114,7 @@ function toV2Step(
|
|||||||
id: number,
|
id: number,
|
||||||
urlToId: Map<string, string>,
|
urlToId: Map<string, string>,
|
||||||
fallbackUrl?: string,
|
fallbackUrl?: string,
|
||||||
): Step | null {
|
): StepV2 | null {
|
||||||
if (!shouldIncludeDraft(step)) return null;
|
if (!shouldIncludeDraft(step)) return null;
|
||||||
|
|
||||||
const pageUrl = pageUrlForDraft(step, fallbackUrl);
|
const pageUrl = pageUrlForDraft(step, fallbackUrl);
|
||||||
@@ -181,8 +181,8 @@ function toV2Step(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ReducedTrace {
|
export interface ReducedTrace {
|
||||||
pages: PageRef[];
|
pages: PageRefV2[];
|
||||||
steps: Step[];
|
steps: StepV2[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,7 +192,7 @@ export interface ReducedTrace {
|
|||||||
export function reduceTraceSteps(steps: DraftTraceStep[], startUrl?: string): ReducedTrace {
|
export function reduceTraceSteps(steps: DraftTraceStep[], startUrl?: string): ReducedTrace {
|
||||||
const collapsed = collapseNavigations(steps);
|
const collapsed = collapseNavigations(steps);
|
||||||
const { pages, urlToId } = buildPageRegistry(collapsed, startUrl);
|
const { pages, urlToId } = buildPageRegistry(collapsed, startUrl);
|
||||||
const out: Step[] = [];
|
const out: StepV2[] = [];
|
||||||
let id = 1;
|
let id = 1;
|
||||||
let lastUrl = startUrl;
|
let lastUrl = startUrl;
|
||||||
for (const draft of collapsed) {
|
for (const draft of collapsed) {
|
||||||
@@ -210,7 +210,7 @@ export function reduceTraceSteps(steps: DraftTraceStep[], startUrl?: string): Re
|
|||||||
export function resolveTraceStartUrl(
|
export function resolveTraceStartUrl(
|
||||||
drafts: DraftTraceStep[],
|
drafts: DraftTraceStep[],
|
||||||
startUrl?: string,
|
startUrl?: string,
|
||||||
pages?: PageRef[],
|
pages?: PageRefV2[],
|
||||||
): string {
|
): string {
|
||||||
if (startUrl) return startUrl;
|
if (startUrl) return startUrl;
|
||||||
const navigate = drafts.find((step): step is Extract<DraftTraceStep, { op: "navigate" }> => {
|
const navigate = drafts.find((step): step is Extract<DraftTraceStep, { op: "navigate" }> => {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import type {
|
|||||||
RecordStopParams,
|
RecordStopParams,
|
||||||
RecordStopResult,
|
RecordStopResult,
|
||||||
RpcError,
|
RpcError,
|
||||||
Trace,
|
TraceV2,
|
||||||
} from "@/transport/types";
|
} from "@/transport/types";
|
||||||
import { handleNavigate } from "./navigation";
|
import { handleNavigate } from "./navigation";
|
||||||
import {
|
import {
|
||||||
@@ -50,8 +50,8 @@ interface ActiveRecording {
|
|||||||
steps: DraftTraceStep[];
|
steps: DraftTraceStep[];
|
||||||
startedAt: string;
|
startedAt: string;
|
||||||
startedAtMs: number;
|
startedAtMs: number;
|
||||||
finishPromise: Promise<Trace>;
|
finishPromise: Promise<TraceV2>;
|
||||||
resolveFinish: (trace: Trace) => void;
|
resolveFinish: (trace: TraceV2) => void;
|
||||||
rejectFinish: (err: Error) => void;
|
rejectFinish: (err: Error) => void;
|
||||||
settled: boolean;
|
settled: boolean;
|
||||||
finishing: boolean;
|
finishing: boolean;
|
||||||
@@ -154,7 +154,7 @@ async function sendRecordStartWithAck(
|
|||||||
throw lastError ?? new Error("failed to start recording in content script");
|
throw lastError ?? new Error("failed to start recording in content script");
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTrace(recording: ActiveRecording): Trace {
|
function buildTrace(recording: ActiveRecording): TraceV2 {
|
||||||
const { pages, steps } = reduceTraceSteps(recording.steps, recording.startUrl);
|
const { pages, steps } = reduceTraceSteps(recording.steps, recording.startUrl);
|
||||||
const startUrl = resolveTraceStartUrl(recording.steps, recording.startUrl, pages);
|
const startUrl = resolveTraceStartUrl(recording.steps, recording.startUrl, pages);
|
||||||
return {
|
return {
|
||||||
@@ -532,7 +532,7 @@ async function finishRecordingByRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function finishRecording(sessionId: string, deps: RecordDeps): Promise<Trace | null> {
|
async function finishRecording(sessionId: string, deps: RecordDeps): Promise<TraceV2 | null> {
|
||||||
const recording = recordings.get(sessionId);
|
const recording = recordings.get(sessionId);
|
||||||
if (!recording || recording.settled || recording.finishing) return null;
|
if (!recording || recording.settled || recording.finishing) return null;
|
||||||
recording.finishing = true;
|
recording.finishing = true;
|
||||||
@@ -574,9 +574,9 @@ export async function handleRecordStart(
|
|||||||
// on the destination page can RECORD_QUERY → rearm → show RecordOverlay
|
// on the destination page can RECORD_QUERY → rearm → show RecordOverlay
|
||||||
// instead of flashing ControlOverlay ("Agent 正在控制").
|
// instead of flashing ControlOverlay ("Agent 正在控制").
|
||||||
const requestId = makeRequestId(target.tabId);
|
const requestId = makeRequestId(target.tabId);
|
||||||
let resolveFinish!: (trace: Trace) => void;
|
let resolveFinish!: (trace: TraceV2) => void;
|
||||||
let rejectFinish!: (err: Error) => void;
|
let rejectFinish!: (err: Error) => void;
|
||||||
const finishPromise = new Promise<Trace>((resolve, reject) => {
|
const finishPromise = new Promise<TraceV2>((resolve, reject) => {
|
||||||
resolveFinish = resolve;
|
resolveFinish = resolve;
|
||||||
rejectFinish = reject;
|
rejectFinish = reject;
|
||||||
});
|
});
|
||||||
@@ -785,9 +785,9 @@ export async function handleRecordAwait(
|
|||||||
return { code: "cancelled", message: "record_await aborted" };
|
return { code: "cancelled", message: "record_await aborted" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const outcome = await new Promise<{ trace: Trace } | { error: RpcError }>((resolve) => {
|
const outcome = await new Promise<{ trace: TraceV2 } | { error: RpcError }>((resolve) => {
|
||||||
let settled = false;
|
let settled = false;
|
||||||
const finish = (result: { trace: Trace } | { error: RpcError }) => {
|
const finish = (result: { trace: TraceV2 } | { error: RpcError }) => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
if (timer) clearTimeout(timer);
|
if (timer) clearTimeout(timer);
|
||||||
|
|||||||
@@ -65,11 +65,6 @@ function deferredFakeTransport(): { transport: Transport; emit: (frame: Protocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("performHandshake", () => {
|
describe("performHandshake", () => {
|
||||||
it("advertises the protocol compatibility boundary", () => {
|
|
||||||
expect(PROTOCOL_VERSION).toBe("1.1");
|
|
||||||
expect(MIN_COMPATIBLE_PROTOCOL).toBe("1.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends system.handshake with identity and both compat fields", async () => {
|
it("sends system.handshake with identity and both compat fields", async () => {
|
||||||
let sentFrame: ProtocolFrame | null = null;
|
let sentFrame: ProtocolFrame | null = null;
|
||||||
const transport = fakeTransport((req) => {
|
const transport = fakeTransport((req) => {
|
||||||
@@ -79,7 +74,7 @@ describe("performHandshake", () => {
|
|||||||
result: {
|
result: {
|
||||||
server: "browser-skill-daemon",
|
server: "browser-skill-daemon",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
protocol_version: "1.1",
|
protocol_version: "1.0",
|
||||||
min_compatible_peer: "0.0.0",
|
min_compatible_peer: "0.0.0",
|
||||||
min_compatible_protocol: "1.0",
|
min_compatible_protocol: "1.0",
|
||||||
},
|
},
|
||||||
@@ -134,8 +129,8 @@ describe("performHandshake", () => {
|
|||||||
const response = {
|
const response = {
|
||||||
server: "browser-skill-daemon",
|
server: "browser-skill-daemon",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
protocol_version: "1.1",
|
protocol_version: "1.0",
|
||||||
min_compatible_protocol: "1.1",
|
min_compatible_protocol: "1.0",
|
||||||
} satisfies HandshakeResult;
|
} satisfies HandshakeResult;
|
||||||
const transport = fakeTransport((req) => ({
|
const transport = fakeTransport((req) => ({
|
||||||
id: (req as { id: string }).id,
|
id: (req as { id: string }).id,
|
||||||
@@ -150,7 +145,7 @@ describe("performHandshake", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(outcome.result.min_compatible_peer).toBeUndefined();
|
expect(outcome.result.min_compatible_peer).toBeUndefined();
|
||||||
expect(outcome.result.min_compatible_protocol).toBe("1.1");
|
expect(outcome.result.min_compatible_protocol).toBe("1.0");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects when the daemon responds with an error", async () => {
|
it("rejects when the daemon responds with an error", async () => {
|
||||||
@@ -183,9 +178,9 @@ describe("performHandshake", () => {
|
|||||||
result: {
|
result: {
|
||||||
server: "browser-skill-daemon",
|
server: "browser-skill-daemon",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
protocol_version: "1.1",
|
protocol_version: "1.0",
|
||||||
min_compatible_peer: "0.0.0",
|
min_compatible_peer: "0.0.0",
|
||||||
min_compatible_protocol: "1.1",
|
min_compatible_protocol: "1.0",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
emit({
|
emit({
|
||||||
@@ -193,14 +188,14 @@ describe("performHandshake", () => {
|
|||||||
result: {
|
result: {
|
||||||
server: "browser-skill-daemon",
|
server: "browser-skill-daemon",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
protocol_version: "1.1",
|
protocol_version: "1.0",
|
||||||
min_compatible_peer: "0.0.0",
|
min_compatible_peer: "0.0.0",
|
||||||
min_compatible_protocol: "1.1",
|
min_compatible_protocol: "1.0",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(pending).resolves.toMatchObject({
|
await expect(pending).resolves.toMatchObject({
|
||||||
result: { server: "browser-skill-daemon", protocol_version: "1.1" },
|
result: { server: "browser-skill-daemon", protocol_version: "1.0" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
ResponseFrame,
|
ResponseFrame,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export const PROTOCOL_VERSION = "1.1";
|
export const PROTOCOL_VERSION = "1.0";
|
||||||
/**
|
/**
|
||||||
* Extension semver, injected at build time from `package.json` via
|
* Extension semver, injected at build time from `package.json` via
|
||||||
* Vite's `define` (see `wxt.config.ts` and `vitest.config.ts`).
|
* Vite's `define` (see `wxt.config.ts` and `vitest.config.ts`).
|
||||||
|
|||||||
@@ -657,12 +657,10 @@ export interface EmulateResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Semantic record payloads — legacy Trace v2 shapes (`Trace`/`Step`) mirror
|
// Semantic record payloads mirror the versioned Rust protocol models.
|
||||||
// bsk-protocol record_v2.rs; Trace v3 shapes (`TraceV3`/`StepV3`) mirror
|
|
||||||
// record.rs (note: in Rust, `Trace` is the v3 shape).
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
export const TRACE_VERSION = 3;
|
export const TRACE_VERSION_V3 = 3;
|
||||||
export const TRACE_VERSION_V2 = 2;
|
export const TRACE_VERSION_V2 = 2;
|
||||||
export const DEFAULT_TRACE_VERSION = 2;
|
export const DEFAULT_TRACE_VERSION = 2;
|
||||||
export const VOM_FORMAT_VERSION = 1;
|
export const VOM_FORMAT_VERSION = 1;
|
||||||
@@ -682,25 +680,22 @@ export interface RecorderInfo {
|
|||||||
|
|
||||||
export type StopReason = "user_finish" | "cli_stop";
|
export type StopReason = "user_finish" | "cli_stop";
|
||||||
|
|
||||||
export interface TraceState {
|
export interface TraceStateV3 {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
/** Wire-only: full page observation text. */
|
body: string;
|
||||||
body?: string;
|
|
||||||
/** Disk-only: filename under the bundle `pages/` directory. */
|
|
||||||
page?: string;
|
|
||||||
truncated?: boolean;
|
truncated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepResult {
|
export interface StepResultV3 {
|
||||||
state: string;
|
state: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepCommonV3 {
|
export interface StepCommonV3 {
|
||||||
id: number;
|
id: number;
|
||||||
state: string;
|
state: string;
|
||||||
result: StepResult;
|
result: StepResultV3;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavigationCause =
|
export type NavigationCause =
|
||||||
@@ -715,7 +710,7 @@ export type NavigationCause =
|
|||||||
export type FillCommit = "enter" | "suggestion" | "blur";
|
export type FillCommit = "enter" | "suggestion" | "blur";
|
||||||
|
|
||||||
/** Legacy v2 target shape retained for existing record producers. */
|
/** Legacy v2 target shape retained for existing record producers. */
|
||||||
export interface TargetDescriptor {
|
export interface TargetDescriptorV2 {
|
||||||
role?: string;
|
role?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
@@ -728,43 +723,43 @@ export interface TraceEntry {
|
|||||||
start_url: string;
|
start_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageRef {
|
export interface PageRefV2 {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectedOption {
|
export interface SelectedOptionV2 {
|
||||||
value: string;
|
value: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepEffect {
|
export interface StepEffectV2 {
|
||||||
navigated_to: string;
|
navigated_to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepCommon {
|
export interface StepCommonV2 {
|
||||||
id: number;
|
id: number;
|
||||||
page: string;
|
page: string;
|
||||||
effect?: StepEffect;
|
effect?: StepEffectV2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Capture/buffer draft before v2 reduction. */
|
/** Capture/buffer draft before v2 reduction. */
|
||||||
export type DraftTraceStep =
|
export type DraftTraceStep =
|
||||||
| {
|
| {
|
||||||
op: "click";
|
op: "click";
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
navigated_to?: string;
|
navigated_to?: string;
|
||||||
page_url?: string;
|
page_url?: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: "hover";
|
op: "hover";
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
page_url?: string;
|
page_url?: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: "fill";
|
op: "fill";
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
value: string;
|
value: string;
|
||||||
redacted?: boolean;
|
redacted?: boolean;
|
||||||
page_url?: string;
|
page_url?: string;
|
||||||
@@ -772,14 +767,14 @@ export type DraftTraceStep =
|
|||||||
| {
|
| {
|
||||||
op: "press";
|
op: "press";
|
||||||
key: string;
|
key: string;
|
||||||
target?: TargetDescriptor;
|
target?: TargetDescriptorV2;
|
||||||
modifiers?: KeyModifier[];
|
modifiers?: KeyModifier[];
|
||||||
navigated_to?: string;
|
navigated_to?: string;
|
||||||
page_url?: string;
|
page_url?: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: "select";
|
op: "select";
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
values: string[];
|
values: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
navigated_to?: string;
|
navigated_to?: string;
|
||||||
@@ -792,38 +787,38 @@ export type DraftTraceStep =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Exported record-only step (trace v2). */
|
/** Exported record-only step (trace v2). */
|
||||||
export type Step =
|
export type StepV2 =
|
||||||
| ({ op: "navigate" } & StepCommon & { to: string })
|
| ({ op: "navigate" } & StepCommonV2 & { to: string })
|
||||||
| ({ op: "click" } & StepCommon & { target: TargetDescriptor })
|
| ({ op: "click" } & StepCommonV2 & { target: TargetDescriptorV2 })
|
||||||
| ({ op: "hover" } & StepCommon & { target: TargetDescriptor })
|
| ({ op: "hover" } & StepCommonV2 & { target: TargetDescriptorV2 })
|
||||||
| ({ op: "fill" } & StepCommon & {
|
| ({ op: "fill" } & StepCommonV2 & {
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
value: string;
|
value: string;
|
||||||
redacted?: boolean;
|
redacted?: boolean;
|
||||||
})
|
})
|
||||||
| ({ op: "select" } & StepCommon & {
|
| ({ op: "select" } & StepCommonV2 & {
|
||||||
target: TargetDescriptor;
|
target: TargetDescriptorV2;
|
||||||
selection: SelectedOption[];
|
selection: SelectedOptionV2[];
|
||||||
})
|
})
|
||||||
| ({ op: "press" } & StepCommon & {
|
| ({ op: "press" } & StepCommonV2 & {
|
||||||
key: string;
|
key: string;
|
||||||
modifiers?: KeyModifier[];
|
modifiers?: KeyModifier[];
|
||||||
target?: TargetDescriptor;
|
target?: TargetDescriptorV2;
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TargetDescriptorV2 = TargetDescriptor;
|
export interface TraceV2 {
|
||||||
export type StepV2 = Step;
|
|
||||||
|
|
||||||
export interface Trace {
|
|
||||||
recorded_at: string;
|
recorded_at: string;
|
||||||
started_at?: string;
|
started_at?: string;
|
||||||
purpose?: string;
|
purpose?: string;
|
||||||
entry: TraceEntry;
|
entry: TraceEntry;
|
||||||
pages: PageRef[];
|
pages: PageRefV2[];
|
||||||
steps: Step[];
|
steps: StepV2[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TraceV2 = Trace;
|
export interface SelectedOptionV3 {
|
||||||
|
value: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type StepV3 =
|
export type StepV3 =
|
||||||
| ({ op: "navigate" } & StepCommonV3 & { to: string; cause: NavigationCause })
|
| ({ op: "navigate" } & StepCommonV3 & { to: string; cause: NavigationCause })
|
||||||
@@ -837,7 +832,7 @@ export type StepV3 =
|
|||||||
})
|
})
|
||||||
| ({ op: "select" } & StepCommonV3 & {
|
| ({ op: "select" } & StepCommonV3 & {
|
||||||
target: TargetDescriptorV3;
|
target: TargetDescriptorV3;
|
||||||
selection?: SelectedOption[];
|
selection?: SelectedOptionV3[];
|
||||||
})
|
})
|
||||||
| ({ op: "press" } & StepCommonV3 & {
|
| ({ op: "press" } & StepCommonV3 & {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -847,14 +842,14 @@ export type StepV3 =
|
|||||||
| ({ op: "scroll" } & StepCommonV3);
|
| ({ op: "scroll" } & StepCommonV3);
|
||||||
|
|
||||||
export interface TraceV3 {
|
export interface TraceV3 {
|
||||||
version: 3;
|
version: typeof TRACE_VERSION_V3;
|
||||||
recorded_at: string;
|
recorded_at: string;
|
||||||
started_at?: string;
|
started_at?: string;
|
||||||
purpose?: string;
|
purpose?: string;
|
||||||
stopped_by: StopReason;
|
stopped_by: StopReason;
|
||||||
entry: TraceEntry;
|
entry: TraceEntry;
|
||||||
recorder: RecorderInfo;
|
recorder: RecorderInfo;
|
||||||
states: TraceState[];
|
states: TraceStateV3[];
|
||||||
steps: StepV3[];
|
steps: StepV3[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,10 +860,6 @@ export interface RecordStartParams {
|
|||||||
tab_id?: number;
|
tab_id?: number;
|
||||||
url?: string;
|
url?: string;
|
||||||
purpose?: string;
|
purpose?: string;
|
||||||
max_page_tokens?: number;
|
|
||||||
redact_values?: boolean;
|
|
||||||
/** Omitted means v2; `3` requests a state-linked v3 trace. */
|
|
||||||
trace_version?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecordStartResult {
|
export interface RecordStartResult {
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ mod m2_tests {
|
|||||||
fn fake_status(browsers: Vec<BrowserStatusEntry>, skew: Vec<VersionSkewEntry>) -> StatusResult {
|
fn fake_status(browsers: Vec<BrowserStatusEntry>, skew: Vec<VersionSkewEntry>) -> StatusResult {
|
||||||
StatusResult {
|
StatusResult {
|
||||||
daemon_version: env!("CARGO_PKG_VERSION").into(),
|
daemon_version: env!("CARGO_PKG_VERSION").into(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
pid: 1,
|
pid: 1,
|
||||||
uptime_secs: 0,
|
uptime_secs: 0,
|
||||||
ws_port: 0,
|
ws_port: 0,
|
||||||
@@ -571,7 +571,7 @@ mod m2_tests {
|
|||||||
session_count: 0,
|
session_count: 0,
|
||||||
connected_at_ms: 1,
|
connected_at_ms: 1,
|
||||||
version_skew: false,
|
version_skew: false,
|
||||||
extension_protocol_version: "1.1".into(),
|
extension_protocol_version: "1.0".into(),
|
||||||
}],
|
}],
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
);
|
);
|
||||||
@@ -592,7 +592,7 @@ mod m2_tests {
|
|||||||
session_count: 0,
|
session_count: 0,
|
||||||
connected_at_ms: 1,
|
connected_at_ms: 1,
|
||||||
version_skew: true,
|
version_skew: true,
|
||||||
extension_protocol_version: "1.2".into(),
|
extension_protocol_version: "1.1".into(),
|
||||||
}],
|
}],
|
||||||
vec![VersionSkewEntry {
|
vec![VersionSkewEntry {
|
||||||
instance_id: "alpha".into(),
|
instance_id: "alpha".into(),
|
||||||
@@ -600,8 +600,8 @@ mod m2_tests {
|
|||||||
label: "Personal".into(),
|
label: "Personal".into(),
|
||||||
server_version: env!("CARGO_PKG_VERSION").into(),
|
server_version: env!("CARGO_PKG_VERSION").into(),
|
||||||
client_version: "0.0.9".into(),
|
client_version: "0.0.9".into(),
|
||||||
server_protocol_version: "1.1".into(),
|
server_protocol_version: "1.0".into(),
|
||||||
client_protocol_version: "1.2".into(),
|
client_protocol_version: "1.1".into(),
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
let check = check_browsers_protocol_compatible(Some(&status));
|
let check = check_browsers_protocol_compatible(Some(&status));
|
||||||
|
|||||||
@@ -98,9 +98,6 @@ fn dispatch_start(args: RecordStartArgs, format: Format) -> Result<(), CliError>
|
|||||||
tab_id: args.tab_id,
|
tab_id: args.tab_id,
|
||||||
url: args.url,
|
url: args.url,
|
||||||
purpose: args.purpose.clone(),
|
purpose: args.purpose.clone(),
|
||||||
max_page_tokens: None,
|
|
||||||
redact_values: None,
|
|
||||||
trace_version: None,
|
|
||||||
};
|
};
|
||||||
let start_result = business_rpc::call::<RecordStartParams, RecordStartResult>(
|
let start_result = business_rpc::call::<RecordStartParams, RecordStartResult>(
|
||||||
info.sock_path.clone(),
|
info.sock_path.clone(),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use crate::daemon::{
|
|||||||
browsers::{BROWSER_LIVENESS_TICK, BROWSER_LIVENESS_TIMEOUT, EXTENSION_CONNECT_WAIT},
|
browsers::{BROWSER_LIVENESS_TICK, BROWSER_LIVENESS_TIMEOUT, EXTENSION_CONNECT_WAIT},
|
||||||
info as daemon_info, ipc, lockfile, paths,
|
info as daemon_info, ipc, lockfile, paths,
|
||||||
sessions::{StopSessionError, forget_session, stop_session},
|
sessions::{StopSessionError, forget_session, stop_session},
|
||||||
state::{DaemonState, PROTOCOL_VERSION},
|
state::DaemonState,
|
||||||
ws,
|
ws,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,7 +297,7 @@ pub fn run_foreground(cfg: DaemonConfig) -> Result<()> {
|
|||||||
ws_port,
|
ws_port,
|
||||||
sock_path: sock_path.clone(),
|
sock_path: sock_path.clone(),
|
||||||
daemon_version: env!("CARGO_PKG_VERSION"),
|
daemon_version: env!("CARGO_PKG_VERSION"),
|
||||||
protocol_version: PROTOCOL_VERSION,
|
protocol_version: "1.0",
|
||||||
};
|
};
|
||||||
let handler = ipc::full_handler(status, Arc::clone(&state));
|
let handler = ipc::full_handler(status, Arc::clone(&state));
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use super::start::DaemonConfig;
|
|||||||
use super::ws::WsHandle;
|
use super::ws::WsHandle;
|
||||||
|
|
||||||
pub const DAEMON_VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const DAEMON_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub const PROTOCOL_VERSION: &str = "1.1";
|
pub const PROTOCOL_VERSION: &str = "1.0";
|
||||||
/// Lowest **protocol** version peers must speak (e.g. `"1.0"`).
|
/// Lowest **protocol** version peers must speak (e.g. `"1.0"`).
|
||||||
pub const MIN_COMPATIBLE_PROTOCOL: &str = "1.0";
|
pub const MIN_COMPATIBLE_PROTOCOL: &str = "1.0";
|
||||||
/// Legacy app-semver floor used only when `HandshakeResult.min_compatible_peer`
|
/// Legacy app-semver floor used only when `HandshakeResult.min_compatible_peer`
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ async fn handshake_as_ext(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ async fn handshake_as_ext(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ async fn send_handshake_with_floors(
|
|||||||
async fn handshake_ok_when_protocol_matches() {
|
async fn handshake_ok_when_protocol_matches() {
|
||||||
let (handle, _sock) = spawn_daemon().await;
|
let (handle, _sock) = spawn_daemon().await;
|
||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
let mut ws = open_ws(handle.ws_addr()).await;
|
||||||
let resp = send_handshake(&mut ws, "1.1", env!("CARGO_PKG_VERSION")).await;
|
let resp = send_handshake(&mut ws, "1.0", env!("CARGO_PKG_VERSION")).await;
|
||||||
let result: HandshakeResult = match resp.body {
|
let result: HandshakeResult = match resp.body {
|
||||||
ResponseBody::Ok(v) => serde_json::from_value(v).unwrap(),
|
ResponseBody::Ok(v) => serde_json::from_value(v).unwrap(),
|
||||||
ResponseBody::Err(e) => panic!("expected ok handshake, got {e:?}"),
|
ResponseBody::Err(e) => panic!("expected ok handshake, got {e:?}"),
|
||||||
};
|
};
|
||||||
assert_eq!(result.protocol_version, "1.1");
|
assert_eq!(result.protocol_version, "1.0");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result
|
result
|
||||||
.min_compatible_peer
|
.min_compatible_peer
|
||||||
@@ -135,7 +135,7 @@ async fn handshake_ok_when_app_versions_differ_but_protocol_matches() {
|
|||||||
let (handle, _sock) = spawn_daemon().await;
|
let (handle, _sock) = spawn_daemon().await;
|
||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
let mut ws = open_ws(handle.ws_addr()).await;
|
||||||
let resp =
|
let resp =
|
||||||
send_handshake_with_floors(&mut ws, "1.1", "9.9.9", Some("0.0.0"), Some("1.1")).await;
|
send_handshake_with_floors(&mut ws, "1.0", "9.9.9", Some("0.0.0"), Some("1.0")).await;
|
||||||
match resp.body {
|
match resp.body {
|
||||||
ResponseBody::Ok(_) => {}
|
ResponseBody::Ok(_) => {}
|
||||||
other => panic!("expected ok when protocol matches, got {other:?}"),
|
other => panic!("expected ok when protocol matches, got {other:?}"),
|
||||||
@@ -149,32 +149,7 @@ async fn handshake_skew_when_protocol_minor_differs() {
|
|||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
let mut ws = open_ws(handle.ws_addr()).await;
|
||||||
let resp = send_handshake_with_floors(
|
let resp = send_handshake_with_floors(
|
||||||
&mut ws,
|
&mut ws,
|
||||||
"1.2",
|
"1.1",
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
Some("0.0.0"),
|
|
||||||
Some("1.1"),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
match resp.body {
|
|
||||||
ResponseBody::Ok(_) => {}
|
|
||||||
other => panic!("minor protocol drift should warn-but-allow, got {other:?}"),
|
|
||||||
}
|
|
||||||
let state = handle.state();
|
|
||||||
let client = state
|
|
||||||
.browsers
|
|
||||||
.get(&bsk::daemon::browsers::BrowserId(TEST_EXT_ID.into()))
|
|
||||||
.expect("browser registered");
|
|
||||||
assert!(client.version_skew);
|
|
||||||
handle.shutdown().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handshake_skew_when_protocol_1_0_peer() {
|
|
||||||
let (handle, _sock) = spawn_daemon().await;
|
|
||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
|
||||||
let resp = send_handshake_with_floors(
|
|
||||||
&mut ws,
|
|
||||||
"1.0",
|
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
Some("0.0.0"),
|
Some("0.0.0"),
|
||||||
Some("1.0"),
|
Some("1.0"),
|
||||||
@@ -182,7 +157,7 @@ async fn handshake_skew_when_protocol_1_0_peer() {
|
|||||||
.await;
|
.await;
|
||||||
match resp.body {
|
match resp.body {
|
||||||
ResponseBody::Ok(_) => {}
|
ResponseBody::Ok(_) => {}
|
||||||
other => panic!("protocol 1.0 peer should connect with skew, got {other:?}"),
|
other => panic!("minor protocol drift should warn-but-allow, got {other:?}"),
|
||||||
}
|
}
|
||||||
let state = handle.state();
|
let state = handle.state();
|
||||||
let client = state
|
let client = state
|
||||||
@@ -235,7 +210,7 @@ async fn handshake_legacy_ext_without_protocol_floor_still_ok() {
|
|||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
let mut ws = open_ws(handle.ws_addr()).await;
|
||||||
let resp = send_handshake_with_floors(
|
let resp = send_handshake_with_floors(
|
||||||
&mut ws,
|
&mut ws,
|
||||||
"1.1",
|
"1.0",
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
Some("0.1.0"),
|
Some("0.1.0"),
|
||||||
None,
|
None,
|
||||||
@@ -260,7 +235,7 @@ async fn status_surfaces_version_skew_for_skewed_browser() {
|
|||||||
browser_name: "chrome".into(),
|
browser_name: "chrome".into(),
|
||||||
browser_version: "131.0".into(),
|
browser_version: "131.0".into(),
|
||||||
extension_version: "9.9.9".into(),
|
extension_version: "9.9.9".into(),
|
||||||
extension_protocol_version: "1.2".into(),
|
extension_protocol_version: "1.1".into(),
|
||||||
label: "Older".into(),
|
label: "Older".into(),
|
||||||
sink: bsk::daemon::browsers::BrowserSink { tx },
|
sink: bsk::daemon::browsers::BrowserSink { tx },
|
||||||
pending: Mutex::new(bsk::daemon::browsers::Pending::default()),
|
pending: Mutex::new(bsk::daemon::browsers::Pending::default()),
|
||||||
@@ -288,8 +263,8 @@ async fn status_surfaces_version_skew_for_skewed_browser() {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|s| s.instance_id == "skew-only-test")
|
.find(|s| s.instance_id == "skew-only-test")
|
||||||
.expect("status must list our skew client");
|
.expect("status must list our skew client");
|
||||||
assert_eq!(skew.client_protocol_version, "1.2");
|
assert_eq!(skew.client_protocol_version, "1.1");
|
||||||
assert_eq!(skew.server_protocol_version, "1.1");
|
assert_eq!(skew.server_protocol_version, "1.0");
|
||||||
assert_eq!(skew.client_version, "9.9.9");
|
assert_eq!(skew.client_version, "9.9.9");
|
||||||
let entry = status
|
let entry = status
|
||||||
.browsers
|
.browsers
|
||||||
@@ -306,7 +281,7 @@ async fn handshake_rejects_when_local_below_peer_min_compatible_protocol() {
|
|||||||
let mut ws = open_ws(handle.ws_addr()).await;
|
let mut ws = open_ws(handle.ws_addr()).await;
|
||||||
let resp = send_handshake_with_floors(
|
let resp = send_handshake_with_floors(
|
||||||
&mut ws,
|
&mut ws,
|
||||||
"1.1",
|
"1.0",
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
Some("0.0.0"),
|
Some("0.0.0"),
|
||||||
Some("99.0.0"),
|
Some("99.0.0"),
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ async fn handshake_as_ext(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ async fn handshake_as_ext(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async fn handshake_as_ext(ws: &mut TestWs) -> HandshakeResult {
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
@@ -758,7 +758,7 @@ async fn connect_second_ext(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: instance_id.into(),
|
instance_id: instance_id.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "edge".into(),
|
name: "edge".into(),
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ fn bsk_status_json_returns_structured_payload() {
|
|||||||
|
|
||||||
assert!(parsed["pid"].as_u64().unwrap() > 0);
|
assert!(parsed["pid"].as_u64().unwrap() > 0);
|
||||||
assert!(!parsed["daemon_version"].as_str().unwrap().is_empty());
|
assert!(!parsed["daemon_version"].as_str().unwrap().is_empty());
|
||||||
assert_eq!(parsed["protocol_version"], "1.1");
|
assert_eq!(parsed["protocol_version"], "1.0");
|
||||||
assert!(parsed["sock_path"].as_str().is_some());
|
assert!(parsed["sock_path"].as_str().is_some());
|
||||||
assert_eq!(parsed["browsers"], serde_json::json!([]));
|
assert_eq!(parsed["browsers"], serde_json::json!([]));
|
||||||
assert_eq!(parsed["sessions"], serde_json::json!([]));
|
assert_eq!(parsed["sessions"], serde_json::json!([]));
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ async fn do_handshake(ws: &mut Ws) -> HandshakeResult {
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ async fn do_handshake(ws: &mut Ws) -> HandshakeResult {
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ async fn do_handshake(ws: &mut Ws) -> HandshakeResult {
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async fn do_handshake(ws: &mut Ws) -> HandshakeResult {
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: TEST_EXT_ID.into(),
|
instance_id: TEST_EXT_ID.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ pub async fn send_handshake(
|
|||||||
let params = HandshakeParams {
|
let params = HandshakeParams {
|
||||||
client: "browser-skill-extension".into(),
|
client: "browser-skill-extension".into(),
|
||||||
version: "0.1.0-dev.0".parse().unwrap(),
|
version: "0.1.0-dev.0".parse().unwrap(),
|
||||||
protocol_version: "1.1".into(),
|
protocol_version: "1.0".into(),
|
||||||
instance_id: instance_id.into(),
|
instance_id: instance_id.into(),
|
||||||
browser: BrowserPeerInfo {
|
browser: BrowserPeerInfo {
|
||||||
name: "chrome".into(),
|
name: "chrome".into(),
|
||||||
@@ -91,7 +91,7 @@ async fn ws_handshake_registers_browser_in_state() {
|
|||||||
let mut ws = connect_ext(handle.ws_addr(), &origin).await;
|
let mut ws = connect_ext(handle.ws_addr(), &origin).await;
|
||||||
let result = send_handshake(&mut ws, TEST_EXT_ID).await;
|
let result = send_handshake(&mut ws, TEST_EXT_ID).await;
|
||||||
assert_eq!(result.server, "browser-skill-daemon");
|
assert_eq!(result.server, "browser-skill-daemon");
|
||||||
assert_eq!(result.protocol_version, "1.1");
|
assert_eq!(result.protocol_version, "1.0");
|
||||||
|
|
||||||
let state = handle.state();
|
let state = handle.state();
|
||||||
let browsers = state.browsers.snapshot();
|
let browsers = state.browsers.snapshot();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"browser"
|
"browser"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"PageRef": {
|
"PageRefV2": {
|
||||||
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"$ref": "#/definitions/TraceV2"
|
"$ref": "#/definitions/TraceV2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/Trace"
|
"$ref": "#/definitions/TraceV3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -90,24 +90,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SelectedOption": {
|
|
||||||
"description": "One selected option (`select` op).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"label": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SelectedOptionV2": {
|
"SelectedOptionV2": {
|
||||||
"description": "One selected option (`select` op).",
|
"description": "One selected option (`select` op).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -126,284 +108,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Step": {
|
"SelectedOptionV3": {
|
||||||
"description": "One recorded user action — discriminated union by `op`.",
|
"description": "One selected option (`select` op).",
|
||||||
"oneOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
"value"
|
||||||
"type": "object",
|
],
|
||||||
"required": [
|
"properties": {
|
||||||
"cause",
|
"label": {
|
||||||
"id",
|
"type": [
|
||||||
"op",
|
"string",
|
||||||
"result",
|
"null"
|
||||||
"state",
|
]
|
||||||
"to"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"cause": {
|
|
||||||
"$ref": "#/definitions/NavigationCause"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"navigate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"value": {
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
"type": "string"
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"click"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"hover"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"commit",
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target",
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"commit": {
|
|
||||||
"$ref": "#/definitions/FillCommit"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"fill"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"redacted": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"select"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/SelectedOption"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"key",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modifiers": {
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/KeyModifier"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"press"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"scroll"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"StepEffectV2": {
|
"StepEffectV2": {
|
||||||
"description": "Observed navigation after a step (objective fact only).",
|
"description": "Observed navigation after a step (objective fact only).",
|
||||||
@@ -418,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"StepResult": {
|
"StepResultV3": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"state"
|
"state"
|
||||||
@@ -710,6 +431,285 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"StepV3": {
|
||||||
|
"description": "One recorded user action — discriminated union by `op`.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cause",
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"to"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"cause": {
|
||||||
|
"$ref": "#/definitions/NavigationCause"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"navigate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"click"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"hover"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"commit",
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"commit": {
|
||||||
|
"$ref": "#/definitions/FillCommit"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fill"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"redacted": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"select"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"selection": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SelectedOptionV3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"key",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modifiers": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/KeyModifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"press"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"scroll"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"StopReason": {
|
"StopReason": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -717,39 +717,6 @@
|
|||||||
"cli_stop"
|
"cli_stop"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"TargetDescriptor": {
|
|
||||||
"description": "Stable semantic handle for an interacted element within a page observation.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ctx": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ref": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"role": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unmatched": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TargetDescriptorV2": {
|
"TargetDescriptorV2": {
|
||||||
"description": "Stable semantic handle for an interacted element (v2).\n\n`name` and `nearby_label` are **untrusted page text**.",
|
"description": "Stable semantic handle for an interacted element (v2).\n\n`name` and `nearby_label` are **untrusted page text**.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -792,8 +759,126 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Trace": {
|
"TargetDescriptorV3": {
|
||||||
"description": "Persisted user-action trace exported by `tool.record_stop` / `await`.",
|
"description": "Stable semantic handle for an interacted element within a page observation.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ctx": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unmatched": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"start_url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"start_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceStateV3": {
|
||||||
|
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"body",
|
||||||
|
"id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"body": {
|
||||||
|
"description": "Full page observation (front matter + VOM body + annotations).",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"truncated": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceV2": {
|
||||||
|
"description": "Persisted user-action trace exported by legacy `tool.record_stop` / `await`.",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"entry",
|
||||||
|
"pages",
|
||||||
|
"recorded_at",
|
||||||
|
"steps"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"entry": {
|
||||||
|
"$ref": "#/definitions/TraceEntry"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/PageRefV2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"purpose": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"recorded_at": {
|
||||||
|
"description": "RFC 3339 timestamp when recording stopped.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/StepV2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"TraceV3": {
|
||||||
|
"description": "Wire trace returned by `tool.record_stop` / `await`.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"entry",
|
"entry",
|
||||||
@@ -829,13 +914,13 @@
|
|||||||
"states": {
|
"states": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/TraceState"
|
"$ref": "#/definitions/TraceStateV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Step"
|
"$ref": "#/definitions/StepV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stopped_by": {
|
"stopped_by": {
|
||||||
@@ -847,101 +932,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
|
||||||
"TraceEntry": {
|
|
||||||
"description": "Recording entry point — first URL the flow starts from.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"start_url"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"start_url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TraceState": {
|
|
||||||
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"url"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"body": {
|
|
||||||
"description": "Wire-only: full page observation (front matter + VOM body + annotations).",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"page": {
|
|
||||||
"description": "Disk-only: filename under the bundle `pages/` directory.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"truncated": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TraceV2": {
|
|
||||||
"description": "Persisted user-action trace exported by legacy `tool.record_stop` / `await`.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"entry",
|
|
||||||
"pages",
|
|
||||||
"recorded_at",
|
|
||||||
"steps"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"entry": {
|
|
||||||
"$ref": "#/definitions/TraceEntry"
|
|
||||||
},
|
|
||||||
"pages": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/PageRef"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"purpose": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"recorded_at": {
|
|
||||||
"description": "RFC 3339 timestamp when recording stopped.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"started_at": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"steps": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/StepV2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,12 @@
|
|||||||
"session_id"
|
"session_id"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_page_tokens": {
|
|
||||||
"type": [
|
|
||||||
"integer",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"purpose": {
|
"purpose": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"redact_values": {
|
|
||||||
"type": [
|
|
||||||
"boolean",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"session_id": {
|
"session_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -36,15 +22,6 @@
|
|||||||
],
|
],
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"trace_version": {
|
|
||||||
"description": "Desired trace export format. Omitted ⇒ v2; `3` ⇒ state-linked v3 bundle.",
|
|
||||||
"type": [
|
|
||||||
"integer",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"url": {
|
"url": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"browser"
|
"browser"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"PageRef": {
|
"PageRefV2": {
|
||||||
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"$ref": "#/definitions/TraceV2"
|
"$ref": "#/definitions/TraceV2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/Trace"
|
"$ref": "#/definitions/TraceV3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -90,24 +90,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SelectedOption": {
|
|
||||||
"description": "One selected option (`select` op).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"label": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SelectedOptionV2": {
|
"SelectedOptionV2": {
|
||||||
"description": "One selected option (`select` op).",
|
"description": "One selected option (`select` op).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -126,284 +108,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Step": {
|
"SelectedOptionV3": {
|
||||||
"description": "One recorded user action — discriminated union by `op`.",
|
"description": "One selected option (`select` op).",
|
||||||
"oneOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
"value"
|
||||||
"type": "object",
|
],
|
||||||
"required": [
|
"properties": {
|
||||||
"cause",
|
"label": {
|
||||||
"id",
|
"type": [
|
||||||
"op",
|
"string",
|
||||||
"result",
|
"null"
|
||||||
"state",
|
]
|
||||||
"to"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"cause": {
|
|
||||||
"$ref": "#/definitions/NavigationCause"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"navigate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"value": {
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
"type": "string"
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"click"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"hover"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"commit",
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target",
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"commit": {
|
|
||||||
"$ref": "#/definitions/FillCommit"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"fill"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"redacted": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"select"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/SelectedOption"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"key",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modifiers": {
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/KeyModifier"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"press"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fields shared by every step variant (flattened in JSON).",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"op",
|
|
||||||
"result",
|
|
||||||
"state"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"op": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"scroll"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/definitions/StepResult"
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"description": "Observation id immediately before this action.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"StepEffectV2": {
|
"StepEffectV2": {
|
||||||
"description": "Observed navigation after a step (objective fact only).",
|
"description": "Observed navigation after a step (objective fact only).",
|
||||||
@@ -418,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"StepResult": {
|
"StepResultV3": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"state"
|
"state"
|
||||||
@@ -710,6 +431,285 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"StepV3": {
|
||||||
|
"description": "One recorded user action — discriminated union by `op`.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cause",
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"to"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"cause": {
|
||||||
|
"$ref": "#/definitions/NavigationCause"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"navigate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"click"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"hover"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"commit",
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"commit": {
|
||||||
|
"$ref": "#/definitions/FillCommit"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fill"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"redacted": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"select"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"selection": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SelectedOptionV3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"key",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modifiers": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/KeyModifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"press"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fields shared by every step variant (flattened in JSON).",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"op",
|
||||||
|
"result",
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"op": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"scroll"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/StepResultV3"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "Observation id immediately before this action.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"StopReason": {
|
"StopReason": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -717,39 +717,6 @@
|
|||||||
"cli_stop"
|
"cli_stop"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"TargetDescriptor": {
|
|
||||||
"description": "Stable semantic handle for an interacted element within a page observation.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ctx": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ref": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"role": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unmatched": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TargetDescriptorV2": {
|
"TargetDescriptorV2": {
|
||||||
"description": "Stable semantic handle for an interacted element (v2).\n\n`name` and `nearby_label` are **untrusted page text**.",
|
"description": "Stable semantic handle for an interacted element (v2).\n\n`name` and `nearby_label` are **untrusted page text**.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -792,8 +759,126 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Trace": {
|
"TargetDescriptorV3": {
|
||||||
"description": "Persisted user-action trace exported by `tool.record_stop` / `await`.",
|
"description": "Stable semantic handle for an interacted element within a page observation.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ctx": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unmatched": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"start_url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"start_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceStateV3": {
|
||||||
|
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"body",
|
||||||
|
"id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"body": {
|
||||||
|
"description": "Full page observation (front matter + VOM body + annotations).",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"truncated": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TraceV2": {
|
||||||
|
"description": "Persisted user-action trace exported by legacy `tool.record_stop` / `await`.",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"entry",
|
||||||
|
"pages",
|
||||||
|
"recorded_at",
|
||||||
|
"steps"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"entry": {
|
||||||
|
"$ref": "#/definitions/TraceEntry"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/PageRefV2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"purpose": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"recorded_at": {
|
||||||
|
"description": "RFC 3339 timestamp when recording stopped.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/StepV2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"TraceV3": {
|
||||||
|
"description": "Wire trace returned by `tool.record_stop` / `await`.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"entry",
|
"entry",
|
||||||
@@ -829,13 +914,13 @@
|
|||||||
"states": {
|
"states": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/TraceState"
|
"$ref": "#/definitions/TraceStateV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Step"
|
"$ref": "#/definitions/StepV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stopped_by": {
|
"stopped_by": {
|
||||||
@@ -847,101 +932,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
|
||||||
"TraceEntry": {
|
|
||||||
"description": "Recording entry point — first URL the flow starts from.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"start_url"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"start_url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TraceState": {
|
|
||||||
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"url"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"body": {
|
|
||||||
"description": "Wire-only: full page observation (front matter + VOM body + annotations).",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"page": {
|
|
||||||
"description": "Disk-only: filename under the bundle `pages/` directory.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"truncated": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TraceV2": {
|
|
||||||
"description": "Persisted user-action trace exported by legacy `tool.record_stop` / `await`.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"entry",
|
|
||||||
"pages",
|
|
||||||
"recorded_at",
|
|
||||||
"steps"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"entry": {
|
|
||||||
"$ref": "#/definitions/TraceEntry"
|
|
||||||
},
|
|
||||||
"pages": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/PageRef"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"purpose": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"recorded_at": {
|
|
||||||
"description": "RFC 3339 timestamp when recording stopped.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"started_at": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"steps": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/StepV2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Trace",
|
"title": "TraceV3",
|
||||||
"description": "Persisted user-action trace exported by `tool.record_stop` / `await`.",
|
"description": "Wire trace returned by `tool.record_stop` / `await`.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"entry",
|
"entry",
|
||||||
@@ -37,13 +37,13 @@
|
|||||||
"states": {
|
"states": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/TraceState"
|
"$ref": "#/definitions/TraceStateV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Step"
|
"$ref": "#/definitions/StepV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stopped_by": {
|
"stopped_by": {
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SelectedOption": {
|
"SelectedOptionV3": {
|
||||||
"description": "One selected option (`select` op).",
|
"description": "One selected option (`select` op).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -121,7 +121,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Step": {
|
"StepResultV3": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StepV3": {
|
||||||
"description": "One recorded user action — discriminated union by `op`.",
|
"description": "One recorded user action — discriminated union by `op`.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
@@ -151,7 +162,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -185,14 +196,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -219,14 +230,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -261,14 +272,14 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -298,12 +309,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"selection": {
|
"selection": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/SelectedOption"
|
"$ref": "#/definitions/SelectedOptionV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
@@ -311,7 +322,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -350,7 +361,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -359,7 +370,7 @@
|
|||||||
"target": {
|
"target": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "null"
|
"type": "null"
|
||||||
@@ -390,7 +401,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -400,17 +411,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"StepResult": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"state"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"state": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"StopReason": {
|
"StopReason": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
"cli_stop"
|
"cli_stop"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"TargetDescriptor": {
|
"TargetDescriptorV3": {
|
||||||
"description": "Stable semantic handle for an interacted element within a page observation.",
|
"description": "Stable semantic handle for an interacted element within a page observation.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -452,7 +452,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TraceEntry": {
|
"TraceEntry": {
|
||||||
"description": "Recording entry point — first URL the flow starts from.",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"start_url"
|
"start_url"
|
||||||
@@ -463,31 +462,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TraceState": {
|
"TraceStateV3": {
|
||||||
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
"description": "Page observation dictionary entry — referenced by steps via `state` / `result.state`.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
"body",
|
||||||
"id",
|
"id",
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"body": {
|
"body": {
|
||||||
"description": "Wire-only: full page observation (front matter + VOM body + annotations).",
|
"description": "Full page observation (front matter + VOM body + annotations).",
|
||||||
"type": [
|
"type": "string"
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"page": {
|
|
||||||
"description": "Disk-only: filename under the bundle `pages/` directory.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title": {
|
"title": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Step",
|
"title": "StepV3",
|
||||||
"description": "One recorded user action — discriminated union by `op`.",
|
"description": "One recorded user action — discriminated union by `op`.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -64,14 +64,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -98,14 +98,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -140,14 +140,14 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -177,12 +177,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"selection": {
|
"selection": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/SelectedOption"
|
"$ref": "#/definitions/SelectedOptionV3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
"target": {
|
"target": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TargetDescriptor"
|
"$ref": "#/definitions/TargetDescriptorV3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "null"
|
"type": "null"
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/definitions/StepResult"
|
"$ref": "#/definitions/StepResultV3"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"description": "Observation id immediately before this action.",
|
"description": "Observation id immediately before this action.",
|
||||||
@@ -309,7 +309,7 @@
|
|||||||
"browser"
|
"browser"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SelectedOption": {
|
"SelectedOptionV3": {
|
||||||
"description": "One selected option (`select` op).",
|
"description": "One selected option (`select` op).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"StepResult": {
|
"StepResultV3": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"state"
|
"state"
|
||||||
@@ -338,7 +338,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TargetDescriptor": {
|
"TargetDescriptorV3": {
|
||||||
"description": "Stable semantic handle for an interacted element within a page observation.",
|
"description": "Stable semantic handle for an interacted element within a page observation.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"pages": {
|
"pages": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PageRef"
|
"$ref": "#/definitions/PageRefV2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"purpose": {
|
"purpose": {
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"shift"
|
"shift"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"PageRef": {
|
"PageRefV2": {
|
||||||
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
"description": "Page context dictionary entry — referenced by steps via `page` id.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -431,7 +431,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TraceEntry": {
|
"TraceEntry": {
|
||||||
"description": "Recording entry point — first URL the flow starts from.",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"start_url"
|
"start_url"
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ fn main() {
|
|||||||
dump!(RequestHelpResult, "tool_request_help_result");
|
dump!(RequestHelpResult, "tool_request_help_result");
|
||||||
|
|
||||||
dump!(TraceV2, "trace_v2");
|
dump!(TraceV2, "trace_v2");
|
||||||
dump!(Trace, "trace");
|
dump!(TraceV3, "trace");
|
||||||
dump!(Step, "trace_step");
|
dump!(StepV3, "trace_step");
|
||||||
dump!(RecordStartParams, "tool_record_start_params");
|
dump!(RecordStartParams, "tool_record_start_params");
|
||||||
dump!(RecordStartResult, "tool_record_start_result");
|
dump!(RecordStartResult, "tool_record_start_result");
|
||||||
dump!(RecordStopParams, "tool_record_stop_params");
|
dump!(RecordStopParams, "tool_record_stop_params");
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ pub mod navigation;
|
|||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod observation;
|
pub mod observation;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
pub mod record_v2;
|
mod record_common;
|
||||||
|
mod record_v2;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod tabs;
|
pub mod tabs;
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ use serde::{Deserialize, Deserializer, Serialize};
|
|||||||
|
|
||||||
use super::interaction::KeyModifier;
|
use super::interaction::KeyModifier;
|
||||||
|
|
||||||
pub use crate::record_v2::{
|
pub use super::record_common::TraceEntry;
|
||||||
PageRef, SelectedOptionV2, StepCommonV2, StepEffectV2, StepV2, TargetDescriptorV2, TraceV2,
|
pub use super::record_v2::{
|
||||||
|
PageRefV2, SelectedOptionV2, StepCommonV2, StepEffectV2, StepV2, TargetDescriptorV2, TraceV2,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TRACE_VERSION: u32 = 3;
|
pub const TRACE_VERSION_V3: u32 = 3;
|
||||||
pub const TRACE_VERSION_V2: u32 = 2;
|
pub const TRACE_VERSION_V2: u32 = 2;
|
||||||
pub const DEFAULT_TRACE_VERSION: u32 = 2;
|
pub const DEFAULT_TRACE_VERSION: u32 = 2;
|
||||||
pub const VOM_FORMAT_VERSION: u32 = 1;
|
pub const VOM_FORMAT_VERSION: u32 = 1;
|
||||||
@@ -22,9 +23,9 @@ where
|
|||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let version = u32::deserialize(deserializer)?;
|
let version = u32::deserialize(deserializer)?;
|
||||||
if version != TRACE_VERSION {
|
if version != TRACE_VERSION_V3 {
|
||||||
return Err(serde::de::Error::custom(format!(
|
return Err(serde::de::Error::custom(format!(
|
||||||
"unsupported trace version {version} (expected {TRACE_VERSION})"
|
"unsupported trace version {version} (expected {TRACE_VERSION_V3})"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(version)
|
Ok(version)
|
||||||
@@ -33,7 +34,7 @@ where
|
|||||||
fn trace_v3_version_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
fn trace_v3_version_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
schemars::schema::SchemaObject {
|
schemars::schema::SchemaObject {
|
||||||
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
|
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
|
||||||
const_value: Some(serde_json::json!(TRACE_VERSION)),
|
const_value: Some(serde_json::json!(TRACE_VERSION_V3)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
@@ -45,7 +46,7 @@ fn trace_v3_version_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars
|
|||||||
|
|
||||||
/// Stable semantic handle for an interacted element within a page observation.
|
/// Stable semantic handle for an interacted element within a page observation.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TargetDescriptor {
|
pub struct TargetDescriptorV3 {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none", rename = "ref")]
|
#[serde(default, skip_serializing_if = "Option::is_none", rename = "ref")]
|
||||||
pub element_ref: Option<String>,
|
pub element_ref: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -59,15 +60,9 @@ pub struct TargetDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Trace envelope
|
// Trace v3 envelope
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Recording entry point — first URL the flow starts from.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct TraceEntry {
|
|
||||||
pub start_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct RecorderInfo {
|
pub struct RecorderInfo {
|
||||||
pub bsk: String,
|
pub bsk: String,
|
||||||
@@ -83,42 +78,38 @@ pub enum StopReason {
|
|||||||
|
|
||||||
/// Page observation dictionary entry — referenced by steps via `state` / `result.state`.
|
/// Page observation dictionary entry — referenced by steps via `state` / `result.state`.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TraceState {
|
pub struct TraceStateV3 {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
/// Wire-only: full page observation (front matter + VOM body + annotations).
|
/// Full page observation (front matter + VOM body + annotations).
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub body: String,
|
||||||
pub body: Option<String>,
|
|
||||||
/// Disk-only: filename under the bundle `pages/` directory.
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub page: Option<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
pub truncated: bool,
|
pub truncated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct StepResult {
|
pub struct StepResultV3 {
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fields shared by every step variant (flattened in JSON).
|
/// Fields shared by every step variant (flattened in JSON).
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct StepCommon {
|
pub struct StepCommonV3 {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
/// Observation id immediately before this action.
|
/// Observation id immediately before this action.
|
||||||
pub state: String,
|
pub state: String,
|
||||||
pub result: StepResult,
|
pub result: StepResultV3,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Step op-specific payloads
|
// Trace v3 step payloads
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// One selected option (`select` op).
|
/// One selected option (`select` op).
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct SelectedOption {
|
pub struct SelectedOptionV3 {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
@@ -147,27 +138,27 @@ pub enum FillCommit {
|
|||||||
/// One recorded user action — discriminated union by `op`.
|
/// One recorded user action — discriminated union by `op`.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(tag = "op", rename_all = "snake_case")]
|
#[serde(tag = "op", rename_all = "snake_case")]
|
||||||
pub enum Step {
|
pub enum StepV3 {
|
||||||
Navigate {
|
Navigate {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
to: String,
|
to: String,
|
||||||
cause: NavigationCause,
|
cause: NavigationCause,
|
||||||
},
|
},
|
||||||
Click {
|
Click {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
target: TargetDescriptor,
|
target: TargetDescriptorV3,
|
||||||
},
|
},
|
||||||
Hover {
|
Hover {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
target: TargetDescriptor,
|
target: TargetDescriptorV3,
|
||||||
},
|
},
|
||||||
Fill {
|
Fill {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
target: TargetDescriptor,
|
target: TargetDescriptorV3,
|
||||||
value: String,
|
value: String,
|
||||||
commit: FillCommit,
|
commit: FillCommit,
|
||||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
@@ -175,34 +166,34 @@ pub enum Step {
|
|||||||
},
|
},
|
||||||
Select {
|
Select {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
target: TargetDescriptor,
|
target: TargetDescriptorV3,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
selection: Vec<SelectedOption>,
|
selection: Vec<SelectedOptionV3>,
|
||||||
},
|
},
|
||||||
Press {
|
Press {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
key: String,
|
key: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
modifiers: Option<Vec<KeyModifier>>,
|
modifiers: Option<Vec<KeyModifier>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
target: Option<TargetDescriptor>,
|
target: Option<TargetDescriptorV3>,
|
||||||
},
|
},
|
||||||
Scroll {
|
Scroll {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
common: StepCommon,
|
common: StepCommonV3,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Trace root
|
// Trace v3 root
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Persisted user-action trace exported by `tool.record_stop` / `await`.
|
/// Wire trace returned by `tool.record_stop` / `await`.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Trace {
|
pub struct TraceV3 {
|
||||||
#[serde(deserialize_with = "deserialize_trace_v3_version")]
|
#[serde(deserialize_with = "deserialize_trace_v3_version")]
|
||||||
#[schemars(schema_with = "trace_v3_version_schema")]
|
#[schemars(schema_with = "trace_v3_version_schema")]
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
@@ -214,8 +205,8 @@ pub struct Trace {
|
|||||||
pub stopped_by: StopReason,
|
pub stopped_by: StopReason,
|
||||||
pub entry: TraceEntry,
|
pub entry: TraceEntry,
|
||||||
pub recorder: RecorderInfo,
|
pub recorder: RecorderInfo,
|
||||||
pub states: Vec<TraceState>,
|
pub states: Vec<TraceStateV3>,
|
||||||
pub steps: Vec<Step>,
|
pub steps: Vec<StepV3>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -231,13 +222,6 @@ pub struct RecordStartParams {
|
|||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub purpose: Option<String>,
|
pub purpose: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub max_page_tokens: Option<u32>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub redact_values: Option<bool>,
|
|
||||||
/// Desired trace export format. Omitted ⇒ v2; `3` ⇒ state-linked v3 bundle.
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub trace_version: Option<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
@@ -255,13 +239,13 @@ pub struct RecordStopParams {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum RecordedTrace {
|
pub enum RecordedTrace {
|
||||||
V2(TraceV2),
|
V2(TraceV2),
|
||||||
V3(Trace),
|
V3(TraceV3),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecordedTrace {
|
impl RecordedTrace {
|
||||||
pub fn classify_value(v: &serde_json::Value) -> Result<Self, String> {
|
pub fn classify_value(v: &serde_json::Value) -> Result<Self, String> {
|
||||||
if let Some(ver) = v.get("version").and_then(|x| x.as_u64()) {
|
if let Some(ver) = v.get("version").and_then(|x| x.as_u64()) {
|
||||||
if ver != u64::from(TRACE_VERSION) {
|
if ver != u64::from(TRACE_VERSION_V3) {
|
||||||
return Err(format!("unsupported trace version {ver}"));
|
return Err(format!("unsupported trace version {ver}"));
|
||||||
}
|
}
|
||||||
if v.get("pages").is_some() {
|
if v.get("pages").is_some() {
|
||||||
@@ -281,24 +265,6 @@ impl RecordedTrace {
|
|||||||
}
|
}
|
||||||
Err("ambiguous or unparseable trace".into())
|
Err("ambiguous or unparseable trace".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_v3(&self) -> bool {
|
|
||||||
matches!(self, RecordedTrace::V3(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_v3(&self) -> Option<&Trace> {
|
|
||||||
match self {
|
|
||||||
RecordedTrace::V3(t) => Some(t),
|
|
||||||
RecordedTrace::V2(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_v2(&self) -> Option<&TraceV2> {
|
|
||||||
match self {
|
|
||||||
RecordedTrace::V2(t) => Some(t),
|
|
||||||
RecordedTrace::V3(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for RecordedTrace {
|
impl Serialize for RecordedTrace {
|
||||||
@@ -333,7 +299,7 @@ impl JsonSchema for RecordedTrace {
|
|||||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||||
one_of: Some(vec![
|
one_of: Some(vec![
|
||||||
generator.subschema_for::<TraceV2>(),
|
generator.subschema_for::<TraceV2>(),
|
||||||
generator.subschema_for::<Trace>(),
|
generator.subschema_for::<TraceV3>(),
|
||||||
]),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
@@ -369,18 +335,18 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
fn sample_common(id: u32, state: &str, result_state: &str) -> StepCommon {
|
fn sample_common(id: u32, state: &str, result_state: &str) -> StepCommonV3 {
|
||||||
StepCommon {
|
StepCommonV3 {
|
||||||
id,
|
id,
|
||||||
state: state.into(),
|
state: state.into(),
|
||||||
result: StepResult {
|
result: StepResultV3 {
|
||||||
state: result_state.into(),
|
state: result_state.into(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_target() -> TargetDescriptor {
|
fn sample_target() -> TargetDescriptorV3 {
|
||||||
TargetDescriptor {
|
TargetDescriptorV3 {
|
||||||
element_ref: Some("e21".into()),
|
element_ref: Some("e21".into()),
|
||||||
role: Some("button".into()),
|
role: Some("button".into()),
|
||||||
name: Some("发布".into()),
|
name: Some("发布".into()),
|
||||||
@@ -389,9 +355,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_trace() -> Trace {
|
fn sample_trace() -> TraceV3 {
|
||||||
Trace {
|
TraceV3 {
|
||||||
version: TRACE_VERSION,
|
version: TRACE_VERSION_V3,
|
||||||
purpose: Some("把草稿商品发布上架".into()),
|
purpose: Some("把草稿商品发布上架".into()),
|
||||||
started_at: Some("2026-08-10T02:10:41.080Z".into()),
|
started_at: Some("2026-08-10T02:10:41.080Z".into()),
|
||||||
recorded_at: "2026-08-10T02:12:55.360Z".into(),
|
recorded_at: "2026-08-10T02:12:55.360Z".into(),
|
||||||
@@ -404,32 +370,30 @@ mod tests {
|
|||||||
vom: VOM_FORMAT_VERSION,
|
vom: VOM_FORMAT_VERSION,
|
||||||
},
|
},
|
||||||
states: vec![
|
states: vec![
|
||||||
TraceState {
|
TraceStateV3 {
|
||||||
id: "s1".into(),
|
id: "s1".into(),
|
||||||
url: "https://example.com/".into(),
|
url: "https://example.com/".into(),
|
||||||
title: Some("Example Domain".into()),
|
title: Some("Example Domain".into()),
|
||||||
body: None,
|
body: "@vom 1\nRootWebArea \"Example Domain\"".into(),
|
||||||
page: Some("s1.vom.txt".into()),
|
|
||||||
truncated: false,
|
truncated: false,
|
||||||
},
|
},
|
||||||
TraceState {
|
TraceStateV3 {
|
||||||
id: "s2".into(),
|
id: "s2".into(),
|
||||||
url: "https://shop.example.com/products?status=draft".into(),
|
url: "https://shop.example.com/products?status=draft".into(),
|
||||||
title: Some("商品管理".into()),
|
title: Some("商品管理".into()),
|
||||||
body: None,
|
body: "@vom 1\nRootWebArea \"商品管理\"".into(),
|
||||||
page: Some("s2.vom.txt".into()),
|
|
||||||
truncated: false,
|
truncated: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
steps: vec![
|
steps: vec![
|
||||||
Step::Navigate {
|
StepV3::Navigate {
|
||||||
common: sample_common(1, "s1", "s2"),
|
common: sample_common(1, "s1", "s2"),
|
||||||
to: "https://shop.example.com/products?status=draft".into(),
|
to: "https://shop.example.com/products?status=draft".into(),
|
||||||
cause: NavigationCause::UserTyped,
|
cause: NavigationCause::UserTyped,
|
||||||
},
|
},
|
||||||
Step::Fill {
|
StepV3::Fill {
|
||||||
common: sample_common(2, "s2", "s2"),
|
common: sample_common(2, "s2", "s2"),
|
||||||
target: TargetDescriptor {
|
target: TargetDescriptorV3 {
|
||||||
element_ref: Some("e12".into()),
|
element_ref: Some("e12".into()),
|
||||||
role: Some("textbox".into()),
|
role: Some("textbox".into()),
|
||||||
name: Some("搜索商品".into()),
|
name: Some("搜索商品".into()),
|
||||||
@@ -440,7 +404,7 @@ mod tests {
|
|||||||
commit: FillCommit::Enter,
|
commit: FillCommit::Enter,
|
||||||
redacted: false,
|
redacted: false,
|
||||||
},
|
},
|
||||||
Step::Click {
|
StepV3::Click {
|
||||||
common: sample_common(3, "s2", "s2"),
|
common: sample_common(3, "s2", "s2"),
|
||||||
target: sample_target(),
|
target: sample_target(),
|
||||||
},
|
},
|
||||||
@@ -450,7 +414,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_click_round_trips() {
|
fn step_click_round_trips() {
|
||||||
let step = Step::Click {
|
let step = StepV3::Click {
|
||||||
common: sample_common(1, "s1", "s2"),
|
common: sample_common(1, "s1", "s2"),
|
||||||
target: sample_target(),
|
target: sample_target(),
|
||||||
};
|
};
|
||||||
@@ -459,15 +423,15 @@ mod tests {
|
|||||||
assert_eq!(v.get("state").and_then(|v| v.as_str()), Some("s1"));
|
assert_eq!(v.get("state").and_then(|v| v.as_str()), Some("s1"));
|
||||||
assert_eq!(v["result"]["state"], "s2");
|
assert_eq!(v["result"]["state"], "s2");
|
||||||
assert_eq!(v["target"]["ref"], "e21");
|
assert_eq!(v["target"]["ref"], "e21");
|
||||||
let round: Step = serde_json::from_value(v).unwrap();
|
let round: StepV3 = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(round, step);
|
assert_eq!(round, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_fill_with_commit_round_trips() {
|
fn step_fill_with_commit_round_trips() {
|
||||||
let step = Step::Fill {
|
let step = StepV3::Fill {
|
||||||
common: sample_common(2, "s2", "s3"),
|
common: sample_common(2, "s2", "s3"),
|
||||||
target: TargetDescriptor {
|
target: TargetDescriptorV3 {
|
||||||
element_ref: Some("e12".into()),
|
element_ref: Some("e12".into()),
|
||||||
role: Some("textbox".into()),
|
role: Some("textbox".into()),
|
||||||
name: Some("搜索商品".into()),
|
name: Some("搜索商品".into()),
|
||||||
@@ -482,15 +446,15 @@ mod tests {
|
|||||||
assert_eq!(v["op"], "fill");
|
assert_eq!(v["op"], "fill");
|
||||||
assert_eq!(v["commit"], "enter");
|
assert_eq!(v["commit"], "enter");
|
||||||
assert!(v.get("redacted").is_none());
|
assert!(v.get("redacted").is_none());
|
||||||
let round: Step = serde_json::from_value(v).unwrap();
|
let round: StepV3 = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(round, step);
|
assert_eq!(round, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_fill_password_is_redacted() {
|
fn step_fill_password_is_redacted() {
|
||||||
let step = Step::Fill {
|
let step = StepV3::Fill {
|
||||||
common: sample_common(1, "s1", "s1"),
|
common: sample_common(1, "s1", "s1"),
|
||||||
target: TargetDescriptor {
|
target: TargetDescriptorV3 {
|
||||||
element_ref: Some("e3".into()),
|
element_ref: Some("e3".into()),
|
||||||
role: Some("textbox".into()),
|
role: Some("textbox".into()),
|
||||||
name: Some("密码".into()),
|
name: Some("密码".into()),
|
||||||
@@ -508,7 +472,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_navigate_with_cause_round_trips() {
|
fn step_navigate_with_cause_round_trips() {
|
||||||
let step = Step::Navigate {
|
let step = StepV3::Navigate {
|
||||||
common: sample_common(1, "s1", "s2"),
|
common: sample_common(1, "s1", "s2"),
|
||||||
to: "https://example.com".into(),
|
to: "https://example.com".into(),
|
||||||
cause: NavigationCause::UserTyped,
|
cause: NavigationCause::UserTyped,
|
||||||
@@ -516,7 +480,7 @@ mod tests {
|
|||||||
let v = serde_json::to_value(&step).unwrap();
|
let v = serde_json::to_value(&step).unwrap();
|
||||||
assert_eq!(v["op"], "navigate");
|
assert_eq!(v["op"], "navigate");
|
||||||
assert_eq!(v["cause"], "user_typed");
|
assert_eq!(v["cause"], "user_typed");
|
||||||
let round: Step = serde_json::from_value(v).unwrap();
|
let round: StepV3 = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(round, step);
|
assert_eq!(round, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,15 +492,15 @@ mod tests {
|
|||||||
assert!(v.get("pages").is_none());
|
assert!(v.get("pages").is_none());
|
||||||
assert_eq!(v["recorder"]["vom"], 1);
|
assert_eq!(v["recorder"]["vom"], 1);
|
||||||
assert_eq!(v["states"].as_array().unwrap().len(), 2);
|
assert_eq!(v["states"].as_array().unwrap().len(), 2);
|
||||||
let round: Trace = serde_json::from_value(v).unwrap();
|
let round: TraceV3 = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(round, trace);
|
assert_eq!(round, trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_fields_are_omitted() {
|
fn default_fields_are_omitted() {
|
||||||
let step = Step::Click {
|
let step = StepV3::Click {
|
||||||
common: sample_common(1, "s1", "s1"),
|
common: sample_common(1, "s1", "s1"),
|
||||||
target: TargetDescriptor {
|
target: TargetDescriptorV3 {
|
||||||
element_ref: Some("e1".into()),
|
element_ref: Some("e1".into()),
|
||||||
role: Some("button".into()),
|
role: Some("button".into()),
|
||||||
name: Some("OK".into()),
|
name: Some("OK".into()),
|
||||||
@@ -552,9 +516,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unmatched_target_serializes_flag() {
|
fn unmatched_target_serializes_flag() {
|
||||||
let step = Step::Click {
|
let step = StepV3::Click {
|
||||||
common: sample_common(1, "s1", "s2"),
|
common: sample_common(1, "s1", "s2"),
|
||||||
target: TargetDescriptor {
|
target: TargetDescriptorV3 {
|
||||||
element_ref: None,
|
element_ref: None,
|
||||||
role: Some("button".into()),
|
role: Some("button".into()),
|
||||||
name: Some("发布".into()),
|
name: Some("发布".into()),
|
||||||
@@ -628,7 +592,7 @@ mod tests {
|
|||||||
"steps": []
|
"steps": []
|
||||||
});
|
});
|
||||||
assert!(RecordedTrace::classify_value(&unsupported).is_err());
|
assert!(RecordedTrace::classify_value(&unsupported).is_err());
|
||||||
assert!(serde_json::from_value::<Trace>(unsupported).is_err());
|
assert!(serde_json::from_value::<TraceV3>(unsupported).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -639,7 +603,7 @@ mod tests {
|
|||||||
.expect("RecordedTrace schema should use oneOf");
|
.expect("RecordedTrace schema should use oneOf");
|
||||||
|
|
||||||
assert_eq!(variants.len(), 2);
|
assert_eq!(variants.len(), 2);
|
||||||
let trace_schema = schema["definitions"]["Trace"].clone();
|
let trace_schema = schema["definitions"]["TraceV3"].clone();
|
||||||
assert_eq!(trace_schema["properties"]["version"]["const"], 3);
|
assert_eq!(trace_schema["properties"]["version"]["const"], 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,8 +618,8 @@ mod tests {
|
|||||||
"entry": { "start_url": "https://example.com/editor" },
|
"entry": { "start_url": "https://example.com/editor" },
|
||||||
"recorder": { "bsk": "0.1.10", "vom": 1 },
|
"recorder": { "bsk": "0.1.10", "vom": 1 },
|
||||||
"states": [
|
"states": [
|
||||||
{ "id": "s1", "url": "https://example.com/editor", "page": "s1.vom.txt" },
|
{ "id": "s1", "url": "https://example.com/editor", "body": "@vom 1" },
|
||||||
{ "id": "s2", "url": "https://example.com/p/99", "page": "s2.vom.txt" }
|
{ "id": "s2", "url": "https://example.com/p/99", "body": "@vom 1" }
|
||||||
],
|
],
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
@@ -676,7 +640,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
let trace: Trace = serde_json::from_value(v).unwrap();
|
let trace: TraceV3 = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(trace.version, 3);
|
assert_eq!(trace.version, 3);
|
||||||
assert_eq!(trace.states.len(), 2);
|
assert_eq!(trace.states.len(), 2);
|
||||||
assert_eq!(trace.steps.len(), 2);
|
assert_eq!(trace.steps.len(), 2);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct TraceEntry {
|
||||||
|
pub start_url: String,
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
//! Trace v2 — record-only user-action log with `pages[]` and step `page` refs.
|
//! Trace v2 — record-only user-action log with `pages[]` and step `page` refs.
|
||||||
//!
|
//!
|
||||||
//! Legacy wire format: no top-level `version` field. New peers omit
|
//! Legacy wire format with no top-level `version` field.
|
||||||
//! `trace_version` on `record_start` to request this shape.
|
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::interaction::KeyModifier;
|
use super::interaction::KeyModifier;
|
||||||
use super::record::TraceEntry;
|
use super::record_common::TraceEntry;
|
||||||
|
|
||||||
/// Stable semantic handle for an interacted element (v2).
|
/// Stable semantic handle for an interacted element (v2).
|
||||||
///
|
///
|
||||||
@@ -29,7 +28,7 @@ pub struct TargetDescriptorV2 {
|
|||||||
|
|
||||||
/// Page context dictionary entry — referenced by steps via `page` id.
|
/// Page context dictionary entry — referenced by steps via `page` id.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct PageRef {
|
pub struct PageRefV2 {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -116,7 +115,7 @@ pub struct TraceV2 {
|
|||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub purpose: Option<String>,
|
pub purpose: Option<String>,
|
||||||
pub entry: TraceEntry,
|
pub entry: TraceEntry,
|
||||||
pub pages: Vec<PageRef>,
|
pub pages: Vec<PageRefV2>,
|
||||||
pub steps: Vec<StepV2>,
|
pub steps: Vec<StepV2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +152,7 @@ mod tests {
|
|||||||
entry: TraceEntry {
|
entry: TraceEntry {
|
||||||
start_url: "https://example.com/".into(),
|
start_url: "https://example.com/".into(),
|
||||||
},
|
},
|
||||||
pages: vec![PageRef {
|
pages: vec![PageRefV2 {
|
||||||
id: "p1".into(),
|
id: "p1".into(),
|
||||||
url: "https://example.com/".into(),
|
url: "https://example.com/".into(),
|
||||||
title: None,
|
title: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user