@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.
Files changed (53) hide show
  1. package/README.md +20 -4
  2. package/dist/errors.d.ts +92 -1
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +100 -0
  5. package/dist/errors.js.map +1 -1
  6. package/dist/hand-host.d.ts +198 -5
  7. package/dist/hand-host.d.ts.map +1 -1
  8. package/dist/hand-host.js +664 -21
  9. package/dist/hand-host.js.map +1 -1
  10. package/dist/index.d.ts +4 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +3 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/playwright-attach-transport.d.ts +8 -1
  15. package/dist/playwright-attach-transport.d.ts.map +1 -1
  16. package/dist/playwright-attach-transport.js +19 -4
  17. package/dist/playwright-attach-transport.js.map +1 -1
  18. package/dist/playwright-launch-transport.d.ts.map +1 -1
  19. package/dist/playwright-launch-transport.js +13 -4
  20. package/dist/playwright-launch-transport.js.map +1 -1
  21. package/dist/profile-location.d.ts +19 -0
  22. package/dist/profile-location.d.ts.map +1 -1
  23. package/dist/profile-location.js +21 -0
  24. package/dist/profile-location.js.map +1 -1
  25. package/dist/seam.d.ts +501 -7
  26. package/dist/seam.d.ts.map +1 -1
  27. package/dist/seam.js +31 -0
  28. package/dist/seam.js.map +1 -1
  29. package/dist/session-rpc.d.ts +63 -1
  30. package/dist/session-rpc.d.ts.map +1 -1
  31. package/dist/session-rpc.js +174 -11
  32. package/dist/session-rpc.js.map +1 -1
  33. package/dist/stub-transport.d.ts.map +1 -1
  34. package/dist/stub-transport.js +74 -6
  35. package/dist/stub-transport.js.map +1 -1
  36. package/dist/test-fixtures/fixture-pages.d.ts.map +1 -1
  37. package/dist/test-fixtures/fixture-pages.js +994 -0
  38. package/dist/test-fixtures/fixture-pages.js.map +1 -1
  39. package/dist/test-fixtures/fixture-server.d.ts.map +1 -1
  40. package/dist/test-fixtures/fixture-server.js +33 -3
  41. package/dist/test-fixtures/fixture-server.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/errors.ts +134 -1
  44. package/src/hand-host.ts +797 -21
  45. package/src/index.ts +20 -1
  46. package/src/playwright-attach-transport.ts +25 -3
  47. package/src/playwright-launch-transport.ts +13 -2
  48. package/src/profile-location.ts +25 -0
  49. package/src/seam.ts +535 -7
  50. package/src/session-rpc.ts +276 -14
  51. package/src/stub-transport.ts +83 -6
  52. package/src/test-fixtures/fixture-pages.ts +1010 -0
  53. package/src/test-fixtures/fixture-server.ts +32 -3
package/dist/seam.d.ts CHANGED
@@ -65,6 +65,36 @@ export interface Cookie {
65
65
  readonly secure?: boolean;
66
66
  readonly sameSite?: 'Strict' | 'Lax' | 'None';
67
67
  }
68
+ /**
69
+ * Options for the {@link WebHandsPage.eval} verb.
70
+ *
71
+ * This is an OPTIONS OBJECT, not a positional argument, on purpose (R1, the
72
+ * reversibility invariant): the optional `frame` qualifier is an ADDITION to
73
+ * this object, so a call passing no options keeps `eval`'s today behaviour
74
+ * unchanged. `eval` is the ONE verb that carries a `frame?` qualifier, because
75
+ * it runs page-world JS and CANNOT carry a `frameLocator(...)` expression the
76
+ * way the locator-taking verbs do (the spike confirmed `ReferenceError`); the
77
+ * other verbs reach a same-origin frame through a `frameLocator(...)` hop in
78
+ * their locator string instead (R1).
79
+ */
80
+ export interface EvalOptions {
81
+ /**
82
+ * A transport-neutral SELECTOR for a SAME-ORIGIN child frame to evaluate the
83
+ * expression in — a CSS selector for the `<iframe>` element (e.g.
84
+ * `#main-iframe`), the form the single frame resolver understands. NEVER a
85
+ * Playwright `Frame` handle (no Playwright type crosses the seam, ADR-0003).
86
+ *
87
+ * Omitted == today's top-document `eval` (backward compatible). When given,
88
+ * the expression runs in the named frame and its result crosses the seam by
89
+ * the SAME structured-clone contract `eval` already has.
90
+ *
91
+ * SAME-ORIGIN ONLY: a cross-origin frame is a browser security boundary
92
+ * page-world JS cannot cross, so a selector that resolves to a CROSS-ORIGIN
93
+ * frame fails LOUD with a typed error (never a silent empty); cross-origin
94
+ * reach is the separate Tier-4 frameLocator/coordinate surface.
95
+ */
96
+ readonly frame?: string;
97
+ }
68
98
  /** What to wait for in the {@link WebHandsPage.wait} verb. */
