Skip to content

Commit 474961a

Browse files
committed
fix(protocol): integrate with vom
1 parent 3c7f3bc commit 474961a

33 files changed

Lines changed: 867 additions & 998 deletions

apps/extension/src/lib/__tests__/connection-controller.test.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ function handshake(
2626
}
2727

2828
describe("computeConnectedState (protocol-based compat)", () => {
29-
it("returns connected when daemon protocol equals extension protocol", () => {
30-
expect(computeConnectedState(handshake("1.1", "1.0"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
29+
it("returns connected when protocol strings match", () => {
30+
expect(computeConnectedState(handshake("1.0", "1.0"), MIN_COMPATIBLE_PROTOCOL)).toEqual({
3131
kind: "connected",
3232
});
3333
});
3434

3535
it("returns version_skew when daemon protocol minor is newer", () => {
36-
expect(computeConnectedState(handshake("1.2", "1.0"))).toEqual({
36+
expect(computeConnectedState(handshake("1.1", "1.0"))).toEqual({
3737
kind: "version_skew",
3838
});
3939
});
4040

4141
it("returns version_skew when daemon protocol string differs but floor is satisfied", () => {
42-
expect(computeConnectedState(handshake("1.1.0", "1.0"))).toEqual({
42+
expect(computeConnectedState(handshake("1", "1.0"))).toEqual({
4343
kind: "version_skew",
4444
});
4545
});
@@ -53,7 +53,7 @@ describe("computeConnectedState (protocol-based compat)", () => {
5353
});
5454

5555
it("rejects when extension is below daemon min_compatible_protocol", () => {
56-
const result = computeConnectedState(handshake("1.1", "1.5"));
56+
const result = computeConnectedState(handshake("1.0", "1.5"));
5757
expect(result.kind).toBe("rejected");
5858
if (result.kind === "rejected") {
5959
expect(result.reason).toContain("min_compatible_protocol");
@@ -65,7 +65,7 @@ describe("computeConnectedState (protocol-based compat)", () => {
6565
const result = computeConnectedState({
6666
server: "browser-skill-daemon",
6767
version: "0.1.0",
68-
protocol_version: "1.1",
68+
protocol_version: "1.0",
6969
min_compatible_peer: "0.1.0",
7070
});
7171
expect(result).toEqual({ kind: "connected" });
@@ -79,14 +79,8 @@ describe("computeConnectedState (protocol-based compat)", () => {
7979
}
8080
});
8181

82-
it("returns version_skew when daemon protocol is 1.0 and floor is satisfied", () => {
83-
expect(computeConnectedState(handshake("1.0", "1.0"))).toEqual({
84-
kind: "version_skew",
85-
});
86-
});
87-
8882
it("rejects malformed daemon min_compatible_protocol with a daemon-floor reason", () => {
89-
const result = computeConnectedState(handshake("1.1", "not-a-protocol"));
83+
const result = computeConnectedState(handshake("1.0", "not-a-protocol"));
9084
expect(result.kind).toBe("rejected");
9185
if (result.kind === "rejected") {
9286
expect(result.reason).toContain("daemon min_compatible_protocol");
@@ -248,11 +242,11 @@ describe("ConnectionController connectionEnabled", () => {
248242
const second = transport.send.mock.calls[1]?.[0] as { id: string };
249243
expect(second.id).not.toBe(first.id);
250244

251-
transport.emitMessage({ id: first.id, result: handshake("1.1", "1.0") });
245+
transport.emitMessage({ id: first.id, result: handshake("1.0", "1.0") });
252246
await Promise.resolve();
253247
expect(controller.snapshot().state).not.toBe("connected");
254248

255-
transport.emitMessage({ id: second.id, result: handshake("1.1", "1.0") });
249+
transport.emitMessage({ id: second.id, result: handshake("1.0", "1.0") });
256250
await vi.waitFor(() => expect(controller.snapshot().state).toBe("connected"));
257251
});
258252
});

apps/extension/src/lib/trace-reducer.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DraftTraceStep, PageRef, SelectedOption, Step } from "@/transport/types";
1+
import type { DraftTraceStep, PageRefV2, SelectedOptionV2, StepV2 } from "@/transport/types";
22

33
const CLIPBOARD_KEYS = new Set(["a", "c", "v", "x", "A", "C", "V", "X"]);
44
const MODIFIER_ONLY_KEYS = new Set(["Meta", "Control", "Alt", "Shift", "OS", "Hyper", "Super"]);
@@ -60,7 +60,7 @@ function collectUrls(steps: DraftTraceStep[], startUrl?: string): string[] {
6060
function buildPageRegistry(
6161
steps: DraftTraceStep[],
6262
startUrl?: string,
63-
): { pages: PageRef[]; urlToId: Map<string, string> } {
63+
): { pages: PageRefV2[]; urlToId: Map<string, string> } {
6464
const urls = collectUrls(steps, startUrl);
6565
const urlToId = new Map<string, string>();
6666
const pages = urls.map((url, index) => {
@@ -90,19 +90,19 @@ function pageUrlForDraft(step: DraftTraceStep, fallbackUrl?: string): string | u
9090
function effectForNavigation(
9191
navigatedTo: string | undefined,
9292
urlToId: Map<string, string>,
93-
): Step["effect"] {
93+
): StepV2["effect"] {
9494
if (!navigatedTo) return undefined;
9595
const pageId = urlToId.get(navigatedTo);
9696
if (!pageId) return undefined;
9797
return { navigated_to: pageId };
9898
}
9999

100-
function withEffect(step: Step, effect: Step["effect"]): Step {
100+
function withEffect(step: StepV2, effect: StepV2["effect"]): StepV2 {
101101
if (!effect) return step;
102102
return { ...step, effect };
103103
}
104104

105-
function toSelection(values: string[], labels?: string[]): SelectedOption[] {
105+
function toSelection(values: string[], labels?: string[]): SelectedOptionV2[] {
106106
return values.map((value, index) => ({
107107
value,
108108
...(labels?.[index] ? { label: labels[index] } : {}),
@@ -114,7 +114,7 @@ function toV2Step(
114114
id: number,
115115
urlToId: Map<string, string>,
116116
fallbackUrl?: string,
117-
): Step | null {
117+
): StepV2 | null {
118118
if (!shouldIncludeDraft(step)) return null;
119119

120120
const pageUrl = pageUrlForDraft(step, fallbackUrl);
@@ -181,8 +181,8 @@ function toV2Step(
181181
}
182182

183183
export interface ReducedTrace {
184-
pages: PageRef[];
185-
steps: Step[];
184+
pages: PageRefV2[];
185+
steps: StepV2[];
186186
}
187187

188188
/**
@@ -192,7 +192,7 @@ export interface ReducedTrace {
192192
export function reduceTraceSteps(steps: DraftTraceStep[], startUrl?: string): ReducedTrace {
193193
const collapsed = collapseNavigations(steps);
194194
const { pages, urlToId } = buildPageRegistry(collapsed, startUrl);
195-
const out: Step[] = [];
195+
const out: StepV2[] = [];
196196
let id = 1;
197197
let lastUrl = startUrl;
198198
for (const draft of collapsed) {
@@ -210,7 +210,7 @@ export function reduceTraceSteps(steps: DraftTraceStep[], startUrl?: string): Re
210210
export function resolveTraceStartUrl(
211211
drafts: DraftTraceStep[],
212212
startUrl?: string,
213-
pages?: PageRef[],
213+
pages?: PageRefV2[],
214214
): string {
215215
if (startUrl) return startUrl;
216216
const navigate = drafts.find((step): step is Extract<DraftTraceStep, { op: "navigate" }> => {

apps/extension/src/tools/record.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import type {
2929
RecordStopParams,
3030
RecordStopResult,
3131
RpcError,
32-
Trace,
32+
TraceV2,
3333
} from "@/transport/types";
3434
import { handleNavigate } from "./navigation";
3535
import {
@@ -50,8 +50,8 @@ interface ActiveRecording {
5050
steps: DraftTraceStep[];
5151
startedAt: string;
5252
startedAtMs: number;
53-
finishPromise: Promise<Trace>;
54-
resolveFinish: (trace: Trace) => void;
53+
finishPromise: Promise<TraceV2>;
54+
resolveFinish: (trace: TraceV2) => void;
5555
rejectFinish: (err: Error) => void;
5656
settled: boolean;
5757
finishing: boolean;
@@ -154,7 +154,7 @@ async function sendRecordStartWithAck(
154154
throw lastError ?? new Error("failed to start recording in content script");
155155
}
156156

157-
function buildTrace(recording: ActiveRecording): Trace {
157+
function buildTrace(recording: ActiveRecording): TraceV2 {
158158
const { pages, steps } = reduceTraceSteps(recording.steps, recording.startUrl);
159159
const startUrl = resolveTraceStartUrl(recording.steps, recording.startUrl, pages);
160160
return {
@@ -532,7 +532,7 @@ async function finishRecordingByRequest(
532532
}
533533
}
534534

535-
async function finishRecording(sessionId: string, deps: RecordDeps): Promise<Trace | null> {
535+
async function finishRecording(sessionId: string, deps: RecordDeps): Promise<TraceV2 | null> {
536536
const recording = recordings.get(sessionId);
537537
if (!recording || recording.settled || recording.finishing) return null;
538538
recording.finishing = true;
@@ -574,9 +574,9 @@ export async function handleRecordStart(
574574
// on the destination page can RECORD_QUERY → rearm → show RecordOverlay
575575
// instead of flashing ControlOverlay ("Agent 正在控制").
576576
const requestId = makeRequestId(target.tabId);
577-
let resolveFinish!: (trace: Trace) => void;
577+
let resolveFinish!: (trace: TraceV2) => void;
578578
let rejectFinish!: (err: Error) => void;
579-
const finishPromise = new Promise<Trace>((resolve, reject) => {
579+
const finishPromise = new Promise<TraceV2>((resolve, reject) => {
580580
resolveFinish = resolve;
581581
rejectFinish = reject;
582582
});
@@ -785,9 +785,9 @@ export async function handleRecordAwait(
785785
return { code: "cancelled", message: "record_await aborted" };
786786
}
787787

788-
const outcome = await new Promise<{ trace: Trace } | { error: RpcError }>((resolve) => {
788+
const outcome = await new Promise<{ trace: TraceV2 } | { error: RpcError }>((resolve) => {
789789
let settled = false;
790-
const finish = (result: { trace: Trace } | { error: RpcError }) => {
790+
const finish = (result: { trace: TraceV2 } | { error: RpcError }) => {
791791
if (settled) return;
792792
settled = true;
793793
if (timer) clearTimeout(timer);

apps/extension/src/transport/__tests__/handshake.test.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ function deferredFakeTransport(): { transport: Transport; emit: (frame: Protocol
6565
}
6666

6767
describe("performHandshake", () => {
68-
it("advertises the protocol compatibility boundary", () => {
69-
expect(PROTOCOL_VERSION).toBe("1.1");
70-
expect(MIN_COMPATIBLE_PROTOCOL).toBe("1.0");
71-
});
72-
7368
it("sends system.handshake with identity and both compat fields", async () => {
7469
let sentFrame: ProtocolFrame | null = null;
7570
const transport = fakeTransport((req) => {
@@ -79,7 +74,7 @@ describe("performHandshake", () => {
7974
result: {
8075
server: "browser-skill-daemon",
8176
version: "0.1.0",
82-
protocol_version: "1.1",
77+
protocol_version: "1.0",
8378
min_compatible_peer: "0.0.0",
8479
min_compatible_protocol: "1.0",
8580
},
@@ -134,8 +129,8 @@ describe("performHandshake", () => {
134129
const response = {
135130
server: "browser-skill-daemon",
136131
version: "0.1.0",
137-
protocol_version: "1.1",
138-
min_compatible_protocol: "1.1",
132+
protocol_version: "1.0",
133+
min_compatible_protocol: "1.0",
139134
} satisfies HandshakeResult;
140135
const transport = fakeTransport((req) => ({
141136
id: (req as { id: string }).id,
@@ -150,7 +145,7 @@ describe("performHandshake", () => {
150145
});
151146

152147
expect(outcome.result.min_compatible_peer).toBeUndefined();
153-
expect(outcome.result.min_compatible_protocol).toBe("1.1");
148+
expect(outcome.result.min_compatible_protocol).toBe("1.0");
154149
});
155150

156151
it("rejects when the daemon responds with an error", async () => {
@@ -183,24 +178,24 @@ describe("performHandshake", () => {
183178
result: {
184179
server: "browser-skill-daemon",
185180
version: "0.1.0",
186-
protocol_version: "1.1",
181+
protocol_version: "1.0",
187182
min_compatible_peer: "0.0.0",
188-
min_compatible_protocol: "1.1",
183+
min_compatible_protocol: "1.0",
189184
},
190185
});
191186
emit({
192187
id: "hs-target",
193188
result: {
194189
server: "browser-skill-daemon",
195190
version: "0.1.0",
196-
protocol_version: "1.1",
191+
protocol_version: "1.0",
197192
min_compatible_peer: "0.0.0",
198-
min_compatible_protocol: "1.1",
193+
min_compatible_protocol: "1.0",
199194
},
200195
});
201196

202197
await expect(pending).resolves.toMatchObject({
203-
result: { server: "browser-skill-daemon", protocol_version: "1.1" },
198+
result: { server: "browser-skill-daemon", protocol_version: "1.0" },
204199
});
205200
});
206201

apps/extension/src/transport/handshake.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
ResponseFrame,
88
} from "./types";
99

10-
export const PROTOCOL_VERSION = "1.1";
10+
export const PROTOCOL_VERSION = "1.0";
1111
/**
1212
* Extension semver, injected at build time from `package.json` via
1313
* Vite's `define` (see `wxt.config.ts` and `vitest.config.ts`).

0 commit comments

Comments
 (0)