@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-pages.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,KAAK,GAAG;;;;;;;;;;;;;CAab,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BlB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;CAenB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BZ,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;CAgBf,CAAC;AAEF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC9D,YAAY,EAAE,KAAK;IACnB,iBAAiB,EAAE,UAAU;IAC7B,cAAc,EAAE,eAAe;IAC/B,kBAAkB,EAAE,WAAW;IAC/B,WAAW,EAAE,IAAI;IACjB,cAAc,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"fixture-pages.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-pages.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-server.d.ts","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fixture-server.d.ts","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"AAiBA,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,SAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CA+CzE"}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { FIXTURE_PAGES } from './fixture-pages.js';
|
|
3
|
+
/** Largest artificial response delay a fixture request may ask for (a guard so
|
|
4
|
+
* a stray `?delayMs=` can never hang the suite). */
|
|
5
|
+
const MAX_DELAY_MS = 5_000;
|
|
6
|
+
/** Parse `?delayMs=N` from a request URL, clamped to `[0, MAX_DELAY_MS]`. */
|
|
7
|
+
function parseDelayMs(reqUrl) {
|
|
8
|
+
const q = reqUrl.indexOf('?');
|
|
9
|
+
if (q === -1)
|
|
10
|
+
return 0;
|
|
11
|
+
const raw = new URLSearchParams(reqUrl.slice(q + 1)).get('delayMs');
|
|
12
|
+
const n = raw === null ? 0 : Number.parseInt(raw, 10);
|
|
13
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
14
|
+
return 0;
|
|
15
|
+
return Math.min(n, MAX_DELAY_MS);
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Start a local HTTP server that serves the controlled static fixture pages
|
|
5
19
|
* from {@link FIXTURE_PAGES}. This is the DETERMINISTIC target for later
|
|
@@ -12,7 +26,8 @@ import { FIXTURE_PAGES } from './fixture-pages.js';
|
|
|
12
26
|
*/
|
|
13
27
|
export async function startFixtureServer(port = 0) {
|
|
14
28
|
const server = createServer((req, res) => {
|
|
15
|
-
const
|
|
29
|
+
const reqUrl = req.url ?? '/';
|
|
30
|
+
const rawPath = reqUrl.split('?')[0];
|
|
16
31
|
const key = rawPath === '/' ? 'index.html' : rawPath.replace(/^\/+/, '');
|
|
17
32
|
const body = FIXTURE_PAGES[key];
|
|
18
33
|
if (body === undefined) {
|
|
@@ -20,8 +35,23 @@ export async function startFixtureServer(port = 0) {
|
|
|
20
35
|
res.end('not found');
|
|
21
36
|
return;
|
|
22
37
|
}
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
// A `?delayMs=N` query holds the RESPONSE back by N ms before sending the
|
|
39
|
+
// (otherwise normal) page. This makes a slow NAVIGATION deterministic: a
|
|
40
|
+
// click that submits to `index.html?delayMs=1500` performs instantly but
|
|
41
|
+
// the navigation only commits ~1.5s later, the case the `click` verb must
|
|
42
|
+
// not mistake for a non-actionable element (it auto-waits for navigation
|
|
43
|
+
// only when not told otherwise). Capped so a bad value cannot hang a test.
|
|
44
|
+
const delayMs = parseDelayMs(reqUrl);
|
|
45
|
+
const send = () => {
|
|
46
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
47
|
+
res.end(body);
|
|
48
|
+
};
|
|
49
|
+
if (delayMs > 0) {
|
|
50
|
+
setTimeout(send, delayMs);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
send();
|
|
54
|
+
}
|
|
25
55
|
});
|
|
26
56
|
await new Promise((resolve) => server.listen(port, '127.0.0.1', resolve));
|
|
27
57
|
const address = server.address();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-server.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAc,MAAM,WAAW,CAAC;AACpD,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"fixture-server.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -24,7 +24,10 @@ export type ControllerErrorCode =
|
|
|
24
24
|
| 'attach-not-chromium'
|
|
25
25
|
| 'attach-no-context'
|
|
26
26
|
| 'no-live-server'
|
|
27
|
-
| 'session-already-active'
|
|
27
|
+
| 'session-already-active'
|
|
28
|
+
| 'cross-origin-frame'
|
|
29
|
+
| 'screenshot-path-outside-managed-dir'
|
|
30
|
+
| 'stale-ref';
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Base class for every identifiable `core` error. Branch on {@link code}.
|
|
@@ -230,6 +233,136 @@ export class SessionAlreadyActiveError extends ControllerError {
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
|
|
236
|
+
/**
|
|
237
|
+
* A frame-scoped `eval` (or any same-origin-frame op) addressed a CROSS-ORIGIN
|
|
238
|
+
* child frame. Page-world JS cannot cross a browser security boundary, so the
|
|
239
|
+
* frame-scoped `eval` reaches the top document and SAME-ORIGIN descendant frames
|
|
240
|
+
* ONLY (the idea's honest-scope note); a cross-origin frame is unreachable BY
|
|
241
|
+
* DESIGN, not a missing feature. We refuse LOUDLY with this typed condition
|
|
242
|
+
* rather than return a silent empty result, because a silent empty would let an
|
|
243
|
+
* agent believe its callback fired / its read succeeded when the security
|
|
244
|
+
* boundary actually blocked it.
|
|
245
|
+
*
|
|
246
|
+
* Cross-origin frame reach is the SEPARATE Tier-4 `frameLocator`/coordinate
|
|
247
|
+
* surface, not this verb; the message points there so the agent is not left
|
|
248
|
+
* guessing. Mirrors the other typed conditions: the CLI maps {@link code} to a
|
|
249
|
+
* message, and {@link isControllerError} narrows it across a bundle boundary.
|
|
250
|
+
*/
|
|
251
|
+
export class CrossOriginFrameError extends ControllerError {
|
|
252
|
+
readonly code = 'cross-origin-frame';
|
|
253
|
+
/** The frame selector the caller passed (echoed back so it is visible). */
|
|
254
|
+
readonly frame: string;
|
|
255
|
+
/** The cross-origin frame's origin, when known (e.g. `https://hcaptcha.com`). */
|
|
256
|
+
readonly frameOrigin?: string;
|
|
257
|
+
/** The page's own (main-frame) origin the frame had to match. */
|
|
258
|
+
readonly pageOrigin?: string;
|
|
259
|
+
|
|
260
|
+
constructor(
|
|
261
|
+
frame: string,
|
|
262
|
+
details?: {frameOrigin?: string; pageOrigin?: string},
|
|
263
|
+
message: string = `The frame ${JSON.stringify(frame)} is CROSS-ORIGIN${
|
|
264
|
+
details?.frameOrigin !== undefined && details?.pageOrigin !== undefined
|
|
265
|
+
? ` (frame origin ${details.frameOrigin}, page origin ${details.pageOrigin})`
|
|
266
|
+
: ''
|
|
267
|
+
} 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.`,
|
|
268
|
+
options?: {cause?: unknown},
|
|
269
|
+
) {
|
|
270
|
+
super(message, options);
|
|
271
|
+
this.frame = frame;
|
|
272
|
+
this.frameOrigin = details?.frameOrigin;
|
|
273
|
+
this.pageOrigin = details?.pageOrigin;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* A `screenshot --out <path>` override pointed OUTSIDE the managed screenshots
|
|
279
|
+
* dir. webhands MINTS screenshots under one managed directory it owns (under the
|
|
280
|
+
* controller home root, beside `profiles/`); a caller MAY override the output
|
|
281
|
+
* path, but only WITHIN that managed dir (prd `broaden-agent-verb-surface`, R3:
|
|
282
|
+
* "validate it stays under a sane managed dir"). An override that escapes it
|
|
283
|
+
* (an absolute path elsewhere, or a `..` traversal out) is refused LOUDLY with
|
|
284
|
+
* this typed condition rather than silently writing a PNG to an arbitrary
|
|
285
|
+
* filesystem location — the seam returns a path, so an unbounded path would let
|
|
286
|
+
* the verb clobber any file the process can write.
|
|
287
|
+
*
|
|
288
|
+
* Mirrors the other typed conditions: the CLI maps {@link code} to a message,
|
|
289
|
+
* and {@link isControllerError} narrows it across a bundle boundary.
|
|
290
|
+
*/
|
|
291
|
+
export class ScreenshotPathError extends ControllerError {
|
|
292
|
+
readonly code = 'screenshot-path-outside-managed-dir';
|
|
293
|
+
/** The offending caller-supplied `out` path, echoed back so it is visible. */
|
|
294
|
+
readonly path: string;
|
|
295
|
+
/** The managed screenshots dir the path had to stay under. */
|
|
296
|
+
readonly managedDir: string;
|
|
297
|
+
|
|
298
|
+
constructor(
|
|
299
|
+
path: string,
|
|
300
|
+
managedDir: string,
|
|
301
|
+
message: string = `The screenshot output path ${JSON.stringify(
|
|
302
|
+
path,
|
|
303
|
+
)} 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.`,
|
|
304
|
+
options?: {cause?: unknown},
|
|
305
|
+
) {
|
|
306
|
+
super(message, options);
|
|
307
|
+
this.path = path;
|
|
308
|
+
this.managedDir = managedDir;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* A durable `query` `ref` (prd `broaden-agent-verb-surface`, R4; finding
|
|
314
|
+
* `query-ref-mint-mechanism-attribute-beats-weakmap`) failed to resolve to
|
|
315
|
+
* EXACTLY ONE element when an action verb (`click`/`type`) tried to act on it.
|
|
316
|
+
* A `ref` is a SHORT-LIVED handle, not a stable identity: between the `query`
|
|
317
|
+
* that minted it and the act, the page may have re-rendered (React keyed-list /
|
|
318
|
+
* Svelte `{#each}` NODE REPLACEMENT), navigated, or cloned a subtree carrying
|
|
319
|
+
* our minted attribute. Two stale shapes, BOTH this one error, never a silent
|
|
320
|
+
* wrong-element action:
|
|
321
|
+
*
|
|
322
|
+
* - resolve-to-ZERO ({@link matched} `=== 0`) — the element was removed or
|
|
323
|
+
* replaced (its minted node is gone, or a reused stable attribute it carried
|
|
324
|
+
* no longer exists). The agent must re-`query` against the fresh DOM.
|
|
325
|
+
* - resolve-to-MANY ({@link matched} `> 1`) — the ref matches more than one
|
|
326
|
+
* element (a framework cloned a subtree carrying our minted attribute, or a
|
|
327
|
+
* reused attribute turned out non-unique after a mutation). We refuse to
|
|
328
|
+
* "pick the first", because that is exactly the silent wrong-element click the
|
|
329
|
+
* ref exists to PREVENT.
|
|
330
|
+
*
|
|
331
|
+
* This is STRICTLY SAFER than re-addressing by a positional `.nth(i)`, which
|
|
332
|
+
* SILENTLY clicks whatever now sits at that index. The agent is told "re-query,
|
|
333
|
+
* the page changed" — its natural loop anyway. Mirrors the other typed
|
|
334
|
+
* conditions: the CLI maps {@link code} to a message, and
|
|
335
|
+
* {@link isControllerError} narrows it across a bundle boundary.
|
|
336
|
+
*/
|
|
337
|
+
export class StaleRefError extends ControllerError {
|
|
338
|
+
readonly code = 'stale-ref';
|
|
339
|
+
/** The ref locator string that went stale (echoed back so it is visible). */
|
|
340
|
+
readonly ref: string;
|
|
341
|
+
/** How many elements the ref resolved to (0 = removed/replaced, >1 = ambiguous). */
|
|
342
|
+
readonly matched: number;
|
|
343
|
+
/** Which verb tried to act on the stale ref (e.g. `click`, `type`). */
|
|
344
|
+
readonly verb: string;
|
|
345
|
+
|
|
346
|
+
constructor(
|
|
347
|
+
ref: string,
|
|
348
|
+
matched: number,
|
|
349
|
+
verb: string,
|
|
350
|
+
message: string = matched === 0
|
|
351
|
+
? `${verb}: the ref ${JSON.stringify(
|
|
352
|
+
ref,
|
|
353
|
+
)} 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.`
|
|
354
|
+
: `${verb}: the ref ${JSON.stringify(
|
|
355
|
+
ref,
|
|
356
|
+
)} 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.`,
|
|
357
|
+
options?: {cause?: unknown},
|
|
358
|
+
) {
|
|
359
|
+
super(message, options);
|
|
360
|
+
this.ref = ref;
|
|
361
|
+
this.matched = matched;
|
|
362
|
+
this.verb = verb;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
233
366
|
/**
|
|
234
367
|
* Narrow an unknown caught value to a {@link ControllerError}. Prefer this over
|
|
235
368
|
* `instanceof` at package boundaries: it checks the {@link ControllerError.isControllerError}
|