69
99
  export type WaitCondition = {
70
100
  readonly kind: 'timeout';
@@ -75,6 +105,153 @@ export type WaitCondition = {
75
105
  } | {
76
106
  readonly kind: 'navigation';
77
107
  };
108
+ /**
109
+ * Which native `<select>` option the {@link WebHandsPage.select} verb chooses
110
+ * (prd `broaden-agent-verb-surface`, Tier-2). EXACTLY ONE of `value` / `label`,
111
+ * a discriminated union so the mutual exclusion is impossible to violate at the
112
+ * seam (the CLI mirrors it with `wait`-style loud validation, R5):
113
+ *
114
+ * - `value` — match the option's `value` attribute (`<option value="v">`).
115
+ * - `label` — match the option's VISIBLE label (its text), what a human reads.
116
+ *
117
+ * Plain strings only, so nothing Playwright-shaped crosses the seam (ADR-0003).
118
+ */
119
+ export type SelectChoice = {
120
+ readonly value: string;
121
+ } | {
122
+ readonly label: string;
123
+ };
124
+ /**
125
+ * Where the {@link WebHandsPage.scroll} verb scrolls (prd
126
+ * `broaden-agent-verb-surface`, Tier-2). EXACTLY ONE of `to` / `by`, a
127
+ * discriminated union mirroring `wait`'s mutually-exclusive forms:
128
+ *
129
+ * - `to` — bring the element a locator EXPRESSION addresses into view
130
+ * (`scrollIntoViewIfNeeded`); reach an off-viewport control.
131
+ * - `by` — scroll the page by a pixel delta (`mouse.wheel`), `dx`/`dy` in
132
+ * CSS pixels (positive `dy` scrolls DOWN, the wheel convention).
133
+ *
134
+ * `to` carries a {@link LocatorString}; `by` carries plain numbers — no
135
+ * Playwright type crosses the seam (ADR-0003).
136
+ */
137
+ export type ScrollTarget = {
138
+ readonly to: LocatorString;
139
+ } | {
140
+ readonly by: {
141
+ readonly dx: number;
142
+ readonly dy: number;
143
+ };
144
+ };
145
+ /**
146
+ * Which mouse button the {@link WebHandsPage.mouse} verb uses (prd
147
+ * `broaden-agent-verb-surface`, Tier-4, R3; story 17). Plain string enum, the
148
+ * Playwright `page.mouse` button vocabulary, so nothing Playwright-shaped
149
+ * crosses the seam (ADR-0003 as amended by the Tier-4 ADR).
150
+ */
151
+ export type MouseButton = 'left' | 'right' | 'middle';
152
+ /**
153
+ * What the {@link WebHandsPage.mouse} verb does at the given coordinate (prd
154
+ * `broaden-agent-verb-surface`, Tier-4, R3):
155
+ *
156
+ * - `'click'` — a full press-and-release at `(x, y)` (`mouse.click`).
157
+ * - `'move'` — move the pointer to `(x, y)` without pressing (`mouse.move`),
158
+ * e.g. to trigger a hover affordance at a raw coordinate.
159
+ * - `'down'` / `'up'` — press / release the button at the current position
160
+ * (`mouse.down` / `mouse.up`), the two halves of a manual drag.
161
+ */
162
+ export type MouseAction = 'click' | 'move' | 'down' | 'up';
163
+ /**
164
+ * A coordinate mouse input (prd `broaden-agent-verb-surface`, Tier-4, R3, story
165
+ * 17). The coordinate-input counterpart to the locator-addressing `click`, for
166
+ * the VISION/TILE captcha family and any task that must act at a raw pixel an
167
+ * agent SAW in a screenshot rather than at a DOM element.
168
+ *
169
+ * COORDINATE FRAME (load-bearing). `x`/`y` are VIEWPORT CSS-pixels (the
170
+ * Playwright `page.mouse` frame), NOT OS-level screen coordinates (webhands
171
+ * never injects OS input). A pixel `(x, y)` in a VIEWPORT {@link Screenshot}
172
+ * maps DIRECTLY to a `mouse` click `(x, y)` — that is the look-then-click
173
+ * contract the agent relies on. A FULL-PAGE screenshot is NOT coordinate-matched
174
+ * (it includes off-viewport content), so its pixels do not map to `mouse`
175
+ * coordinates (see {@link ScreenshotScope}).
176
+ *
177
+ * Plain numbers + a string enum only, so nothing Playwright-shaped crosses the
178
+ * seam (ADR-0003 as amended by the Tier-4 ADR).
179
+ */
180
+ export interface MouseInput {
181
+ /** What to do at the coordinate (click / move / down / up). */
182
+ readonly action: MouseAction;
183
+ /** Viewport CSS-pixel X (left-relative), the `page.mouse` frame. */
184
+ readonly x: number;
185
+ /** Viewport CSS-pixel Y (top-relative), the `page.mouse` frame. */
186
+ readonly y: number;
187
+ /** Which button for `click`/`down`/`up`. Defaults to `'left'`. */
188
+ readonly button?: MouseButton;
189
+ }
190
+ /**
191
+ * Which region a {@link WebHandsPage.screenshot} captures (prd
192
+ * `broaden-agent-verb-surface`, Tier-4, R3; stories 17-19):
193
+ *
194
+ * - `'viewport'` — the DEFAULT: exactly the visible viewport. Its pixels are
195
+ * COORDINATE-MATCHED to the `mouse` verb (a pixel at `(x, y)` is the `mouse`
196
+ * click `(x, y)`), so it is the shot the look-then-click loop uses.
197
+ * - `'full'` — the whole scrollable page (`fullPage`), for READING scrolled-out
198
+ * content. It is NOT coordinate-matched (it includes off-viewport content), so
199
+ * its pixels must NOT be fed back as `mouse` coordinates.
200
+ * - `'element'` — clipped to the element a locator addresses (just the captcha
201
+ * widget, ideal for focusing a vision model). REQUIRES a
202
+ * {@link ScreenshotOptions.locator}; absent, the verb rejects LOUD (like
203
+ * `wait`'s mutually-exclusive validation).
204
+ */
205
+ export type ScreenshotScope = 'viewport' | 'full' | 'element';
206
+ /**
207
+ * Options for the {@link WebHandsPage.screenshot} verb (prd
208
+ * `broaden-agent-verb-surface`, Tier-4, R3; R5). An OPTIONS OBJECT so future
209
+ * fields stay additive (R1).
210
+ *
211
+ * The seam stays ADR-0003-clean (as amended by the Tier-4 ADR): the verb takes
212
+ * STRINGS + an enum and returns a file PATH — NEVER image bytes.
213
+ */
214
+ export interface ScreenshotOptions {
215
+ /**
216
+ * Which region to capture. Defaults to `'viewport'` (the coordinate-matched
217
+ * shot the `mouse` loop uses). See {@link ScreenshotScope}.
218
+ */
219
+ readonly scope?: ScreenshotScope;
220
+ /**
221
+ * The element to clip to for `scope: 'element'`, a raw Playwright locator
222
+ * EXPRESSION resolved through the SAME resolver the other verbs use (so a
223
+ * `frameLocator(...)` hop reaches a frame widget). REQUIRED for `'element'`
224
+ * and rejected (loud, like `wait`) for the other scopes.
225
+ */
226
+ readonly locator?: LocatorString;
227
+ /**
228
+ * Caller override for the output PNG path. When omitted, webhands MINTS a
229
+ * unique path under its managed screenshots dir. When given, it is VALIDATED
230
+ * to stay UNDER that managed dir (a path that escapes it is rejected with a
231
+ * typed error), so the verb never writes to an arbitrary filesystem location.
232
+ * A plain string — no bytes cross the seam.
233
+ */
234
+ readonly out?: string;
235
+ }
236
+ /**
237
+ * The result of a {@link WebHandsPage.screenshot}: the file PATH webhands wrote
238
+ * the PNG to, plus its pixel dimensions (prd `broaden-agent-verb-surface`,
239
+ * Tier-4, R3, story 19).
240
+ *
241
+ * `path` is a plain STRING — the load-bearing ADR-0003 (as amended) choice: a
242
+ * path, not image bytes, crosses the seam, so the seam stays string/number-typed
243
+ * and the agent reads/attaches the file itself. `width`/`height` are the PNG's
244
+ * pixel dimensions, so an agent knows the coordinate space of a VIEWPORT shot
245
+ * before it maps a pixel to a `mouse` click.
246
+ */
247
+ export interface Screenshot {
248
+ /** The filesystem PATH of the written PNG (a string; never bytes). */
249
+ readonly path: string;
250
+ /** The PNG's pixel width. */
251
+ readonly width: number;
252
+ /** The PNG's pixel height. */
253
+ readonly height: number;
254
+ }
78
255
  /**
79
256
  * Which page view a {@link Snapshot} carries.
80
257
  *
@@ -87,7 +264,16 @@ export type WaitCondition = {
87
264
  * `needsAnswers` Q3): default is the accessibility view, `--full` is raw DOM.
88
265
  */
89
266
  export type SnapshotView = 'accessibility' | 'full';
90
- /** Options for the {@link WebHandsPage.snapshot} verb. */
267
+ /**
268
+ * Options for the {@link WebHandsPage.snapshot} verb.
269
+ *
270
+ * `full` is the ONLY recognised key. Unknown keys are REJECTED (not silently
271
+ * ignored) by {@link validateSnapshotOptions}, which every entry point calls:
272
+ * passing `{view: 'full'}` (a natural mistake, because the RESULT carries a
273
+ * {@link SnapshotView} `view` field) throws a clear error instead of silently
274
+ * returning the wrong view. There is no `view` option; `view` is a RESULT
275
+ * field, set by the verb from `full`.
276
+ */
91
277
  export interface SnapshotOptions {
92
278
  /**
93
279
  * When `true`, return the raw DOM (`view: 'full'`) instead of the default
@@ -95,6 +281,21 @@ export interface SnapshotOptions {
95
281
  */
96
282
  readonly full?: boolean;
97
283
  }
284
+ /**
285
+ * Validate a {@link SnapshotOptions} value at a verb entry point, the SINGLE
286
+ * source of truth shared by the in-process host and the RPC server dispatch so
287
+ * neither path can silently drop a misspelled option.
288
+ *
289
+ * Accepts `undefined`, `{}`, and `{full: boolean}`. REJECTS any object carrying
290
+ * a key other than `full`, and a non-boolean `full`, by throwing a clear `Error`
291
+ * that names the offending key and hints the right one (e.g. `{view: 'full'}`
292
+ * throws `snapshot: unknown option "view" (did you mean { full: true }?)`).
293
+ *
294
+ * This turns a silent wrong-result into a loud error: it does not change
295
+ * behaviour for any valid input. Returns the validated options unchanged so it
296
+ * can wrap a call site inline.
297
+ */
298
+ export declare function validateSnapshotOptions(options?: SnapshotOptions): SnapshotOptions | undefined;
98
299
  /**
99
300
  * A structured, token-cheap view of the current page with stable element refs.
100
301
  *
@@ -120,6 +321,152 @@ export interface Snapshot {
120
321
  /** Human/agent-readable structured page content (see {@link Snapshot}). */
121
322
  readonly content: string;
122
323
  }
