@webhands/core 0.5.0 → 0.6.0
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/README.md +20 -4
- package/dist/errors.d.ts +92 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +100 -0
- package/dist/errors.js.map +1 -1
- package/dist/hand-host.d.ts +198 -5
- package/dist/hand-host.d.ts.map +1 -1
- package/dist/hand-host.js +664 -21
- package/dist/hand-host.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/playwright-attach-transport.d.ts +8 -1
- package/dist/playwright-attach-transport.d.ts.map +1 -1
- package/dist/playwright-attach-transport.js +19 -4
- package/dist/playwright-attach-transport.js.map +1 -1
- package/dist/playwright-launch-transport.d.ts.map +1 -1
- package/dist/playwright-launch-transport.js +13 -4
- package/dist/playwright-launch-transport.js.map +1 -1
- package/dist/profile-location.d.ts +19 -0
- package/dist/profile-location.d.ts.map +1 -1
- package/dist/profile-location.js +21 -0
- package/dist/profile-location.js.map +1 -1
- package/dist/seam.d.ts +501 -7
- package/dist/seam.d.ts.map +1 -1
- package/dist/seam.js +31 -0
- package/dist/seam.js.map +1 -1
- package/dist/session-rpc.d.ts +63 -1
- package/dist/session-rpc.d.ts.map +1 -1
- package/dist/session-rpc.js +174 -11
- package/dist/session-rpc.js.map +1 -1
- package/dist/stub-transport.d.ts.map +1 -1
- package/dist/stub-transport.js +74 -6
- package/dist/stub-transport.js.map +1 -1
- package/dist/test-fixtures/fixture-pages.d.ts.map +1 -1
- package/dist/test-fixtures/fixture-pages.js +994 -0
- package/dist/test-fixtures/fixture-pages.js.map +1 -1
- package/dist/test-fixtures/fixture-server.d.ts.map +1 -1
- package/dist/test-fixtures/fixture-server.js +33 -3
- package/dist/test-fixtures/fixture-server.js.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +134 -1
- package/src/hand-host.ts +797 -21
- package/src/index.ts +20 -1
- package/src/playwright-attach-transport.ts +25 -3
- package/src/playwright-launch-transport.ts +13 -2
- package/src/profile-location.ts +25 -0
- package/src/seam.ts +535 -7
- package/src/session-rpc.ts +276 -14
- package/src/stub-transport.ts +83 -6
- package/src/test-fixtures/fixture-pages.ts +1010 -0
- package/src/test-fixtures/fixture-server.ts +32 -3
package/README.md
CHANGED
|
@@ -83,10 +83,26 @@ This is a **personal-use** tool. Its whole premise is that you drive a browser
|
|
|
83
83
|
[`docs/adr/0002`](docs/adr/0002-real-session-over-fingerprint-spoofing.md)). It is
|
|
84
84
|
deliberately local and single-session by design.
|
|
85
85
|
|
|
86
|
-
- **No login-bypass, no CAPTCHA
|
|
87
|
-
clears any anti-bot challenge in the headed `setup-profile` step.
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
- **No login-bypass, no built-in CAPTCHA solver.** The human does the one-time
|
|
87
|
+
login and clears any anti-bot challenge in the headed `setup-profile` step.
|
|
88
|
+
webhands ships NO captcha solver and NO provider key, and does not bypass
|
|
89
|
+
authentication itself. What changed: the verb surface is now rich enough that it
|
|
90
|
+
no longer STANDS IN THE WAY of a capable agent that brings its OWN key. Such an
|
|
91
|
+
agent can get past a captcha by poking the page with verbs, both families: the
|
|
92
|
+
token-harvest family by reading the sitekey with a frame-aware `query`, `type`ing
|
|
93
|
+
a provider token into the response sink, and firing the callback; the vision/tile
|
|
94
|
+
family with the coordinate `mouse`, the element-clipped `screenshot`, and the
|
|
95
|
+
cross-origin frame read. We do not solve it; we no longer stand in the way. The
|
|
96
|
+
agent supplies its own key and its own logic (or uses a hand, below). webhands
|
|
97
|
+
is capable, not a solver.
|
|
98
|
+
- **Hands are the simpler path (still).** A *hand* is a third-party capability
|
|
99
|
+
module (`iamhuman` today, a future buy-on-amazon hand) that closes over the live
|
|
100
|
+
page and makes the hard thing ONE call. A dumb agent plus a hand still gets there
|
|
101
|
+
in a single call, even though a capable agent can now do the same over several
|
|
102
|
+
verb turns. The two paths coexist: the verb surface is the floor that makes the
|
|
103
|
+
unaided path POSSIBLE; a hand is the ramp that makes it EASY. (A hand is a
|
|
104
|
+
trusted in-process peer, loaded only when you name it in `hands.json`; see
|
|
105
|
+
[`docs/adr/0007`](docs/adr/0007-public-hand-contract-and-explicit-declarative-loading.md).)
|
|
90
106
|
- **No fingerprint-spoofing / anti-detect tricks.** It leans on being a *real*
|
|
91
107
|
browser/profile/IP rather than spoofing. There is no proxy *rotation* or
|
|
92
108
|
anti-detect build here. (A single, user-chosen SOCKS proxy for traffic/DNS
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* has to re-derive paths.
|
|
16
16
|
*/
|
|
17
17
|
/** The closed set of identifiable `core` error conditions. */
|
|
18
|
-
export type ControllerErrorCode = 'missing-browser-binary' | 'missing-stealth-dependency' | 'invalid-proxy' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active';
|
|
18
|
+
export type ControllerErrorCode = 'missing-browser-binary' | 'missing-stealth-dependency' | 'invalid-proxy' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active' | 'cross-origin-frame' | 'screenshot-path-outside-managed-dir' | 'stale-ref';
|
|
19
19
|
/**
|
|
20
20
|
* Base class for every identifiable `core` error. Branch on {@link code}.
|
|
21
21
|
*
|
|
@@ -159,6 +159,97 @@ export declare class SessionAlreadyActiveError extends ControllerError {
|
|
|
159
159
|
cause?: unknown;
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* A frame-scoped `eval` (or any same-origin-frame op) addressed a CROSS-ORIGIN
|
|
164
|
+
* child frame. Page-world JS cannot cross a browser security boundary, so the
|
|
165
|
+
* frame-scoped `eval` reaches the top document and SAME-ORIGIN descendant frames
|
|
166
|
+
* ONLY (the idea's honest-scope note); a cross-origin frame is unreachable BY
|
|
167
|
+
* DESIGN, not a missing feature. We refuse LOUDLY with this typed condition
|
|
168
|
+
* rather than return a silent empty result, because a silent empty would let an
|
|
169
|
+
* agent believe its callback fired / its read succeeded when the security
|
|
170
|
+
* boundary actually blocked it.
|
|
171
|
+
*
|
|
172
|
+
* Cross-origin frame reach is the SEPARATE Tier-4 `frameLocator`/coordinate
|
|
173
|
+
* surface, not this verb; the message points there so the agent is not left
|
|
174
|
+
* guessing. Mirrors the other typed conditions: the CLI maps {@link code} to a
|
|
175
|
+
* message, and {@link isControllerError} narrows it across a bundle boundary.
|
|
176
|
+
*/
|
|
177
|
+
export declare class CrossOriginFrameError extends ControllerError {
|
|
178
|
+
readonly code = "cross-origin-frame";
|
|
179
|
+
/** The frame selector the caller passed (echoed back so it is visible). */
|
|
180
|
+
readonly frame: string;
|
|
181
|
+
/** The cross-origin frame's origin, when known (e.g. `https://hcaptcha.com`). */
|
|
182
|
+
readonly frameOrigin?: string;
|
|
183
|
+
/** The page's own (main-frame) origin the frame had to match. */
|
|
184
|
+
readonly pageOrigin?: string;
|
|
185
|
+
constructor(frame: string, details?: {
|
|
186
|
+
frameOrigin?: string;
|
|
187
|
+
pageOrigin?: string;
|
|
188
|
+
}, message?: string, options?: {
|
|
189
|
+
cause?: unknown;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* A `screenshot --out <path>` override pointed OUTSIDE the managed screenshots
|
|
194
|
+
* dir. webhands MINTS screenshots under one managed directory it owns (under the
|
|
195
|
+
* controller home root, beside `profiles/`); a caller MAY override the output
|
|
196
|
+
* path, but only WITHIN that managed dir (prd `broaden-agent-verb-surface`, R3:
|
|
197
|
+
* "validate it stays under a sane managed dir"). An override that escapes it
|
|
198
|
+
* (an absolute path elsewhere, or a `..` traversal out) is refused LOUDLY with
|
|
199
|
+
* this typed condition rather than silently writing a PNG to an arbitrary
|
|
200
|
+
* filesystem location — the seam returns a path, so an unbounded path would let
|
|
201
|
+
* the verb clobber any file the process can write.
|
|
202
|
+
*
|
|
203
|
+
* Mirrors the other typed conditions: the CLI maps {@link code} to a message,
|
|
204
|
+
* and {@link isControllerError} narrows it across a bundle boundary.
|
|
205
|
+
*/
|
|
206
|
+
export declare class ScreenshotPathError extends ControllerError {
|
|
207
|
+
readonly code = "screenshot-path-outside-managed-dir";
|
|
208
|
+
/** The offending caller-supplied `out` path, echoed back so it is visible. */
|
|
209
|
+
readonly path: string;
|
|
210
|
+
/** The managed screenshots dir the path had to stay under. */
|
|
211
|
+
readonly managedDir: string;
|
|
212
|
+
constructor(path: string, managedDir: string, message?: string, options?: {
|
|
213
|
+
cause?: unknown;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* A durable `query` `ref` (prd `broaden-agent-verb-surface`, R4; finding
|
|
218
|
+
* `query-ref-mint-mechanism-attribute-beats-weakmap`) failed to resolve to
|
|
219
|
+
* EXACTLY ONE element when an action verb (`click`/`type`) tried to act on it.
|
|
220
|
+
* A `ref` is a SHORT-LIVED handle, not a stable identity: between the `query`
|
|
221
|
+
* that minted it and the act, the page may have re-rendered (React keyed-list /
|
|
222
|
+
* Svelte `{#each}` NODE REPLACEMENT), navigated, or cloned a subtree carrying
|
|
223
|
+
* our minted attribute. Two stale shapes, BOTH this one error, never a silent
|
|
224
|
+
* wrong-element action:
|
|
225
|
+
*
|
|
226
|
+
* - resolve-to-ZERO ({@link matched} `=== 0`) — the element was removed or
|
|
227
|
+
* replaced (its minted node is gone, or a reused stable attribute it carried
|
|
228
|
+
* no longer exists). The agent must re-`query` against the fresh DOM.
|
|
229
|
+
* - resolve-to-MANY ({@link matched} `> 1`) — the ref matches more than one
|
|
230
|
+
* element (a framework cloned a subtree carrying our minted attribute, or a
|
|
231
|
+
* reused attribute turned out non-unique after a mutation). We refuse to
|
|
232
|
+
* "pick the first", because that is exactly the silent wrong-element click the
|
|
233
|
+
* ref exists to PREVENT.
|
|
234
|
+
*
|
|
235
|
+
* This is STRICTLY SAFER than re-addressing by a positional `.nth(i)`, which
|
|
236
|
+
* SILENTLY clicks whatever now sits at that index. The agent is told "re-query,
|
|
237
|
+
* the page changed" — its natural loop anyway. Mirrors the other typed
|
|
238
|
+
* conditions: the CLI maps {@link code} to a message, and
|
|
239
|
+
* {@link isControllerError} narrows it across a bundle boundary.
|
|
240
|
+
*/
|
|
241
|
+
export declare class StaleRefError extends ControllerError {
|
|
242
|
+
readonly code = "stale-ref";
|
|
243
|
+
/** The ref locator string that went stale (echoed back so it is visible). */
|
|
244
|
+
readonly ref: string;
|
|
245
|
+
/** How many elements the ref resolved to (0 = removed/replaced, >1 = ambiguous). */
|
|
246
|
+
readonly matched: number;
|
|
247
|
+
/** Which verb tried to act on the stale ref (e.g. `click`, `type`). */
|
|
248
|
+
readonly verb: string;
|
|
249
|
+
constructor(ref: string, matched: number, verb: string, message?: string, options?: {
|
|
250
|
+
cause?: unknown;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
162
253
|
/**
|
|
163
254
|
* Narrow an unknown caught value to a {@link ControllerError}. Prefer this over
|
|
164
255
|
* `instanceof` at package boundaries: it checks the {@link ControllerError.isControllerError}
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAC5B,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,mBAAmB,GACnB,gBAAgB,GAChB,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
CHANGED
|
@@ -165,6 +165,106 @@ export class SessionAlreadyActiveError extends ControllerError {
|
|
|
165
165
|
super(message, options);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* A frame-scoped `eval` (or any same-origin-frame op) addressed a CROSS-ORIGIN
|
|
170
|
+
* child frame. Page-world JS cannot cross a browser security boundary, so the
|
|
171
|
+
* frame-scoped `eval` reaches the top document and SAME-ORIGIN descendant frames
|
|
172
|
+
* ONLY (the idea's honest-scope note); a cross-origin frame is unreachable BY
|
|
173
|
+
* DESIGN, not a missing feature. We refuse LOUDLY with this typed condition
|
|
174
|
+
* rather than return a silent empty result, because a silent empty would let an
|
|
175
|
+
* agent believe its callback fired / its read succeeded when the security
|
|
176
|
+
* boundary actually blocked it.
|
|
177
|
+
*
|
|
178
|
+
* Cross-origin frame reach is the SEPARATE Tier-4 `frameLocator`/coordinate
|
|
179
|
+
* surface, not this verb; the message points there so the agent is not left
|
|
180
|
+
* guessing. Mirrors the other typed conditions: the CLI maps {@link code} to a
|
|
181
|
+
* message, and {@link isControllerError} narrows it across a bundle boundary.
|
|
182
|
+
*/
|
|
183
|
+
export class CrossOriginFrameError extends ControllerError {
|
|
184
|
+
code = 'cross-origin-frame';
|
|
185
|
+
/** The frame selector the caller passed (echoed back so it is visible). */
|
|
186
|
+
frame;
|
|
187
|
+
/** The cross-origin frame's origin, when known (e.g. `https://hcaptcha.com`). */
|
|
188
|
+
frameOrigin;
|
|
189
|
+
/** The page's own (main-frame) origin the frame had to match. */
|
|
190
|
+
pageOrigin;
|
|
191
|
+
constructor(frame, details, message = `The frame ${JSON.stringify(frame)} is CROSS-ORIGIN${details?.frameOrigin !== undefined && details?.pageOrigin !== undefined
|
|
192
|
+
? ` (frame origin ${details.frameOrigin}, page origin ${details.pageOrigin})`
|
|
193
|
+
: ''} and is unreachable from page-world JS. eval --frame reaches the top document and SAME-ORIGIN child frames only; a cross-origin frame is a browser security boundary. Reach cross-origin frames with the Tier-4 frameLocator/coordinate ops instead.`, options) {
|
|
194
|
+
super(message, options);
|
|
195
|
+
this.frame = frame;
|
|
196
|
+
this.frameOrigin = details?.frameOrigin;
|
|
197
|
+
this.pageOrigin = details?.pageOrigin;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A `screenshot --out <path>` override pointed OUTSIDE the managed screenshots
|
|
202
|
+
* dir. webhands MINTS screenshots under one managed directory it owns (under the
|
|
203
|
+
* controller home root, beside `profiles/`); a caller MAY override the output
|
|
204
|
+
* path, but only WITHIN that managed dir (prd `broaden-agent-verb-surface`, R3:
|
|
205
|
+
* "validate it stays under a sane managed dir"). An override that escapes it
|
|
206
|
+
* (an absolute path elsewhere, or a `..` traversal out) is refused LOUDLY with
|
|
207
|
+
* this typed condition rather than silently writing a PNG to an arbitrary
|
|
208
|
+
* filesystem location — the seam returns a path, so an unbounded path would let
|
|
209
|
+
* the verb clobber any file the process can write.
|
|
210
|
+
*
|
|
211
|
+
* Mirrors the other typed conditions: the CLI maps {@link code} to a message,
|
|
212
|
+
* and {@link isControllerError} narrows it across a bundle boundary.
|
|
213
|
+
*/
|
|
214
|
+
export class ScreenshotPathError extends ControllerError {
|
|
215
|
+
code = 'screenshot-path-outside-managed-dir';
|
|
216
|
+
/** The offending caller-supplied `out` path, echoed back so it is visible. */
|
|
217
|
+
path;
|
|
218
|
+
/** The managed screenshots dir the path had to stay under. */
|
|
219
|
+
managedDir;
|
|
220
|
+
constructor(path, managedDir, message = `The screenshot output path ${JSON.stringify(path)} is OUTSIDE the managed screenshots dir (${managedDir}). A caller may override --out only within the managed dir; webhands never writes a screenshot to an arbitrary location.`, options) {
|
|
221
|
+
super(message, options);
|
|
222
|
+
this.path = path;
|
|
223
|
+
this.managedDir = managedDir;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A durable `query` `ref` (prd `broaden-agent-verb-surface`, R4; finding
|
|
228
|
+
* `query-ref-mint-mechanism-attribute-beats-weakmap`) failed to resolve to
|
|
229
|
+
* EXACTLY ONE element when an action verb (`click`/`type`) tried to act on it.
|
|
230
|
+
* A `ref` is a SHORT-LIVED handle, not a stable identity: between the `query`
|
|
231
|
+
* that minted it and the act, the page may have re-rendered (React keyed-list /
|
|
232
|
+
* Svelte `{#each}` NODE REPLACEMENT), navigated, or cloned a subtree carrying
|
|
233
|
+
* our minted attribute. Two stale shapes, BOTH this one error, never a silent
|
|
234
|
+
* wrong-element action:
|
|
235
|
+
*
|
|
236
|
+
* - resolve-to-ZERO ({@link matched} `=== 0`) — the element was removed or
|
|
237
|
+
* replaced (its minted node is gone, or a reused stable attribute it carried
|
|
238
|
+
* no longer exists). The agent must re-`query` against the fresh DOM.
|
|
239
|
+
* - resolve-to-MANY ({@link matched} `> 1`) — the ref matches more than one
|
|
240
|
+
* element (a framework cloned a subtree carrying our minted attribute, or a
|
|
241
|
+
* reused attribute turned out non-unique after a mutation). We refuse to
|
|
242
|
+
* "pick the first", because that is exactly the silent wrong-element click the
|
|
243
|
+
* ref exists to PREVENT.
|
|
244
|
+
*
|
|
245
|
+
* This is STRICTLY SAFER than re-addressing by a positional `.nth(i)`, which
|
|
246
|
+
* SILENTLY clicks whatever now sits at that index. The agent is told "re-query,
|
|
247
|
+
* the page changed" — its natural loop anyway. Mirrors the other typed
|
|
248
|
+
* conditions: the CLI maps {@link code} to a message, and
|
|
249
|
+
* {@link isControllerError} narrows it across a bundle boundary.
|
|
250
|
+
*/
|
|
251
|
+
export class StaleRefError extends ControllerError {
|
|
252
|
+
code = 'stale-ref';
|
|
253
|
+
/** The ref locator string that went stale (echoed back so it is visible). */
|
|
254
|
+
ref;
|
|
255
|
+
/** How many elements the ref resolved to (0 = removed/replaced, >1 = ambiguous). */
|
|
256
|
+
matched;
|
|
257
|
+
/** Which verb tried to act on the stale ref (e.g. `click`, `type`). */
|
|
258
|
+
verb;
|
|
259
|
+
constructor(ref, matched, verb, message = matched === 0
|
|
260
|
+
? `${verb}: the ref ${JSON.stringify(ref)} is STALE — it now matches NO element (the page changed: the element was removed, replaced by a re-render, or the page navigated). Re-run query to get a fresh ref.`
|
|
261
|
+
: `${verb}: the ref ${JSON.stringify(ref)} is AMBIGUOUS — it now matches ${matched} elements (the page changed: a subtree was cloned, or the attribute is no longer unique). Refusing to act on a guessed element; re-run query to get a fresh ref.`, options) {
|
|
262
|
+
super(message, options);
|
|
263
|
+
this.ref = ref;
|
|
264
|
+
this.matched = matched;
|
|
265
|
+
this.verb = verb;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
168
268
|
/**
|
|
169
269
|
* Narrow an unknown caught value to a {@link ControllerError}. Prefer this over
|
|
170
270
|
* `instanceof` at package boundaries: it checks the {@link ControllerError.isControllerError}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/hand-host.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type BrowserContext, type Page } from 'playwright';
|
|
2
|
-
import type { WebHandsPage, WaitCondition } from './seam.js';
|
|
1
|
+
import { type BrowserContext, type Frame, type Locator, type Page } from 'playwright';
|
|
2
|
+
import type { EvalOptions, WebHandsPage, MouseInput, QueryOptions, QueryRow, Screenshot, ScreenshotOptions, WaitCondition } from './seam.js';
|
|
3
3
|
/**
|
|
4
4
|
* The hand-host primitive (Phase 1 of the "hands" prd,
|
|
5
5
|
* `work/prds/tasked/hands-pluggable-page-capabilities.md`).
|
|
@@ -57,6 +57,17 @@ export interface HandContext {
|
|
|
57
57
|
readonly pwPage: Page;
|
|
58
58
|
readonly context: BrowserContext;
|
|
59
59
|
readonly ensureOpen: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* The managed SCREENSHOTS directory the `screenshot` verb mints PNGs under
|
|
62
|
+
* (Tier-4, prd `broaden-agent-verb-surface`, R3). Resolved by each transport
|
|
63
|
+
* from its home root (`<homeRoot>/screenshots`, beside `profiles/`) via
|
|
64
|
+
* {@link resolveScreenshotsDir}, so the same `root`/`WEBHANDS_HOME` override
|
|
65
|
+
* that isolates profiles in a test isolates screenshots too. The verb creates
|
|
66
|
+
* it lazily on first write and validates any caller `out` override stays under
|
|
67
|
+
* it ({@link ScreenshotPathError}). Carried HERE (not on the seam) so no path
|
|
68
|
+
* policy leaks into the public {@link WebHandsPage} surface (ADR-0003).
|
|
69
|
+
*/
|
|
70
|
+
readonly screenshotsDir: string;
|
|
60
71
|
}
|
|
61
72
|
/**
|
|
62
73
|
* What a hand contributes once given its {@link HandContext}: a set of named
|
|
@@ -117,7 +128,15 @@ export declare function composePage(ctx: HandContext, hands: readonly Hand[]): C
|
|
|
117
128
|
export declare const navigationHand: Hand;
|
|
118
129
|
/** The `snapshot` verb: the token-cheap a11y view, or `--full` raw DOM. */
|
|
119
130
|
export declare const snapshotHand: Hand;
|
|
120
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* The `click` + `type` verbs: page interaction by raw locator (ADR-0004).
|
|
133
|
+
*
|
|
134
|
+
* With `{byRef: true}` the target is a durable `query` {@link QueryRow.ref}: it
|
|
135
|
+
* is resolved through the SAME {@link resolveLocator} but FIRST asserted to match
|
|
136
|
+
* EXACTLY ONE element ({@link assertRefResolvesToOne}), so a stale (zero) or
|
|
137
|
+
* ambiguous (many) ref fails LOUD with a {@link StaleRefError} instead of
|
|
138
|
+
* silently acting on the wrong element — the safety the durable ref exists for.
|
|
139
|
+
*/
|
|
121
140
|
export declare const interactionHand: Hand;
|
|
122
141
|
/** The `eval` escape hatch: run a JS EXPRESSION in the page, return by value. */
|
|
123
142
|
export declare const evalHand: Hand;
|
|
@@ -130,7 +149,57 @@ export declare const waitHand: Hand;
|
|
|
130
149
|
*/
|
|
131
150
|
export declare const cookiesHand: Hand;
|
|
132
151
|
/**
|
|
133
|
-
*
|
|
152
|
+
* The Tier-1 read verbs (prd `broaden-agent-verb-surface`, R2): the `query`
|
|
153
|
+
* extraction verb plus the thin state shorthands `count` / `exists` /
|
|
154
|
+
* `isVisible` / `getAttribute`. All five address element(s) by the SAME raw
|
|
155
|
+
* Playwright locator expression the other verbs use, resolved through the ONE
|
|
156
|
+
* existing {@link resolveLocator} (so a `frameLocator(...)` same-origin frame
|
|
157
|
+
* hop in the string Just Works, and there is no parallel addressing scheme —
|
|
158
|
+
* R1). They are pure READS: no page mutation.
|
|
159
|
+
*
|
|
160
|
+
* `query` returns one row per match carrying EXACTLY the requested fields (R2);
|
|
161
|
+
* the state verbs are computed over the same machinery (see {@link queryRows}
|
|
162
|
+
* and the per-verb bodies). Read values cross by structured clone, the same
|
|
163
|
+
* contract as `eval` (ADR-0003).
|
|
164
|
+
*/
|
|
165
|
+
export declare const queryHand: Hand;
|
|
166
|
+
/**
|
|
167
|
+
* The Tier-2 rich INPUT verbs (prd `broaden-agent-verb-surface`, stories 8-12):
|
|
168
|
+
* `press` / `hover` / `select` / `scroll` / `drag`. These lift page-level
|
|
169
|
+
* Playwright actions a hand already has on `pwPage` (`keyboard.press`,
|
|
170
|
+
* `hover`, `selectOption`, `mouse.wheel`/`scrollIntoViewIfNeeded`, `dragTo`) up
|
|
171
|
+
* to the agent verb seam so a seam-only agent can drive a browser game or a
|
|
172
|
+
* richer form, not just `click`/`type`.
|
|
173
|
+
*
|
|
174
|
+
* Every locator-addressing form resolves through the SAME single
|
|
175
|
+
* {@link resolveLocator} the other verbs use (so a same-origin `frameLocator(...)`
|
|
176
|
+
* hop in the string Just Works — no parallel addressing scheme, R1). Keys are
|
|
177
|
+
* strings, offsets are numbers, locators are strings: nothing Playwright-shaped
|
|
178
|
+
* crosses the seam (ADR-0003).
|
|
179
|
+
*/
|
|
180
|
+
export declare const inputHand: Hand;
|
|
181
|
+
/**
|
|
182
|
+
* The Tier-4 COORDINATE + SCREENSHOT hand (prd `broaden-agent-verb-surface`,
|
|
183
|
+
* R3; stories 17-19): the `mouse` coordinate-input verb and the `screenshot`
|
|
184
|
+
* path-returning verb, the look-then-click pair that lets a seam-only agent
|
|
185
|
+
* handle the VISION/TILE captcha family and any visual task.
|
|
186
|
+
*
|
|
187
|
+
* The seam stays ADR-0003-clean (as amended by the Tier-4 ADR) by passing ONLY
|
|
188
|
+
* numbers + a string enum (`mouse`) and returning ONLY a file PATH + dimensions
|
|
189
|
+
* (`screenshot`): NO image bytes and NO Playwright/CDP type cross the seam.
|
|
190
|
+
*
|
|
191
|
+
* - `mouse` drives Playwright `page.mouse` at VIEWPORT CSS-pixels (NOT OS-level
|
|
192
|
+
* input). A VIEWPORT screenshot's pixels map directly to these coordinates
|
|
193
|
+
* (the look-then-click contract); a FULL-PAGE shot does not.
|
|
194
|
+
* - `screenshot` MINTS a PNG under the managed {@link HandContext.screenshotsDir}
|
|
195
|
+
* and returns its path. The `element` scope clips to a locator (resolved
|
|
196
|
+
* through the SAME {@link resolveLocator}, so a cross-origin `frameLocator(...)`
|
|
197
|
+
* widget shot Just Works). A caller `out` override is validated to stay under
|
|
198
|
+
* the managed dir ({@link ScreenshotPathError}).
|
|
199
|
+
*/
|
|
200
|
+
export declare const coordinateHand: Hand;
|
|
201
|
+
/**
|
|
202
|
+
* webhands' built-in verbs as built-in hands, in composition order. Both
|
|
134
203
|
* Playwright transports compose THIS exact set, so the verb surface is
|
|
135
204
|
* identical across launch and attach (the only legitimate difference is the
|
|
136
205
|
* per-transport SESSION LIFECYCLE, which is not a hand's concern).
|
|
@@ -181,6 +250,53 @@ export declare function composeWithHands(ctx: HandContext, extraHands: readonly
|
|
|
181
250
|
* verb behaviour stays identical (no parallel second implementation).
|
|
182
251
|
*/
|
|
183
252
|
export declare function waitFor(page: Page, condition: WaitCondition): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* Run the `eval` verb against a Playwright page (PRD story 9; frame scope from
|
|
255
|
+
* prd `broaden-agent-verb-surface`, Tier-3), shared by both Playwright
|
|
256
|
+
* transports (via the built-in eval hand) so the verb behaves identically (no
|
|
257
|
+
* parallel second implementation).
|
|
258
|
+
*
|
|
259
|
+
* With no `frame`, this is the top-document escape hatch: Playwright's
|
|
260
|
+
* `evaluate` IS the seam's serialization contract (see {@link WebHandsPage.eval}):
|
|
261
|
+
* it passes a string as an expression, awaits a returned Promise, and
|
|
262
|
+
* structurally clones the result out of the page by VALUE. That clone is richer
|
|
263
|
+
* than JSON: it preserves NaN/Infinity/BigInt and circular structures (back-refs
|
|
264
|
+
* become a `[Circular]` marker), yields `undefined` for functions/symbols, and
|
|
265
|
+
* returns an opaque preview string for a live host object (a DOM node never
|
|
266
|
+
* crosses the process boundary). A page-side throw rejects. We pass it straight
|
|
267
|
+
* through rather than re-encode it: wrapping the value in a transport-specific
|
|
268
|
+
* envelope would invent a dialect the seam deliberately avoids. The thrown error
|
|
269
|
+
* is a plain `Error`, so no Playwright/CDP type leaks across the seam (ADR-0003).
|
|
270
|
+
*
|
|
271
|
+
* With a `frame` selector, the SAME structured-clone contract holds, but the
|
|
272
|
+
* expression runs in the named SAME-ORIGIN child frame (resolved through the
|
|
273
|
+
* single {@link resolveSameOriginFrame}, which reuses the same
|
|
274
|
+
* {@link resolveLocator} the locator-taking verbs use). A cross-origin frame
|
|
275
|
+
* REJECTS with a typed {@link CrossOriginFrameError} (see that resolver).
|
|
276
|
+
*/
|
|
277
|
+
export declare function evalExpression(page: Page, expression: string, options?: EvalOptions): Promise<unknown>;
|
|
278
|
+
/**
|
|
279
|
+
* Resolve a `frame` SELECTOR string to a live, SAME-ORIGIN Playwright
|
|
280
|
+
* {@link Frame} for a frame-scoped `eval` (prd `broaden-agent-verb-surface`,
|
|
281
|
+
* Tier-3, R1). This is the SINGLE frame resolver: it reuses the very same
|
|
282
|
+
* {@link resolveLocator} the locator-taking verbs use (a `frameLocator(...)`
|
|
283
|
+
* over the selector), then walks the iframe element handle to its content
|
|
284
|
+
* frame — there is no parallel frame-addressing scheme.
|
|
285
|
+
*
|
|
286
|
+
* SAME-ORIGIN ONLY, enforced LOUD. Playwright will happily `evaluate` inside a
|
|
287
|
+
* CROSS-ORIGIN OOPIF (it attaches out-of-band), so a cross-origin frame would
|
|
288
|
+
* NOT throw on its own — it would silently succeed, which is exactly the
|
|
289
|
+
* contract violation this verb forbids (page-world JS cannot cross a security
|
|
290
|
+
* boundary; the seam is same-origin only). So we DETECT cross-origin by
|
|
291
|
+
* comparing the frame's origin to the page's main-frame origin and reject with a
|
|
292
|
+
* typed {@link CrossOriginFrameError} when they differ, never returning a frame
|
|
293
|
+
* the page world could not legitimately reach.
|
|
294
|
+
*
|
|
295
|
+
* Failure modes are loud/typed: a selector that matches NO iframe element
|
|
296
|
+
* rejects (the locator resolves nothing); a matched frame with no content frame
|
|
297
|
+
* rejects; a cross-origin frame rejects with {@link CrossOriginFrameError}.
|
|
298
|
+
*/
|
|
299
|
+
export declare function resolveSameOriginFrame(page: Page, selector: string): Promise<Frame>;
|
|
184
300
|
/**
|
|
185
301
|
* Resolve a raw Playwright locator EXPRESSION (ADR-0004) against the page. The
|
|
186
302
|
* verb surface passes locator expressions like `getByRole('button', …)`; we
|
|
@@ -191,7 +307,7 @@ export declare function waitFor(page: Page, condition: WaitCondition): Promise<v
|
|
|
191
307
|
* One resolution path for both transports (via the built-in interaction/wait
|
|
192
308
|
* hands), so there is no parallel addressing scheme.
|
|
193
309
|
*/
|
|
194
|
-
export declare function resolveLocator(page: Page, expression: string):
|
|
310
|
+
export declare function resolveLocator(page: Page, expression: string): Locator;
|
|
195
311
|
/**
|
|
196
312
|
* Run the `click` verb against a Playwright page (PRD story 8), shared by both
|
|
197
313
|
* Playwright transports (via the built-in interaction hand) so the verb behaves
|
|
@@ -212,6 +328,83 @@ export declare function resolveLocator(page: Page, expression: string): import("
|
|
|
212
328
|
* bad locator) surfaces its timeout quickly instead of hanging the dispatch on
|
|
213
329
|
* Playwright's 30s default — the dispatch escape is for elements that EXIST but
|
|
214
330
|
* are not actionable (hidden custom inputs), not for absent ones.
|
|
331
|
+
*
|
|
332
|
+
* The happy-path click passes `noWaitAfter: true` on purpose. Playwright's
|
|
333
|
+
* `Locator.click()` normally clicks AND THEN auto-waits for any navigation the
|
|
334
|
+
* click scheduled to finish, and that post-click wait counts against the same
|
|
335
|
+
* timeout. A real submit button whose navigation takes longer than
|
|
336
|
+
* {@link NORMAL_CLICK_TIMEOUT_MS} would therefore have its (already-performed)
|
|
337
|
+
* click reported as a `TimeoutError` and be wrongly routed to the dispatch
|
|
338
|
+
* escape, which then re-clicks a page that is already navigating away. We only
|
|
339
|
+
* want the short budget to measure ACTIONABILITY (can we click it?), not how
|
|
340
|
+
* long the resulting navigation takes — `noWaitAfter` returns as soon as the
|
|
341
|
+
* click is performed, so a slow-but-successful submit no longer trips the
|
|
342
|
+
* fallback. A genuinely non-actionable hidden input still cannot be clicked
|
|
343
|
+
* within the budget and still falls through to `dispatchEvent` as before.
|
|
215
344
|
*/
|
|
216
345
|
export declare function clickLocator(page: Page, expression: string): Promise<void>;
|
|
346
|
+
/**
|
|
347
|
+
* Run the `query` verb (prd `broaden-agent-verb-surface`, R2) against a
|
|
348
|
+
* Playwright page: resolve the locator EXPRESSION through the SINGLE existing
|
|
349
|
+
* {@link resolveLocator} (so a same-origin `frameLocator(...)` hop in the string
|
|
350
|
+
* Just Works), then return ONE ROW PER MATCH carrying EXACTLY the requested
|
|
351
|
+
* fields and nothing else.
|
|
352
|
+
*
|
|
353
|
+
* The split is LOUD and never auto-detected:
|
|
354
|
+
* - `attrs[name]` is the element's `getAttribute(name)` (the markup value;
|
|
355
|
+
* `null` if absent).
|
|
356
|
+
* - `props[name]` is the live `el[name]` JS property (runtime state), read in
|
|
357
|
+
* one page-world `evaluate` over the element so the value is structurally
|
|
358
|
+
* cloned out by VALUE — the SAME serialization contract `eval` documents
|
|
359
|
+
* (ADR-0003: no Playwright/CDP type leak; richer than JSON).
|
|
360
|
+
* - `pw.visible` / `pw.bbox` are the closed Playwright-locator extras
|
|
361
|
+
* (`isVisible()` / `boundingBox()`), the only facts not expressible as an
|
|
362
|
+
* attribute or a property. `bbox` is in VIEWPORT CSS-pixels.
|
|
363
|
+
*
|
|
364
|
+
* `limit` bounds the row count. With no fields requested every row is an empty
|
|
365
|
+
* object (the caller asked for nothing; R2). Each row is built independently so
|
|
366
|
+
* a per-element read failure is the page's own throw, surfaced faithfully like
|
|
367
|
+
* `eval` (no silent swallow).
|
|
368
|
+
*/
|
|
369
|
+
export declare function queryRows(page: Page, expression: string, options?: QueryOptions): Promise<QueryRow[]>;
|
|
370
|
+
/**
|
|
371
|
+
* Resolve a durable `query` `ref` and assert it matches EXACTLY ONE element,
|
|
372
|
+
* else throw a typed {@link StaleRefError} (resolve-to-ZERO = removed/replaced;
|
|
373
|
+
* resolve-to-MANY = a cloned subtree / non-unique attribute). The loud-stale
|
|
374
|
+
* guard `click`/`type` run BEFORE acting when `{byRef: true}`, so a stale or
|
|
375
|
+
* ambiguous ref NEVER silently acts on the wrong element (the safety a ref has
|
|
376
|
+
* over a positional `.nth(i)`). Resolved through the SAME {@link resolveLocator}
|
|
377
|
+
* the verbs already use — no parallel addressing path.
|
|
378
|
+
*/
|
|
379
|
+
export declare function assertRefResolvesToOne(page: Page, ref: string, verb: string): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Run the `mouse` verb (prd `broaden-agent-verb-surface`, Tier-4, R3) against a
|
|
382
|
+
* Playwright page: drive `page.mouse` at the given VIEWPORT CSS-pixel
|
|
383
|
+
* coordinate. Viewport-relative, NOT OS-level input — the same coordinate frame
|
|
384
|
+
* a VIEWPORT `screenshot` is captured in, so a pixel an agent saw maps directly
|
|
385
|
+
* to the click. Shared by both transports (via the coordinate hand) so the verb
|
|
386
|
+
* behaves identically. Plain numbers + a string enum only (ADR-0003 as amended).
|
|
387
|
+
*/
|
|
388
|
+
export declare function doMouse(page: Page, input: MouseInput): Promise<void>;
|
|
389
|
+
/**
|
|
390
|
+
* Run the `screenshot` verb (prd `broaden-agent-verb-surface`, Tier-4, R3;
|
|
391
|
+
* stories 17-19) against a Playwright page: capture the requested SCOPE to a PNG
|
|
392
|
+
* FILE under the managed `screenshotsDir` and return `{path, width, height}` —
|
|
393
|
+
* NEVER image bytes (the load-bearing ADR-0003-as-amended choice). Shared by
|
|
394
|
+
* both transports (via the coordinate hand).
|
|
395
|
+
*
|
|
396
|
+
* Scopes:
|
|
397
|
+
* - `viewport` (default) — the visible viewport, COORDINATE-MATCHED to `mouse`.
|
|
398
|
+
* - `full` — the whole scrollable page (`fullPage: true`), NOT coordinate-matched.
|
|
399
|
+
* - `element` — clipped to the locator's element (REQUIRED; resolved through the
|
|
400
|
+
* SAME {@link resolveLocator}, so a `frameLocator(...)` frame widget works even
|
|
401
|
+
* cross-origin). A missing locator for `element`, or a stray locator on a
|
|
402
|
+
* non-`element` scope, is a LOUD validation error (mirrors `wait`).
|
|
403
|
+
*
|
|
404
|
+
* The PNG is written by Playwright to a path webhands MINTS under the managed
|
|
405
|
+
* dir (or a caller `out` override VALIDATED to stay under it, else
|
|
406
|
+
* {@link ScreenshotPathError}). We read the PNG's IHDR for the real pixel
|
|
407
|
+
* dimensions (so the number is the image's, not an assumed viewport size).
|
|
408
|
+
*/
|
|
409
|
+
export declare function takeScreenshot(page: Page, screenshotsDir: string, options?: ScreenshotOptions): Promise<Screenshot>;
|
|
217
410
|
//# sourceMappingURL=hand-host.d.ts.map
|
package/dist/hand-host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hand-host.d.ts","sourceRoot":"","sources":["../src/hand-host.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"hand-host.d.ts","sourceRoot":"","sources":["../src/hand-host.ts"],"names":[],"mappings":"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"}
|