dsh-browser-playwright-codex 0.5.2 → 0.5.4

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +17 -7
  3. package/SECURITY.md +15 -10
  4. package/lib/browser-lifecycle.d.ts +12 -0
  5. package/lib/browser-lifecycle.js +186 -0
  6. package/lib/challenge.d.ts +20 -0
  7. package/lib/challenge.js +40 -0
  8. package/lib/config.d.ts +81 -0
  9. package/lib/config.js +61 -0
  10. package/lib/extract.d.ts +13 -0
  11. package/lib/extract.js +34 -0
  12. package/lib/index.d.ts +4 -2
  13. package/lib/index.js +3 -1
  14. package/lib/login-state.d.ts +10 -0
  15. package/lib/login-state.js +83 -0
  16. package/lib/page-actions.d.ts +134 -0
  17. package/lib/page-actions.js +725 -0
  18. package/lib/page-diagnostics.d.ts +65 -0
  19. package/lib/page-diagnostics.js +181 -0
  20. package/lib/page-probes.d.ts +78 -0
  21. package/lib/page-probes.js +281 -0
  22. package/lib/page-snapshot.d.ts +96 -0
  23. package/lib/page-snapshot.js +280 -0
  24. package/lib/playwright.d.ts +5 -644
  25. package/lib/playwright.js +28 -2602
  26. package/lib/provider.d.ts +157 -0
  27. package/lib/provider.js +352 -0
  28. package/lib/session.d.ts +410 -0
  29. package/lib/session.js +633 -0
  30. package/lib/tool-render.d.ts +20 -0
  31. package/lib/tool-render.js +137 -0
  32. package/lib/tool-schemas.d.ts +76 -0
  33. package/lib/tool-schemas.js +46 -0
  34. package/lib/tool-types.d.ts +82 -0
  35. package/lib/tool-types.js +1 -0
  36. package/lib/tool.d.ts +2 -26
  37. package/lib/tool.js +5 -252
  38. package/lib/url-policy.d.ts +2 -0
  39. package/lib/url-policy.js +26 -0
  40. package/lib/window-focus.d.ts +27 -0
  41. package/lib/window-focus.js +54 -0
  42. package/package.json +1 -1
  43. package/scripts/dsh-browser-verify.mjs +2 -1
  44. package/scripts/prompt-cost.mjs +1 -1