324
+ /**
325
+ * The Playwright-locator-derived extras a {@link QueryRow} can carry under
326
+ * `pw`. This is the ONLY fixed (closed) set in {@link QueryOptions}: these two
327
+ * facts are NOT expressible as a DOM attribute or a live JS property, so they
328
+ * cannot ride in `attrs`/`props` (which are caller-named and open). Everything
329
+ * else the agent wants is named freely as an attribute or a property (R2, no
330
+ * curated DOM field set).
331
+ *
332
+ * - `'visible'` — actionability-grade visibility (`locator.isVisible()`),
333
+ * strictly better than the `offsetParent` hack: a present-but-hidden element
334
+ * reads `false`.
335
+ * - `'bbox'` — the element's bounding box (`locator.boundingBox()`) in VIEWPORT
336
+ * CSS-pixels, the coordinate frame the future Tier-4 `mouse` verb uses.
337
+ */
338
+ export type PwExtra = 'visible' | 'bbox';
339
+ /**
340
+ * An element's bounding box in VIEWPORT CSS-pixels, the value of a
341
+ * {@link QueryRow}'s `pw.bbox`. Plain numbers only, so nothing Playwright-typed
342
+ * crosses the seam (ADR-0003). `null` when the element has no box (e.g. it is
343
+ * not rendered), mirroring `locator.boundingBox()`.
344
+ */
345
+ export interface BoundingBox {
346
+ readonly x: number;
347
+ readonly y: number;
348
+ readonly width: number;
349
+ readonly height: number;
350
+ }
351
+ /**
352
+ * Options for the {@link WebHandsPage.query} verb (R2).
353
+ *
354
+ * This is an OPTIONS OBJECT, not positional fields, on purpose (R1, the
355
+ * reversibility invariant a reviewer checks): a future optional `frame?`
356
+ * qualifier AND the T1b `ref` field are then PURE ADDITIONS to this object,
357
+ * breaking no existing call. Do NOT turn these into positional arguments.
358
+ *
359
+ * There is NO curated DOM field set: a row carries EXACTLY what the caller
360
+ * names here and nothing else. `attrs` and `props` are caller-named and OPEN
361
+ * (the agent already knows DOM/Playwright vocabulary); `pw` is the one closed
362
+ * set ({@link PwExtra}).
363
+ *
364
+ * `refs` is the OPT-IN durable-handle switch (R4): default `query` is a PURE
365
+ * READ that mints nothing and returns no `ref`; `refs: true` adds a `ref` to
366
+ * each row (see {@link QueryRow.ref}). It is a dedicated boolean, NOT a member
367
+ * of `pw`, because a `ref` is not a Playwright-locator-derived FACT about the
368
+ * element (the closed `pw` set) — it is an ADDRESS the agent acts on later. The
369
+ * CLI exposes it as `--with-refs`.
370
+ *
371
+ * The `attrs` vs `props` split is deliberate and LOUD — webhands NEVER
372
+ * auto-detects which of the two a name like `value`/`checked` means, because a
373
+ * silent attribute-vs-property guess is the footgun this repo's "loud over
374
+ * silent" style rejects.
375
+ */
376
+ export interface QueryOptions {
377
+ /**
378
+ * DOM ATTRIBUTES to read by name, via `getAttribute(name)` — what is written
379
+ * in the markup (`href`, `data-sitekey`, `type`). A missing attribute reads
380
+ * `null`.
381
+ */
382
+ readonly attrs?: readonly string[];
383
+ /**
384
+ * Live JS PROPERTIES to read by name, via `el[name]` — runtime state
385
+ * (`innerText`, `value`, `checked`, `selectedIndex`). `text` is just
386
+ * `props: ['innerText']`; there is no special `text` field.
387
+ */
388
+ readonly props?: readonly string[];
389
+ /**
390
+ * Playwright-locator-derived extras to include (the ONLY closed set; see
391
+ * {@link PwExtra}).
392
+ */
393
+ readonly pw?: readonly PwExtra[];
394
+ /** Bound the number of rows returned (token economy on a multi-match). */
395
+ readonly limit?: number;
396
+ /**
397
+ * Opt-in to a durable element {@link QueryRow.ref} per row (R4; finding
398
+ * `query-ref-mint-mechanism-attribute-beats-weakmap`). Default (omitted /
399
+ * `false`) keeps `query` a PURE READ: no `ref` field, and the page is NOT
400
+ * mutated. `true` computes a `ref` per matched element by the PREFERENCE
401
+ * LADDER — REUSE the element's own stable UNIQUE attribute when present
402
+ * (`id`/`data-testid`/…, ZERO DOM mutation), MINT a namespaced
403
+ * `data-webhands-ref` attribute ONLY as the fallback for an anonymous element.
404
+ *
405
+ * Mints are single-`query`-scoped: each `refs: true` query SWEEPS the prior
406
+ * query's mints first, so a ref can never match a stale element from two
407
+ * queries ago. An action verb resolves a `ref` with loud staleness detection
408
+ * (resolve-to-zero / resolve-to-many => {@link StaleRefError}); see
409
+ * {@link ActionOptions.byRef}.
410
+ */
411
+ readonly refs?: boolean;
412
+ }
413
+ /**
414
+ * One matched element's data, carrying EXACTLY the fields the caller named in
415
+ * {@link QueryOptions} and nothing else (R2). A sub-object is present ONLY when
416
+ * the caller asked for that family, and within it a key is present for every
417
+ * name requested:
418
+ *
419
+ * - `attrs[name]` is the `getAttribute(name)` value (`null` if absent).
420
+ * - `props[name]` is the live `el[name]` value, structurally cloned by VALUE
421
+ * (the same contract as `eval`; ADR-0003: no Playwright/CDP type leaks).
422
+ * - `pw.visible` / `pw.bbox` are the requested {@link PwExtra} values.
423
+ *
424
+ * When `query` is called with NO fields, each row is an empty object `{}`: the
425
+ * caller asked for nothing, so the row carries nothing (R2, "a row carries
426
+ * EXACTLY what the caller asked for").
427
+ */
428
+ export interface QueryRow {
429
+ readonly attrs?: Readonly<Record<string, string | null>>;
430
+ readonly props?: Readonly<Record<string, unknown>>;
431
+ readonly pw?: {
432
+ readonly visible?: boolean;
433
+ readonly bbox?: BoundingBox | null;
434
+ };
435
+ /**
436
+ * The element's durable HANDLE, present ONLY when the caller asked
437
+ * ({@link QueryOptions.refs}). It is a LOCATOR STRING the agent feeds back to
438
+ * an action verb (`click`/`type`) with `{byRef: true}` to act on THIS element
439
+ * later even after the list mutates — fixing the index-drift footgun where a
440
+ * positional `.nth(i)` silently clicks the wrong row.
441
+ *
442
+ * It is computed by the LADDER (R4): when the element has a stable UNIQUE
443
+ * attribute it IS that real locator (`#buy-charlie`, `[data-testid="x"]`),
444
+ * durable across framework reconciliation and ZERO DOM mutation; otherwise it
445
+ * is a minted `[data-webhands-ref="<id>"]` selector. Either way it is a plain
446
+ * STRING resolved through the ONE existing resolver — no new addressing engine,
447
+ * no Playwright type on the seam (ADR-0003/0004). It is a SHORT-LIVED handle:
448
+ * acting on it after a NODE-REPLACEMENT re-render or a navigation fails LOUD
449
+ * with {@link StaleRefError}, never a silent wrong-element action.
450
+ */
451
+ readonly ref?: string;
452
+ }
453
+ /**
454
+ * Options for an ACTION verb that may act on a durable {@link QueryRow.ref}
455
+ * instead of a raw locator (R4). An OPTIONS OBJECT so it is an ADDITIVE,
456
+ * non-breaking extension of `click`/`type` (R1): a today call passing no options
457
+ * is unchanged.
458
+ *
459
+ * `byRef: true` tells the verb its `target` is a `ref` from a prior
460
+ * `query({refs: true})`, so it must enforce the loud-stale contract: resolve the
461
+ * ref through the SAME single resolver, then assert it matches EXACTLY ONE
462
+ * element — resolve-to-zero (removed/replaced) OR resolve-to-many (a cloned
463
+ * subtree) BOTH reject with a typed {@link StaleRefError}, never a silent
464
+ * wrong-element action. Omitted / `false` keeps the verb's plain locator
465
+ * behaviour (auto-waiting, first-match), unchanged.
466
+ */
467
+ export interface ActionOptions {
468
+ readonly byRef?: boolean;
469
+ }
123
470
  /**
124
471
  * The page-level verb surface. One method per verb in the domain glossary.
125
472
  * All element addressing flows through {@link LocatorString}.
@@ -130,13 +477,31 @@ export interface WebHandsPage {
130
477
  /**
131
478
  * Return a structured, token-cheap view of the page. Defaults to the
132
479
  * accessibility-tree + visible-text view with stable refs; pass
133
- * `{full: true}` to get the raw DOM instead (PRD story 7).
480
+ * `{full: true}` to get the raw DOM instead (PRD story 7). An unknown or
481
+ * misshapen option REJECTS (e.g. `{view: 'full'}`), it is never silently
482
+ * ignored (see {@link validateSnapshotOptions}).
134
483
  */
