@wudao-org/browser 0.0.0-stage → 0.4.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/port.d.ts ADDED
@@ -0,0 +1,141 @@
1
+ /** Options the runtime resolves BEFORE a launch (executable resolution order
2
+ * and the honest-error contract live in runtime.ts, not here). */
3
+ export interface BrowserLaunchOptions {
4
+ /** A fully resolved browser binary. Absent → the driver's own registry
5
+ * lookup (for playwright: PLAYWRIGHT_BROWSERS_PATH-relative). */
6
+ readonly executablePath?: string;
7
+ /** v1 is headful by design — the whole point is a window the user can
8
+ * watch (弧门: 看得见的浏览器). headless stays an option for the E2E lane. */
9
+ readonly headless?: boolean;
10
+ /** Extra env for the driver launch (PLAYWRIGHT_BROWSERS_PATH rides here). */
11
+ readonly env?: Readonly<Record<string, string>>;
12
+ /** ω99-114: the persistent profile dir the browser launches with
13
+ * (chromium.launchPersistentContext's userDataDir). The runtime always
14
+ * resolves an absolute path before launching; the default adapter is
15
+ * fail-closed on its absence — there is NO silent fallback to an
16
+ * ephemeral context (login state must survive process restarts, or not
17
+ * promise persistence at all). */
18
+ readonly userDataDir?: string;
19
+ /** ω99-142: the directory page-initiated downloads are saved into (the
20
+ * runtime resolves <home>/browser/page-downloads before launching; the
21
+ * adapter mkdirs it and wires context.on("download") → saveAs). Absent →
22
+ * the adapter wires NO download interception — the runtime always passes
23
+ * it, so absence means a hand-rolled port, never the product path. */
24
+ readonly downloadsDir?: string;
25
+ }
26
+ /** One live browser process (default impl: one playwright Browser). */
27
+ export interface BrowserHandle {
28
+ newPage(): Promise<PageHandle>;
29
+ close(): Promise<void>;
30
+ /** Best-effort SYNCHRONOUS kill for process-exit hooks — an async close()
31
+ * cannot settle inside "exit". Absent → close() is the only recourse. */
32
+ kill?(): void;
33
+ }
34
+ /** ω99-142 — one COMPLETED page-initiated download (a page's export button
35
+ * clicked by the model, saved to disk by the adapter's context.on("download")
36
+ * interception). An in-flight save is deliberately NOT representable: the
37
+ * manifest reports only files that actually landed (honest numbers). */
38
+ export interface DownloadRecord {
39
+ /** The sanitized landed filename — timestamp-prefixed, safeSegment-verified
40
+ * (the page-controlled suggestedFilename NEVER joins a path raw). */
41
+ readonly filename: string;
42
+ /** File size in bytes (stat after saveAs). */
43
+ readonly bytes: number;
44
+ /** The absolute path the file landed at. */
45
+ readonly path: string;
46
+ }
47
+ /** One controlled tab (default impl: one playwright Page). */
48
+ export interface PageHandle {
49
+ /** The page's current URL — the runtime's navigation detector. */
50
+ url(): Promise<string>;
51
+ /** Navigate; resolves on domcontentloaded with the FINAL url (redirects
52
+ * included). Throws on invalid/unreachable targets. */
53
+ goto(url: string): Promise<{
54
+ readonly finalUrl: string;
55
+ }>;
56
+ title(): Promise<string>;
57
+ /** Evaluate a page expression (the snapshot collector runs here). */
58
+ evaluate<T = unknown>(script: string): Promise<T>;
59
+ /** Click the element matching the selector (snapshot refs become
60
+ * `[data-wudao-ref="eN"]` selectors). Short timeout — a miss is the stale
61
+ * story, not a 30s hang. */
62
+ click(selector: string): Promise<void>;
63
+ /** ω99-139 — click at RAW viewport coordinates for the canvas/unref'd layer
64
+ * of browser_click_xy. SPACE: CSS pixels, the same grid as the snapshot
65
+ * bboxes (getBoundingClientRect) and browser_see's css-scale screenshot —
66
+ * the driver dispatches at viewport coordinates, so no conversion happens
67
+ * on either side of this seam. */
68
+ clickAt(x: number, y: number): Promise<void>;
69
+ /** ω99-141 — move the mouse to RAW viewport coordinates, then dispatch a
70
+ * wheel with the given deltas — ONE gesture (move + wheel are inseparable:
71
+ * the driver routes the wheel to the scrollable container under the
72
+ * pointer, which is exactly what browser_scroll_xy needs and what
73
+ * page-level window.scrollBy can never reach). SPACE: CSS pixels, the same
74
+ * grid as the snapshot bboxes and clickAt; dx/dy are wheel deltas in the
75
+ * same units (positive dy scrolls down). */
76
+ wheelAt(x: number, y: number, dx: number, dy: number): Promise<void>;
77
+ /** Type text into a form field (focus + replace). */
78
+ fill(selector: string, text: string): Promise<void>;
79
+ /** The page body's visible text. */
80
+ text(): Promise<string>;
81
+ /** Press a keyboard key at the page ("Enter" for submit). */
82
+ press(key: string): Promise<void>;
83
+ /** ω99-140 — type text KEYSTROKE BY KEYSTROKE into whatever element
84
+ * currently holds focus (playwright's keyboard.type dispatches each key
85
+ * to the focused element). The typing primitive for browser_type_xy's
86
+ * miss layer: clickAt(x, y) lands the focus, typeText fills it — an
87
+ * unref'd field (canvas, contenteditable) has no selector, so focus is
88
+ * the only honest address. */
89
+ typeText(text: string): Promise<void>;
90
+ /** ω99-136 — PNG bytes of the page for the vision lane (canvas pages the
91
+ * DOM snapshot is blind to). DEFAULT PIXEL SPACE: DEVICE pixels —
92
+ * playwright captures via CDP, so a Retina tab (devicePixelRatio 2) yields
93
+ * a PNG twice the CSS viewport in each dimension; snapshot bboxes
94
+ * (snapshot.ts) are CSS pixels.
95
+ * ω99-138 — `scale: "css"` captures at device-INDEPENDENT CSS pixels (the
96
+ * DPR gap is normalized at the source), which makes CSS the canonical
97
+ * coordinate space: a css-scale screenshot and the bbox table describe the
98
+ * same grid, so no conversion ever has to happen downstream. The device
99
+ * default stays (136 behavior, zero drift); browser_see asks for "css". */
100
+ screenshot(opts?: {
101
+ readonly scale?: "css" | "device";
102
+ }): Promise<Uint8Array>;
103
+ /** ω99-142 — the COMPLETED page-initiated downloads of this browser
104
+ * session (context-level: any tab's export lands here; the surface rides
105
+ * the page because the runtime is single-page). Completed saves only —
106
+ * a save still in flight never appears (the manifest has no phantom
107
+ * rows). Optional: the default adapter always provides it. */
108
+ downloads?(): Promise<readonly DownloadRecord[]>;
109
+ close(): Promise<void>;
110
+ }
111
+ /** The seam. Inject a Fake in every unit battery; the default is the
112
+ * playwright-core adapter below. */
113
+ export interface BrowserPort {
114
+ launch(opts: BrowserLaunchOptions): Promise<BrowserHandle>;
115
+ }
116
+ /** Timeouts the default adapter pins (playwright's 30s defaults would turn a
117
+ * stale-ref miss into a half-minute hang; these keep failure honest and
118
+ * quick). Exported for the E2E lane's expectations. */
119
+ export declare const PORT_TIMEOUTS: {
120
+ /** Navigation budget (a slow first paint is fine to wait for). */
121
+ readonly gotoMs: 30000;
122
+ /** Element-action budget (click/fill — a miss means stale refs). */
123
+ readonly actionMs: 5000;
124
+ };
125
+ /** The default BrowserPort — playwright-core, loaded lazily. The dynamic
126
+ * import is the ONLY reference to the driver in this package's static
127
+ * graph; unit tests never reach it (they inject their own port). The
128
+ * playwright objects are viewed through a minimal structural type so this
129
+ * adapter survives driver typing churn (sync url(), evaluate overloads,
130
+ * the launched-process optional).
131
+ *
132
+ * ω99-114: launches via chromium.launchPersistentContext(userDataDir, …) —
133
+ * one persistent profile per userDataDir, so cookies/logins survive
134
+ * process restarts (the whole point of the login arc). The returned
135
+ * BrowserContext (not a Browser) has pages()/newPage()/close() but NO
136
+ * process handle, so this handle omits kill (BrowserHandle.kill is
137
+ * optional): playwright's own SIGINT/SIGTERM/SIGHUP handling below is the
138
+ * first line of process hygiene, and the runtime's exit hook degrades to
139
+ * a close() attempt — recorded honestly, never faked. */
140
+ export declare function createPlaywrightPort(): BrowserPort;
141
+ //# sourceMappingURL=port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
package/dist/port.js ADDED
@@ -0,0 +1,145 @@
1
+ // ω99-112 — the controlled-browser port: the injectable seam between the
2
+ // wudao runtime and the browser driver. Everything above this file is driver
3
+ // agnostic (tests inject a Fake port, zero real browser); playwright-core
4
+ // itself is imported ONLY inside the default implementation's launch(), as a
5
+ // dynamic import — importing this module costs nothing and the CLI's static
6
+ // graph never pulls the ~9MB driver in until a browser is actually launched.
7
+ //
8
+ // PageHandle is deliberately playwright-shaped (a strict subset: goto/title/
9
+ // evaluate/click/fill/text/press/close + url for stale-ref detection), so the
10
+ // default adapter below is a one-liner per method and a second driver (CDP,
11
+ // WebKit, …) can slot in without touching the runtime, the snapshot, or the
12
+ // six model tools.
13
+ // ω99-142 — the download seam: the adapter intercepts context-level
14
+ // "download" events (a page export would otherwise vanish on close) and
15
+ // saves them under sanitized names; PageHandle gains an OPTIONAL downloads()
16
+ // for the manifest (the default adapter always provides it — absence marks a
17
+ // hand-rolled port, never the product path).
18
+ import { mkdir, stat } from "node:fs/promises";
19
+ import { join } from "node:path";
20
+ import { safeSegment } from "./download.js";
21
+ /** Timeouts the default adapter pins (playwright's 30s defaults would turn a
22
+ * stale-ref miss into a half-minute hang; these keep failure honest and
23
+ * quick). Exported for the E2E lane's expectations. */
24
+ export const PORT_TIMEOUTS = {
25
+ /** Navigation budget (a slow first paint is fine to wait for). */
26
+ gotoMs: 30_000,
27
+ /** Element-action budget (click/fill — a miss means stale refs). */
28
+ actionMs: 5_000,
29
+ };
30
+ /** The default BrowserPort — playwright-core, loaded lazily. The dynamic
31
+ * import is the ONLY reference to the driver in this package's static
32
+ * graph; unit tests never reach it (they inject their own port). The
33
+ * playwright objects are viewed through a minimal structural type so this
34
+ * adapter survives driver typing churn (sync url(), evaluate overloads,
35
+ * the launched-process optional).
36
+ *
37
+ * ω99-114: launches via chromium.launchPersistentContext(userDataDir, …) —
38
+ * one persistent profile per userDataDir, so cookies/logins survive
39
+ * process restarts (the whole point of the login arc). The returned
40
+ * BrowserContext (not a Browser) has pages()/newPage()/close() but NO
41
+ * process handle, so this handle omits kill (BrowserHandle.kill is
42
+ * optional): playwright's own SIGINT/SIGTERM/SIGHUP handling below is the
43
+ * first line of process hygiene, and the runtime's exit hook degrades to
44
+ * a close() attempt — recorded honestly, never faked. */
45
+ export function createPlaywrightPort() {
46
+ return {
47
+ async launch(opts) {
48
+ // Fail-closed BEFORE the dynamic import: absent/empty userDataDir
49
+ // must refuse here (unit-testable without pulling the ~9MB driver —
50
+ // 红线 4), and must never silently fall back to an ephemeral context
51
+ // that would eat the login state on exit.
52
+ if (typeof opts.userDataDir !== "string" || opts.userDataDir === "") {
53
+ throw new Error("userDataDir is required: the controlled browser always launches with a persistent profile (no silent fallback to an ephemeral context).");
54
+ }
55
+ const userDataDir = opts.userDataDir;
56
+ const { chromium } = await import("playwright-core");
57
+ const context = (await chromium.launchPersistentContext(userDataDir, {
58
+ headless: opts.headless ?? false,
59
+ ...(opts.executablePath !== undefined ? { executablePath: opts.executablePath } : {}),
60
+ ...(opts.env !== undefined ? { env: { ...process.env, ...opts.env } } : {}),
61
+ // Playwright's own signal hygiene is the first line of "进程退出必收
62
+ // 窗"; our exit hook (runtime.ts) is the belt-and-braces second
63
+ // (degraded to close() — see the doc comment above).
64
+ handleSIGINT: true,
65
+ handleSIGTERM: true,
66
+ handleSIGHUP: true,
67
+ }));
68
+ // ω99-142 — a page-initiated download left unsaved is LOST when the
69
+ // context closes (playwright keeps it only in memory): intercept every
70
+ // download event, saveAs into the caller-resolved dir under a
71
+ // timestamp-prefixed SANITIZED name, and record COMPLETED saves for
72
+ // PageHandle.downloads() (the manifest reports landed files only).
73
+ const completed = [];
74
+ if (opts.downloadsDir !== undefined) {
75
+ const dir = opts.downloadsDir;
76
+ // Honest failure: a downloads dir we cannot create is a launch
77
+ // failure (the interception promise cannot be kept) — never a silent
78
+ // no-download posture (the exact bug this arc exists to close).
79
+ await mkdir(dir, { recursive: true });
80
+ const inflight = new Set(); // received-but-unsaved — invisible to the manifest by design
81
+ context.on("download", (d) => {
82
+ inflight.add(d);
83
+ void (async () => {
84
+ // suggestedFilename is PAGE-CONTROLLED: safeSegment (download.ts)
85
+ // or the "download.bin" fallback — the raw name never joins a path.
86
+ const safeName = safeSegment(d.suggestedFilename()) ?? "download.bin";
87
+ const filename = `${Date.now()}-${safeName}`; // timestamp prefix: same-name exports never overwrite
88
+ const path = join(dir, filename);
89
+ await d.saveAs(path);
90
+ completed.push({ filename, bytes: (await stat(path)).size, path });
91
+ })()
92
+ .catch(() => undefined) // a failed save never enters the manifest (completed only — honest numbers)
93
+ .finally(() => { inflight.delete(d); });
94
+ });
95
+ }
96
+ const adapt = (page) => ({
97
+ url: async () => page.url(),
98
+ async goto(url) {
99
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: PORT_TIMEOUTS.gotoMs });
100
+ return { finalUrl: page.url() };
101
+ },
102
+ title: () => page.title(),
103
+ evaluate: (script) => page.evaluate(script),
104
+ click: (selector) => page.click(selector, { timeout: PORT_TIMEOUTS.actionMs }),
105
+ // ω99-139: raw coordinate click (CSS pixels) — mouse passthrough.
106
+ clickAt: (x, y) => page.mouse.click(x, y),
107
+ // ω99-141: coordinate wheel — move the pointer FIRST, then wheel; the
108
+ // driver delivers the scroll to whatever container sits under (x, y).
109
+ wheelAt: async (x, y, dx, dy) => {
110
+ await page.mouse.move(x, y);
111
+ await page.mouse.wheel(dx, dy);
112
+ },
113
+ fill: (selector, text) => page.fill(selector, text, { timeout: PORT_TIMEOUTS.actionMs }),
114
+ text: () => page.innerText("body"),
115
+ press: (key) => page.keyboard.press(key),
116
+ // ω99-140: keystroke typing to the FOCUSED element — one-line
117
+ // passthrough (see PageHandle.typeText for the semantics).
118
+ typeText: (text) => page.keyboard.type(text),
119
+ // ω99-136: vision-lane passthrough — PNG bytes in the driver's device
120
+ // pixel space, straight from playwright (see PageHandle.screenshot).
121
+ // ω99-138: an explicit scale rides through verbatim; absent scale
122
+ // keeps the 136 bytes (playwright's device default) — zero drift.
123
+ screenshot: (opts) => page.screenshot({ type: "png", ...(opts?.scale !== undefined ? { scale: opts.scale } : {}) }),
124
+ close: () => page.close(),
125
+ // ω99-142: the manifest — one shared array per context (every page of
126
+ // this launch sees the same completed records; a COPY, never the live
127
+ // array). No downloadsDir → no method: the honest degradation.
128
+ ...(opts.downloadsDir !== undefined
129
+ ? { downloads: async () => [...completed] }
130
+ : {}),
131
+ });
132
+ return {
133
+ async newPage() {
134
+ // launchPersistentContext spawns with one initial blank page —
135
+ // adopt it instead of piling up tabs; newPage() only when the
136
+ // initial page is gone (closed by a test or the idle reaper).
137
+ const page = context.pages()[0] ?? (await context.newPage());
138
+ return adapt(page);
139
+ },
140
+ close: () => context.close(),
141
+ };
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,mBAAmB;AACnB,oEAAoE;AACpE,wEAAwE;AACxE,6EAA6E;AAC7E,6EAA6E;AAC7E,6CAA6C;AAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAsH5C;;wDAEwD;AACxD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,kEAAkE;IAClE,MAAM,EAAE,MAAM;IACd,oEAAoE;IACpE,QAAQ,EAAE,KAAK;CACP,CAAC;AA0CX;;;;;;;;;;;;;;0DAc0D;AAC1D,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,IAAI;YACf,kEAAkE;YAClE,oEAAoE;YACpE,mEAAmE;YACnE,0CAA0C;YAC1C,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,KAAK,EAAE,EAAE,CAAC;gBACpE,MAAM,IAAI,KAAK,CACb,yIAAyI,CAC1I,CAAC;YACJ,CAAC;YACD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;YACrC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,uBAAuB,CAAC,WAAW,EAAE;gBACnE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK;gBAChC,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrF,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3E,+DAA+D;gBAC/D,+DAA+D;gBAC/D,qDAAqD;gBACrD,YAAY,EAAE,IAAI;gBAClB,aAAa,EAAE,IAAI;gBACnB,YAAY,EAAE,IAAI;aACnB,CAAC,CAMD,CAAC;YACF,oEAAoE;YACpE,uEAAuE;YACvE,8DAA8D;YAC9D,oEAAoE;YACpE,mEAAmE;YACnE,MAAM,SAAS,GAAqB,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;gBACpC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC;gBAC9B,+DAA+D;gBAC/D,qEAAqE;gBACrE,gEAAgE;gBAChE,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAC,CAAC,6DAA6D;gBACrG,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;oBAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBAChB,KAAK,CAAC,KAAK,IAAI,EAAE;wBACf,kEAAkE;wBAClE,oEAAoE;wBACpE,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,IAAI,cAAc,CAAC;wBACtE,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC,sDAAsD;wBACpG,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;wBACjC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;wBACrB,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrE,CAAC,CAAC,EAAE;yBACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,4EAA4E;yBACnG,OAAO,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5C,CAAC,CAAC,CAAC;YACL,CAAC;YACD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC;gBAC/B,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC3B,KAAK,CAAC,IAAI,CAAC,GAAW;oBACpB,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;oBACvF,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAClC,CAAC;gBACD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE;gBACzB,QAAQ,EAAE,CAAI,MAAc,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAI,MAAM,CAAC;gBACzD,KAAK,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,CAAC;gBACtF,kEAAkE;gBAClE,OAAO,EAAE,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzD,sEAAsE;gBACtE,sEAAsE;gBACtE,OAAO,EAAE,KAAK,EAAE,CAAS,EAAE,CAAS,EAAE,EAAU,EAAE,EAAU,EAAE,EAAE;oBAC9D,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAC5B,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACjC,CAAC;gBACD,IAAI,EAAE,CAAC,QAAgB,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,CAAC;gBACxG,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;gBAClC,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;gBAChD,8DAA8D;gBAC9D,2DAA2D;gBAC3D,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBACpD,sEAAsE;gBACtE,qEAAqE;gBACrE,kEAAkE;gBAClE,kEAAkE;gBAClE,UAAU,EAAE,CAAC,IAAmC,EAAE,EAAE,CAClD,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC/F,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE;gBACzB,sEAAsE;gBACtE,sEAAsE;gBACtE,+DAA+D;gBAC/D,GAAG,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS;oBACjC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,IAAwC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE;oBAC/E,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;YACH,OAAO;gBACL,KAAK,CAAC,OAAO;oBACX,+DAA+D;oBAC/D,8DAA8D;oBAC9D,8DAA8D;oBAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrB,CAAC;gBACD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;aAC7B,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,92 @@
1
+ import type { BrowserPort, PageHandle } from "./port.js";
2
+ import type { ElementBbox } from "./snapshot.js";
3
+ /** The idle reaper's window: 10 minutes of no tool calls → the page and the
4
+ * browser close (no orphan windows after the model wanders off). */
5
+ export declare const DEFAULT_IDLE_MS: number;
6
+ export type BrowserState = "closed" | "launching" | "open";
7
+ /** The last snapshot's ref→element mapping (see snapshot.ts). */
8
+ export interface SnapshotRefEntry {
9
+ readonly selector: string;
10
+ readonly desc: string;
11
+ /** ω99-114: the input's type attribute when the collector saw one.
12
+ * "password" arms the fail-closed typing gate in tools.ts (the model
13
+ * never types passwords); undefined means unknown — NOT password — and
14
+ * never blocks typing (fail-closed fires only on certain knowledge). */
15
+ readonly type?: string;
16
+ /** ω99-139: the element's viewport rect in CSS pixels when the collector
17
+ * reported one (136's ElementBbox) — browser_click_xy's hit-test source.
18
+ * Absent = geometry not collected, never guessed; it never gates click/type. */
19
+ readonly bbox?: ElementBbox;
20
+ }
21
+ export interface PageSnapshotRefs {
22
+ readonly url: string;
23
+ readonly refs: ReadonlyMap<string, SnapshotRefEntry>;
24
+ }
25
+ /** The executable resolution decision (pure; order pinned by tests):
26
+ * env override → the downloaded/installed kernel → the driver's registry. */
27
+ export type ExecutableResolution = {
28
+ readonly kind: "executable";
29
+ readonly path: string;
30
+ } | {
31
+ readonly kind: "registry";
32
+ };
33
+ export declare function resolveExecutablePath(env: Record<string, string | undefined>, installed: string | null): ExecutableResolution;
34
+ /** Honest launch failure (model-facing): what broke + the one-sentence way
35
+ * out. Every launch-path failure surfaces THIS shape, never a raw stack. */
36
+ export declare function browserLaunchError(reason: string): Error;
37
+ export interface BrowserRuntimeOptions {
38
+ readonly port?: BrowserPort;
39
+ /** default ~/.wudao-desktop (the desktop app's data home). */
40
+ readonly homeDir?: string;
41
+ /** default () => process.env (read at LAUNCH time, not construction). */
42
+ readonly env?: () => Record<string, string | undefined>;
43
+ readonly idleMs?: number;
44
+ /** E2E lane may force headless; product default is headful (围观). */
45
+ readonly headless?: boolean;
46
+ /** ω99-114 persistent login profile: the userDataDir the browser launches
47
+ * with, so cookies/logins survive process restarts (a site the user
48
+ * logged into once in the visible window is still logged in next time).
49
+ * Default <home>/browser/profile — the SAME root as the kernel download
50
+ * (download.ts's browserRoot), never the user's daily browser profile.
51
+ * Created lazily on first launch; construction stays zero-IO. Tests/E2E
52
+ * may inject a temp dir. */
53
+ readonly userDataDir?: string;
54
+ /** default: ensureChromiumDownloaded (first-use download). */
55
+ readonly download?: (homeDir: string) => Promise<{
56
+ readonly executablePath: string;
57
+ }>;
58
+ /** default: installedExecutablePath (the ~/.wudao-desktop/browser probe). */
59
+ readonly resolveInstalled?: (homeDir: string) => Promise<string | null>;
60
+ /** default: node:fs/promises mkdir({recursive:true}). Injected as an
61
+ * immediate no-op by fake-timer choreographies (a real-IO mkdir cannot
62
+ * settle inside advanceTimersByTimeAsync, which yields only microtasks
63
+ * and fake timers); the A-2 battery keeps the real default and asserts
64
+ * the directory truly materializes. */
65
+ readonly profileMkdir?: (dir: string) => Promise<void>;
66
+ /** default: process.once("exit", fn); returns the unregister. */
67
+ readonly registerExitHook?: (fn: () => void) => () => void;
68
+ /** ω99-112 SEA degrade: the probe for the single-file desktop bundle.
69
+ * Default: node:sea's isSea(). Under SEA the launch fails FAST with the
70
+ * honest posture (the bundle ships playwright-core as an esbuild external
71
+ * — its internal requires cannot bundle), instead of a module-not-found
72
+ * stack or a silent hang. Terminal/dev mode is unaffected. */
73
+ readonly seaProbe?: () => boolean;
74
+ }
75
+ export interface BrowserRuntime {
76
+ state(): BrowserState;
77
+ /** Launch-if-needed (memoized) + reset the idle reaper. Rejects with the
78
+ * honest browserLaunchError on any launch-path failure. */
79
+ openPage(): Promise<PageHandle>;
80
+ /** Reset the idle window (every tool call rides this). */
81
+ touch(): void;
82
+ /** Close page + browser, clear timers/hooks/snapshot. Idempotent. */
83
+ close(): Promise<void>;
84
+ /** Snapshot bookkeeping (the tools' ref store; navigation voids it). */
85
+ saveSnapshot(url: string, refs: ReadonlyMap<string, SnapshotRefEntry>): void;
86
+ currentSnapshot(): PageSnapshotRefs | null;
87
+ invalidateSnapshot(): void;
88
+ /** Void the snapshot when the page moved since it was taken. */
89
+ noteUrl(url: string): void;
90
+ }
91
+ export declare function createBrowserRuntime(opts?: BrowserRuntimeOptions): BrowserRuntime;
92
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ // ω99-112 — the browser runtime: one controlled browser per session, with a
2
+ // closed→launching→open state machine, launch-once memoization (concurrent
3
+ // tool calls share one launch), a 10-minute idle reaper (every tool call
4
+ // resets it; the visible window closes itself when the model stops using
5
+ // it), a process-exit hook belt-and-braces over playwright's own signal
6
+ // handling, and the snapshot ref store with navigation invalidation.
7
+ // Construction is ZERO-IO: nothing spawns, nothing stats, nothing reads env
8
+ // until the first openPage() — safe to build at every frontend boot seam.
9
+ import { homedir } from "node:os";
10
+ import { mkdir } from "node:fs/promises";
11
+ import { join } from "node:path";
12
+ import { createPlaywrightPort } from "./port.js";
13
+ import { browserRoot, ensureChromiumDownloaded, installedExecutablePath } from "./download.js";
14
+ /** The idle reaper's window: 10 minutes of no tool calls → the page and the
15
+ * browser close (no orphan windows after the model wanders off). */
16
+ export const DEFAULT_IDLE_MS = 10 * 60_000;
17
+ export function resolveExecutablePath(env, installed) {
18
+ const fromEnv = (env["WUDAO_BROWSER_EXECUTABLE"] ?? "").trim();
19
+ if (fromEnv !== "")
20
+ return { kind: "executable", path: fromEnv };
21
+ if (installed !== null)
22
+ return { kind: "executable", path: installed };
23
+ return { kind: "registry" };
24
+ }
25
+ /** Honest launch failure (model-facing): what broke + the one-sentence way
26
+ * out. Every launch-path failure surfaces THIS shape, never a raw stack. */
27
+ export function browserLaunchError(reason) {
28
+ return new Error(`Browser launch failed (${reason}). The Chromium kernel auto-downloads to ~/.wudao-desktop/browser on first use; to prepare it manually, point WUDAO_BROWSER_EXECUTABLE at a local Chromium executable and retry.`);
29
+ }
30
+ const defaultRegisterExitHook = (fn) => {
31
+ process.once("exit", fn);
32
+ return () => process.removeListener("exit", fn);
33
+ };
34
+ /** node:sea's isSea, guarded for non-Node/runtimes without the module. */
35
+ const defaultSeaProbe = () => {
36
+ try {
37
+ // node:sea exists on Node >= 22; isSea() is false everywhere except a
38
+ // single-executable application blob.
39
+ return process.getBuiltinModule?.("node:sea")?.isSea() === true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ };
45
+ export function createBrowserRuntime(opts = {}) {
46
+ const port = opts.port ?? createPlaywrightPort();
47
+ const homeDir = opts.homeDir ?? join(homedir(), ".wudao-desktop");
48
+ // ω99-114: the persistent login profile (string resolution only — the
49
+ // dir itself materializes on first launch, never at construction).
50
+ const userDataDir = opts.userDataDir ?? join(browserRoot(homeDir), "profile");
51
+ // ω99-142: where page-initiated downloads land — a sibling of the profile
52
+ // under the SAME browser root, deliberately independent of the kernel's
53
+ // scratch "downloads" dir (page exports never mix with kernel zips).
54
+ const downloadsDir = join(browserRoot(homeDir), "page-downloads");
55
+ const envRead = opts.env ?? (() => process.env);
56
+ const idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;
57
+ const download = opts.download ?? ((home) => ensureChromiumDownloaded({ homeDir: home }));
58
+ const resolveInstalled = opts.resolveInstalled ?? ((home) => installedExecutablePath(home));
59
+ const profileMkdir = opts.profileMkdir ?? ((dir) => mkdir(dir, { recursive: true }));
60
+ const registerExitHook = opts.registerExitHook ?? defaultRegisterExitHook;
61
+ const seaProbe = opts.seaProbe ?? defaultSeaProbe;
62
+ let state = "closed";
63
+ let handle;
64
+ let page;
65
+ let inflight;
66
+ let idleTimer;
67
+ let unregisterExit;
68
+ let snapshot = null;
69
+ const disarmIdle = () => {
70
+ if (idleTimer !== undefined)
71
+ clearTimeout(idleTimer);
72
+ idleTimer = undefined;
73
+ };
74
+ const armIdle = () => {
75
+ disarmIdle();
76
+ idleTimer = setTimeout(() => { void closeInternal(); }, idleMs);
77
+ idleTimer?.unref?.();
78
+ };
79
+ const exitHook = () => {
80
+ // An async close cannot settle inside "exit" — the sync kill is the
81
+ // real reaper (playwright's own SIG* handling is the first line).
82
+ if (handle?.kill !== undefined)
83
+ handle.kill();
84
+ else
85
+ void handle?.close();
86
+ };
87
+ async function launch() {
88
+ // ω99-112 SEA degrade: inside the desktop single-file bundle the driver
89
+ // is an esbuild external (unbundleable internal requires) — fail fast
90
+ // with the honest posture, never a module stack or a hang.
91
+ if (seaProbe()) {
92
+ throw browserLaunchError("the desktop app bundle does not ship the browser driver yet — browser tools work in terminal/dev mode and arrive in a coming desktop release");
93
+ }
94
+ const env = envRead();
95
+ const installed = await resolveInstalled(homeDir).catch(() => null);
96
+ const resolution = resolveExecutablePath(env, installed);
97
+ let launchOpts;
98
+ if (resolution.kind === "executable") {
99
+ launchOpts = { executablePath: resolution.path, ...(opts.headless === true ? { headless: true } : {}) };
100
+ }
101
+ else {
102
+ // First use: try the download once; a user-managed registry kernel
103
+ // (PLAYWRIGHT_BROWSERS_PATH) stays the fallback when it fails.
104
+ let downloaded;
105
+ try {
106
+ downloaded = (await download(homeDir)).executablePath;
107
+ }
108
+ catch {
109
+ downloaded = undefined; // honest fallback below; a full failure surfaces there
110
+ }
111
+ launchOpts = {
112
+ ...(downloaded !== undefined ? { executablePath: downloaded } : { env: { PLAYWRIGHT_BROWSERS_PATH: browserRoot(homeDir) } }),
113
+ ...(opts.headless === true ? { headless: true } : {}),
114
+ };
115
+ }
116
+ try {
117
+ // ω99-114: materialize the persistent profile dir (idempotent; lazy —
118
+ // see BrowserRuntimeOptions.userDataDir). Its failure is a launch
119
+ // failure and surfaces the honest shape.
120
+ await profileMkdir(userDataDir);
121
+ }
122
+ catch (err) {
123
+ throw browserLaunchError(`could not create the persistent profile directory ${userDataDir} (${err instanceof Error ? err.message : String(err)})`);
124
+ }
125
+ try {
126
+ // ω99-142: downloadsDir rides every launch (the adapter's download
127
+ // interception seam — without it a page export vanishes on close).
128
+ handle = await port.launch({ ...launchOpts, userDataDir, downloadsDir });
129
+ page = await handle.newPage();
130
+ }
131
+ catch (err) {
132
+ handle = undefined;
133
+ page = undefined;
134
+ throw browserLaunchError(err instanceof Error ? err.message : String(err));
135
+ }
136
+ state = "open";
137
+ unregisterExit = registerExitHook(exitHook);
138
+ armIdle();
139
+ return page;
140
+ }
141
+ async function closeInternal() {
142
+ if (state === "closed" && handle === undefined && page === undefined)
143
+ return;
144
+ const p = page;
145
+ const h = handle;
146
+ page = undefined;
147
+ handle = undefined;
148
+ state = "closed";
149
+ snapshot = null;
150
+ disarmIdle();
151
+ if (unregisterExit !== undefined)
152
+ unregisterExit();
153
+ unregisterExit = undefined;
154
+ try {
155
+ await p?.close();
156
+ }
157
+ catch { /* best-effort */ }
158
+ try {
159
+ await h?.close();
160
+ }
161
+ catch { /* best-effort */ }
162
+ }
163
+ return {
164
+ state: () => state,
165
+ openPage() {
166
+ if (state === "open" && page !== undefined) {
167
+ armIdle();
168
+ return Promise.resolve(page);
169
+ }
170
+ if (inflight !== undefined)
171
+ return inflight;
172
+ state = "launching";
173
+ inflight = launch().then((p) => { inflight = undefined; return p; }, (err) => { inflight = undefined; state = "closed"; throw err; });
174
+ return inflight;
175
+ },
176
+ touch: () => { if (state === "open")
177
+ armIdle(); },
178
+ close: closeInternal,
179
+ saveSnapshot(url, refs) { snapshot = { url, refs }; },
180
+ currentSnapshot: () => snapshot,
181
+ invalidateSnapshot() { snapshot = null; },
182
+ noteUrl(url) {
183
+ if (snapshot !== null && snapshot.url !== url)
184
+ snapshot = null;
185
+ },
186
+ };
187
+ }
188
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}