@@ -0,0 +1,10 @@
1
+ import type { BrowserContext } from 'playwright-core';
2
+ import type { PlaywrightProvider } from './provider.ts';
3
+ /** Export the login-state fallback to the state file (session-cookie fallback). */
4
+ export declare function persistState(provider: PlaywrightProvider, context: BrowserContext): Promise<void>;
5
+ /**
6
+ * Re-inject a previously exported state. Durable cookies already live in
7
+ * the profile; this covers session cookies the browser drops on close and
8
+ * localStorage entries, so logins survive window close/reopen.
9
+ */
10
+ export declare function restoreState(provider: PlaywrightProvider, context: BrowserContext): Promise<void>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 登录态持久化:把 profile 的 cookies+localStorage 导出到状态文件、并在重开时恢复;
3
+ * 窗口最小化或读不到窗口状态时只取 cookies。
4
+ * @module dsh-browser-playwright-codex/login-state
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ /** Export the login-state fallback to the state file (session-cookie fallback). */
9
+ export async function persistState(provider, context) {
10
+ if (provider.stateFile === undefined || context === undefined)
11
+ return;
12
+ try {
13
+ const state = await provider.collectLoginState(context);
14
+ const tmp = provider.stateFile + '.tmp';
15
+ fs.mkdirSync(path.dirname(provider.stateFile), { recursive: true });
16
+ fs.writeFileSync(tmp, JSON.stringify(state));
17
+ fs.renameSync(tmp, provider.stateFile);
18
+ provider.lastStatePersist = Date.now();
19
+ }
20
+ catch {
21
+ /* best-effort: the profile itself already holds durable cookies */
22
+ }
23
+ }
24
+ /**
25
+ * Re-inject a previously exported state. Durable cookies already live in
26
+ * the profile; this covers session cookies the browser drops on close and
27
+ * localStorage entries, so logins survive window close/reopen.
28
+ */
29
+ export async function restoreState(provider, context) {
30
+ if (provider.stateFile === undefined)
31
+ return;
32
+ let state;
33
+ try {
34
+ state = JSON.parse(fs.readFileSync(provider.stateFile, 'utf8'));
35
+ }
36
+ catch {
37
+ return;
38
+ }
39
+ if (state === null || typeof state !== 'object')
40
+ return;
41
+ const cookies = Array.isArray(state.cookies)
42
+ ? state.cookies.filter((c) => c !== null &&
43
+ typeof c === 'object' &&
44
+ typeof c.name === 'string' &&
45
+ c.value !== undefined &&
46
+ typeof c.domain === 'string')
47
+ : [];
48
+ if (cookies.length > 0) {
49
+ await context.addCookies(cookies.map((c) => ({
50
+ name: c.name,
51
+ value: c.value,
52
+ domain: c.domain,
53
+ path: typeof c.path === 'string' && c.path !== '' ? c.path : '/',
54
+ ...(typeof c.expires === 'number' && c.expires > 0 ? { expires: c.expires } : {}),
55
+ ...(typeof c.httpOnly === 'boolean' ? { httpOnly: c.httpOnly } : {}),
56
+ ...(typeof c.secure === 'boolean' ? { secure: c.secure } : {}),
57
+ ...(typeof c.sameSite === 'string' ? { sameSite: c.sameSite } : {}),
58
+ })));
59
+ }
60
+ const origins = Array.isArray(state.origins)
61
+ ? state.origins.filter((o) => o !== null &&
62
+ typeof o === 'object' &&
63
+ typeof o.origin === 'string' &&
64
+ Array.isArray(o.localStorage) &&
65
+ o.localStorage.length > 0)
66
+ : [];
67
+ if (origins.length > 0) {
68
+ await context.addInitScript(({ rows }) => {
69
+ for (const o of rows) {
70
+ if (location.origin !== o.origin)
71
+ continue;
72
+ for (const kv of o.localStorage) {
73
+ try {
74
+ localStorage.setItem(kv.name, kv.value);
75
+ }
76
+ catch {
77
+ /* quota / privacy mode */
78
+ }
79
+ }
80
+ }
81
+ }, { rows: origins });
82
+ }
83
+ }
@@ -0,0 +1,134 @@
1
+ import type { Locator, Page } from 'playwright-core';
2
+ import type { PlaywrightSession } from './session.ts';
3
+ import type { ActionResult } from './provider.ts';
4
+ import type { LoadState } from './types.ts';
5
+ /** Wrap a Playwright op so an aborted signal rejects while the op keeps draining in the background. */
6
+ export declare function withAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
7
+ /**
8
+ * Post-hover note. A hover proves nothing by itself: the call returning only
9
+ * means the pointer moved. The cheap evidence that content appeared is how the
10
+ * actionable ref count changed, so the note reports it and tells the model
11
+ * what to do when nothing appeared (retarget the owner of the menu, never
12
+ * retry the same hover, never fall back to raw coordinates).
13
+ * @param target - description of what was hovered (a ref, or a quoted label).
14
+ * @param refsBefore - actionable refs before the hover.
15
+ * @param refsAfter - actionable refs in the returned snapshot.
16
+ * @param navigated - whether the hover changed the page URL.
17
+ */
18
+ export declare function hoverNote(target: string, refsBefore: number, refsAfter: number, navigated: boolean): string;
19
+ /** Fast actionability pre-check (visible/enabled/editable) before an action. */
20
+ export declare function assertActionable(_session: PlaywrightSession, locator: Locator, options?: {
21
+ editable?: boolean;
22
+ }): Promise<void>;
23
+ /**
24
+ * Run one page action that may raise a native dialog. Such an action never
25
+ * resolves while the dialog is up, so race it against the dialog appearing
26
+ * and hand the caller the pending state instead (Playwright-MCP pattern).
27
+ */
28
+ export declare function runAction(session: PlaywrightSession, page: Page, action: () => Promise<unknown>): Promise<ActionResult>;
29
+ export declare function navigate(session: PlaywrightSession, url: string, waitUntil: LoadState, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
30
+ url: string;
31
+ title: string;
32
+ nodes: never[];
33
+ totalRefs: number;
34
+ truncated: boolean;
35
+ dialogNote: string;
36
+ }>;
37
+ /**
38
+ * Codex-style click-point resolution. Tries a scroll-alignment ladder
39
+ * (centre, then end, then start). For each alignment: scroll the element
40
+ * into view with a native scrollIntoView, wait for its bounding rect to
41
+ * stop moving, sample the rect centre in-page (sub-pixel, no screenshot
42
+ * measurement), then hit-test that exact point with elementFromPoint — so
43
+ * an overlaying element can never swallow the click silently. Returns the
44
+ * first unobstructed {x, y}, or throws describing what intercepted it.
45
+ */
46
+ export declare function resolveClickPoint(session: PlaywrightSession, locator: Locator, signal: AbortSignal | undefined): Promise<{
47
+ x: number;
48
+ y: number;
49
+ hitDesc: string;
50
+ hitSelf: boolean;
51
+ }>;
52
+ /**
53
+ * Click an element the snapshot gave no ref, addressed by its visible label
54
+ * (e.g. a menu item revealed by browser_hover that is plain text). The
55
+ * label must equal an element's own direct text; the deepest match wins,
56
+ * and the landing note names what was actually clicked plus how many
57
+ * elements shared the label.
58
+ */
59
+ export declare function clickText(session: PlaywrightSession, text: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
60
+ url: string;
61
+ title: string;
62
+ nodes: never[];
63
+ totalRefs: number;
64
+ truncated: boolean;
65
+ dialogNote: string;
66
+ }>;
67
+ /**
68
+ * Shared click core: enforce the URL policy for link targets, run the
69
+ * Codex-style geometry ladder with the Playwright actionability fallback,
70
+ * then report what actually received the click. The label names the target
71
+ * in the landing note; shared is how many elements matched it (0 for refs).
72
+ */
73
+ export declare function clickLocator(session: PlaywrightSession, locator: Locator, signal: AbortSignal | undefined, label: string, shared: number): Promise<import("./types.ts").BrowserSnapshot | {
74
+ url: string;
75
+ title: string;
76
+ nodes: never[];
77
+ totalRefs: number;
78
+ truncated: boolean;
79
+ dialogNote: string;
80
+ }>;
81
+ export declare function clickAt(session: PlaywrightSession, x: number, y: number, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
82
+ url: string;
83
+ title: string;
84
+ nodes: never[];
85
+ totalRefs: number;
86
+ truncated: boolean;
87
+ dialogNote: string;
88
+ }>;
89
+ export declare function clickAtRef(session: PlaywrightSession, ref: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
90
+ url: string;
91
+ title: string;
92
+ nodes: never[];
93
+ totalRefs: number;
94
+ truncated: boolean;
95
+ dialogNote: string;
96
+ }>;
97
+ export declare function fill(session: PlaywrightSession, ref: string, text: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
98
+ /** Read the field back and decide whether the fill landed. */
99
+ export declare function verifyFill(_session: PlaywrightSession, locator: Locator, tag: string, text: string): Promise<boolean>;
100
+ export declare function press(session: PlaywrightSession, ref: string, key: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
101
+ url: string;
102
+ title: string;
103
+ nodes: never[];
104
+ totalRefs: number;
105
+ truncated: boolean;
106
+ dialogNote: string;
107
+ }>;
108
+ /**
109
+ * Hover a trigger the snapshot gave no ref, addressed by its visible label
110
+ * (e.g. a plain span that reveals a menu on mouseenter). The label must
111
+ * equal an element's own direct text; the deepest match wins, and the
112
+ * landing note names what was hovered plus how many elements shared it.
113
+ */
114
+ export declare function hoverText(session: PlaywrightSession, text: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
115
+ /**
116
+ * Shared hover core: rest the pointer on a resolved locator, then report
117
+ * what the actionable ref count did. The label names the target in the
118
+ * landing note; shared is how many elements matched that label (0 when the
119
+ * target came from a ref).
120
+ */
121
+ export declare function hoverLocator(session: PlaywrightSession, locator: Locator, signal: AbortSignal | undefined, label: string, shared: number): Promise<import("./types.ts").BrowserSnapshot>;
122
+ export declare function scroll(session: PlaywrightSession, direction: 'up' | 'down', amount: number, ref: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
123
+ export declare function back(session: PlaywrightSession, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
124
+ export declare function forward(session: PlaywrightSession, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
125
+ export declare function switchTab(session: PlaywrightSession, index: number, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot | {
126
+ url: string;
127
+ title: string;
128
+ nodes: never[];
129
+ totalRefs: number;
130
+ truncated: boolean;
131
+ dialogNote: string;
132
+ }>;
133
+ export declare function openTab(session: PlaywrightSession, url: string, waitUntil: LoadState, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
134
+ export declare function closeTab(session: PlaywrightSession, index: number, signal: AbortSignal | undefined): Promise<void>;