135
484
  snapshot(options?: SnapshotOptions): Promise<Snapshot>;
136
- /** Click the element addressed by a raw Playwright locator string. */
137
- click(target: LocatorString): Promise<void>;
138
- /** Type text into the element addressed by a raw Playwright locator string. */
139
- type(target: LocatorString, text: string): Promise<void>;
485
+ /**
486
+ * Click the element addressed by a raw Playwright locator string.
487
+ *
488
+ * With `{byRef: true}` the `target` is treated as a durable
489
+ * {@link QueryRow.ref} from a prior `query({refs: true})`: it is resolved
490
+ * through the SAME resolver but MUST match EXACTLY ONE element, else a typed
491
+ * {@link StaleRefError} (resolve-to-zero / resolve-to-many) — the loud-stale
492
+ * guarantee that makes a ref strictly safer than a positional `.nth(i)`. The
493
+ * options object is additive (R1); omitted keeps today's plain-locator click.
494
+ */
495
+ click(target: LocatorString, options?: ActionOptions): Promise<void>;
496
+ /**
497
+ * Type text into the element addressed by a raw Playwright locator string.
498
+ *
499
+ * With `{byRef: true}` the `target` is a durable {@link QueryRow.ref}, resolved
500
+ * with the same EXACTLY-ONE loud-stale contract as {@link WebHandsPage.click}
501
+ * (a typed {@link StaleRefError} on zero/many). The options object is additive
502
+ * (R1); omitted keeps today's plain-locator type.
503
+ */
504
+ type(target: LocatorString, text: string, options?: ActionOptions): Promise<void>;
140
505
  /**
141
506
  * Run a JavaScript EXPRESSION in the active page's context and return its
142
507
  * result, the `eval` escape hatch for cases no other verb covers (PRD story
@@ -177,14 +542,143 @@ export interface WebHandsPage {
177
542
  * narrow it. This is deliberately a thin passthrough to the transport's
178
543
  * serialize-and-return: `eval` does not re-encode or wrap the result, so an
179
544
  * agent gets exactly what the page produced.
545
+ *
546
+ * FRAME SCOPE ({@link EvalOptions.frame}). With no `frame` this is exactly
547
+ * the top-document `eval` above. With a `frame` selector the expression runs
548
+ * in that NAMED SAME-ORIGIN child frame instead (e.g. to fire a captcha
549
+ * `data-callback` or read a runtime-only value the top document cannot see),
550
+ * returning by the same structured clone. The frame resolves through the
551
+ * SAME single resolver `click`/`type` use (a `frameLocator(...)` over the
552
+ * selector; R1), so there is no parallel frame-addressing path. A selector
553
+ * that resolves to a CROSS-ORIGIN frame REJECTS with a typed
554
+ * cross-origin-frame error (page-world JS cannot cross a security boundary),
555
+ * never a silent empty result.
180
556
  */
181
- eval(expression: string): Promise<unknown>;
557
+ eval(expression: string, options?: EvalOptions): Promise<unknown>;
182
558
  /** Pace actions by waiting for a condition. */
183
559
  wait(condition: WaitCondition): Promise<void>;
184
560
  /** Read the session's cookies. */
185
561
  cookies(): Promise<readonly Cookie[]>;
186
562
  /** Seed the session's cookies. */
187
563
  setCookies(cookies: readonly Cookie[]): Promise<void>;
564
+ /**
565
+ * Read STRUCTURED data out of the element(s) addressed by a raw Playwright
566
+ * locator string (ADR-0004; already frame-capable for same-origin frames via
567
+ * a `frameLocator(...)` expression). Returns ONE ROW PER MATCH, each carrying
568
+ * EXACTLY the fields named in {@link QueryOptions} — caller-named `attrs`
569
+ * (DOM attributes) and `props` (live JS properties), plus the closed `pw`
570
+ * extras (R2). This kills the `eval`-returns-a-JSON-string pattern.
571
+ *
572
+ * The options are an OPTIONS OBJECT so a future `frame?` field is a
573
+ * non-breaking addition (R1); the locator resolves through the SAME single
574
+ * resolver `click`/`type`/`wait` use — no parallel addressing scheme.
575
+ *
576
+ * With `{refs: true}` (OPT-IN) each row also carries a durable
577
+ * {@link QueryRow.ref} the agent feeds back to `click`/`type` (`{byRef: true}`)
578
+ * to act on THAT element after the page mutates, fixing the index-drift
579
+ * footgun. The default (no `refs`) is a PURE READ that mints nothing.
580
+ *
581
+ * Values cross by structured clone, the SAME contract as `eval` (ADR-0003: no
582
+ * Playwright/CDP types on the seam). With no fields requested, each row is an
583
+ * empty object.
584
+ */
585
+ query(target: LocatorString, options?: QueryOptions): Promise<QueryRow[]>;
586
+ /**
587
+ * The number of elements the locator matches (a property of the MATCH SET,
588
+ * not a row field). A thin shorthand over the same machinery as
589
+ * {@link WebHandsPage.query}.
590
+ */
591
+ count(target: LocatorString): Promise<number>;
592
+ /** Whether the locator matches at least one element (`count(target) > 0`). */
593
+ exists(target: LocatorString): Promise<boolean>;
594
+ /**
595
+ * The first match's actionability-grade visibility (its `pw:['visible']`): a
596
+ * present-but-hidden element reads `false`, and an ABSENT element reads
597
+ * `false` too (no match cannot be visible).
598
+ */
599
+ isVisible(target: LocatorString): Promise<boolean>;
600
+ /**
601
+ * The first match's `name` DOM attribute (its `attrs:[name]`), via
602
+ * `getAttribute`. `null` when the attribute is absent OR the locator matches
603
+ * no element — both "there is no such attribute value to read".
604
+ */
605
+ getAttribute(target: LocatorString, name: string): Promise<string | null>;
606
+ /**
607
+ * Press a keyboard key or chord (prd `broaden-agent-verb-surface`, Tier-2,
608
+ * story 8) — arrows, `Enter`, `Space`, a letter (`w`), or a chord like
609
+ * `Control+A`. The chord grammar is Playwright's `keyboard.press` grammar:
610
+ * `Modifier+Modifier+Key`, modifiers `Control`/`Alt`/`Shift`/`Meta`, key names
611
+ * like `ArrowLeft`/`Enter`/`a` (see the task's ## Decisions note). The key is a
612
+ * plain STRING, so nothing Playwright-shaped crosses the seam (ADR-0003).
613
+ *
614
+ * With `target`, the key is sent to the element that locator addresses (it is
615
+ * focused first, the `locator.press` semantics); WITHOUT it, the key is sent to
616
+ * the page's currently focused element (`keyboard.press`). `target` is an
617
+ * optional trailing arg so a future `frame?` stays additive (R1).
618
+ */
619
+ press(key: string, target?: LocatorString): Promise<void>;
620
+ /**
621
+ * Hover the pointer over the element a locator addresses (prd
622
+ * `broaden-agent-verb-surface`, Tier-2, story 9), to reveal a hover menu /
623
+ * on-hover control `click` cannot surface (`locator.hover`).
624
+ */
625
+ hover(target: LocatorString): Promise<void>;
626
+ /**
627
+ * Choose an option in the native `<select>` a locator addresses (prd
628
+ * `broaden-agent-verb-surface`, Tier-2, story 10), by `value` OR by `label`
629
+ * (EXACTLY ONE; see {@link SelectChoice}). Maps to Playwright
630
+ * `locator.selectOption`; the chosen option is reflected in the element's live
631
+ * state (its `value` / `selectedIndex`).
632
+ */
633
+ select(target: LocatorString, choice: SelectChoice): Promise<void>;
634
+ /**
635
+ * Scroll the page, either TO an element a locator addresses or BY a pixel
636
+ * delta (prd `broaden-agent-verb-surface`, Tier-2, story 11; EXACTLY ONE form,
637
+ * see {@link ScrollTarget}). `to` reaches lazy-loaded / off-viewport content
638
+ * (`scrollIntoViewIfNeeded`); `by` nudges the page a fixed amount
639
+ * (`mouse.wheel`).
640
+ */
641
+ scroll(target: ScrollTarget): Promise<void>;
642
+ /**
643
+ * Drag the element `source` addresses onto the element `target` addresses (prd
644
+ * `broaden-agent-verb-surface`, Tier-2, story 12), for drag-reorder UIs and
645
+ * drag-slider challenges (`locator.dragTo`). Both are raw locator EXPRESSIONS
646
+ * resolved through the SAME resolver as `click`/`type` (ADR-0004).
647
+ */
648
+ drag(source: LocatorString, target: LocatorString): Promise<void>;
649
+ /**
650
+ * Coordinate mouse input at VIEWPORT CSS-pixels (prd
651
+ * `broaden-agent-verb-surface`, Tier-4, R3, story 17): click / move / press /
652
+ * release at a raw `(x, y)` the agent SAW in a VIEWPORT {@link
653
+ * WebHandsPage.screenshot}, the input half of the look-then-click loop. This
654
+ * is the coordinate counterpart to the locator-addressing {@link
655
+ * WebHandsPage.click}, for the vision/tile captcha family and any pixel-level
656
+ * task. It uses Playwright `page.mouse` semantics (viewport-relative CSS
657
+ * pixels), NOT OS-level screen input — see {@link MouseInput}.
658
+ *
659
+ * Plain numbers + a string enum cross the seam (ADR-0003 as amended by the
660
+ * Tier-4 ADR); no Playwright/CDP type leaks.
661
+ */
662
+ mouse(input: MouseInput): Promise<void>;
663
+ /**
664
+ * Capture the page to a PNG FILE and return its PATH (prd
665
+ * `broaden-agent-verb-surface`, Tier-4, R3; stories 17-19). webhands MINTS the
666
+ * PNG under its managed screenshots dir and returns `{path, width, height}`;
667
+ * NO image bytes cross the seam (the load-bearing ADR-0003-as-amended choice),
668
+ * so an agent reads / attaches the file by path.
669
+ *
670
+ * Three scopes ({@link ScreenshotScope}): `viewport` (default,
671
+ * COORDINATE-MATCHED to {@link WebHandsPage.mouse} — a pixel `(x, y)` here is
672
+ * the `mouse` click `(x, y)`), `full` (the whole scrollable page, for reading
673
+ * scrolled-out content, NOT coordinate-matched), and `element` (clipped to the
674
+ * element a locator addresses; the locator is REQUIRED and validated loud like
675
+ * `wait`).
676
+ *
677
+ * A caller MAY override the path ({@link ScreenshotOptions.out}); it is
678
+ * validated to stay UNDER the managed dir (an escaping path rejects with a
679
+ * typed error), so the verb never writes to an arbitrary location.
680
+ */
681
+ screenshot(options?: ScreenshotOptions): Promise<Screenshot>;
188
682
  }
189
683
  /**
190
684
  * A live browser session owning one active {@link WebHandsPage}. The session lifetime
@@ -1 +1 @@
1
- {"version":3,"file":"seam.d.ts","sourceRoot":"","sources":["../src/seam.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seam.d.ts","sourceRoot":"","sources":["../src/seam.ts"],"names":[],"mappings":"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"}
package/dist/seam.js CHANGED
@@ -22,4 +22,35 @@
22
22
  export function locator(expression) {
23
23
  return expression;
24
24
  }
25
+ /**
26
+ * Validate a {@link SnapshotOptions} value at a verb entry point, the SINGLE
27
+ * source of truth shared by the in-process host and the RPC server dispatch so
28
+ * neither path can silently drop a misspelled option.
29
+ *
30
+ * Accepts `undefined`, `{}`, and `{full: boolean}`. REJECTS any object carrying
31
+ * a key other than `full`, and a non-boolean `full`, by throwing a clear `Error`
32
+ * that names the offending key and hints the right one (e.g. `{view: 'full'}`
33
+ * throws `snapshot: unknown option "view" (did you mean { full: true }?)`).
34
+ *
35
+ * This turns a silent wrong-result into a loud error: it does not change
36
+ * behaviour for any valid input. Returns the validated options unchanged so it
37
+ * can wrap a call site inline.
38
+ */
39
+ export function validateSnapshotOptions(options) {
40
+ if (options === undefined) {
41
+ return options;
42
+ }
43
+ if (typeof options !== 'object' || options === null) {
44
+ throw new Error(`snapshot: options must be an object like { full: true }, got ${typeof options}`);
45
+ }
46
+ const unknownKeys = Object.keys(options).filter((key) => key !== 'full');
47
+ if (unknownKeys.length > 0) {
48
+ const named = unknownKeys.map((key) => `"${key}"`).join(', ');
49
+ throw new Error(`snapshot: unknown option ${named} (did you mean { full: true }?)`);
50
+ }
51
+ if (options.full !== undefined && typeof options.full !== 'boolean') {
52
+ throw new Error(`snapshot: option "full" must be a boolean, got ${typeof options.full}`);
53
+ }
54
+ return options;
55
+ }
25
56
  //# sourceMappingURL=seam.js.map
package/dist/seam.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"seam.js","sourceRoot":"","sources":["../src/seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,sEAAsE;AACtE,MAAM,UAAU,OAAO,CAAC,UAAkB;IACzC,OAAO,UAA2B,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"seam.js","sourceRoot":"","sources":["../src/seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,sEAAsE;AACtE,MAAM,UAAU,OAAO,CAAC,UAAkB;IACzC,OAAO,UAA2B,CAAC;AACpC,CAAC;AA8PD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACtC,OAAyB;IAEzB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CACd,gEAAgE,OAAO,OAAO,EAAE,CAChF,CAAC;IACH,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;IACzE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,IAAI,KAAK,CACd,4BAA4B,KAAK,iCAAiC,CAClE,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CACd,kDAAkD,OAAO,OAAO,CAAC,IAAI,EAAE,CACvE,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}