@webhands/core 0.1.0 → 0.2.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 (50) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +112 -0
  3. package/dist/cookies-export.d.ts +5 -5
  4. package/dist/cookies-export.d.ts.map +1 -1
  5. package/dist/cookies-export.js +4 -4
  6. package/dist/hand-host.d.ts +217 -0
  7. package/dist/hand-host.d.ts.map +1 -0
  8. package/dist/hand-host.js +351 -0
  9. package/dist/hand-host.js.map +1 -0
  10. package/dist/hand-loading.d.ts +128 -0
  11. package/dist/hand-loading.d.ts.map +1 -0
  12. package/dist/hand-loading.js +143 -0
  13. package/dist/hand-loading.js.map +1 -0
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/playwright-attach-transport.d.ts +9 -0
  19. package/dist/playwright-attach-transport.d.ts.map +1 -1
  20. package/dist/playwright-attach-transport.js +53 -91
  21. package/dist/playwright-attach-transport.js.map +1 -1
  22. package/dist/playwright-launch-transport.d.ts +7 -62
  23. package/dist/playwright-launch-transport.d.ts.map +1 -1
  24. package/dist/playwright-launch-transport.js +51 -204
  25. package/dist/playwright-launch-transport.js.map +1 -1
  26. package/dist/remote-session.d.ts +12 -2
  27. package/dist/remote-session.d.ts.map +1 -1
  28. package/dist/remote-session.js +37 -6
  29. package/dist/remote-session.js.map +1 -1
  30. package/dist/seam.d.ts +13 -5
  31. package/dist/seam.d.ts.map +1 -1
  32. package/dist/session-rpc.d.ts +76 -12
  33. package/dist/session-rpc.d.ts.map +1 -1
  34. package/dist/session-rpc.js +76 -8
  35. package/dist/session-rpc.js.map +1 -1
  36. package/dist/stub-transport.d.ts +2 -2
  37. package/dist/stub-transport.d.ts.map +1 -1
  38. package/dist/stub-transport.js +11 -0
  39. package/dist/stub-transport.js.map +1 -1
  40. package/package.json +21 -2
  41. package/src/cookies-export.ts +5 -5
  42. package/src/hand-host.ts +511 -0
  43. package/src/hand-loading.ts +254 -0
  44. package/src/index.ts +18 -1
  45. package/src/playwright-attach-transport.ts +65 -119
  46. package/src/playwright-launch-transport.ts +63 -244
  47. package/src/remote-session.ts +43 -5
  48. package/src/seam.ts +13 -5
  49. package/src/session-rpc.ts +121 -11
  50. package/src/stub-transport.ts +15 -3
@@ -1,6 +1,7 @@
1
1
  import { stat } from 'node:fs/promises';
2
- import { chromium, errors as pwErrors, } from 'playwright';
2
+ import { chromium } from 'playwright';
3
3
  import { MissingBrowserBinaryError, MissingProfileError } from './errors.js';
4
+ import { composeWithHands } from './hand-host.js';
4
5
  import { resolveProfileLocation, } from './profile-location.js';
5
6
  /**
6
7
  * The v1 concrete transport: a Playwright browser the controller LAUNCHES
@@ -21,13 +22,19 @@ import { resolveProfileLocation, } from './profile-location.js';
21
22
  */
22
23
  export class PlaywrightLaunchTransport {
23
24
  #location;
25
+ #hands;
24
26
  /**
25
27
  * @param location overrides for where profiles live (a `root` dir and/or an
26
28
  * `env`). Omit in production to use `~/.webhands`; pass a temp
27
29
  * `root` in tests to isolate the shared profile location.
30
+ * @param hands explicitly-loaded third-party hands to compose alongside the
31
+ * built-ins (Phase 2, ADR-0007). These come from {@link loadHands} against
32
+ * the operator's explicit config; the transport does NOT discover them. Omit
33
+ * for the built-ins-only surface.
28
34
  */
29
- constructor(location = {}) {
35
+ constructor(location = {}, hands = []) {
30
36
  this.#location = location;
37
+ this.#hands = hands;
31
38
  }
32
39
  async open(target) {
33
40
  if (target.mode !== 'launch') {
@@ -61,7 +68,7 @@ export class PlaywrightLaunchTransport {
61
68
  // the single active page (PRD: single active session in v1). Create one if
62
69
  // the build ever changes that invariant.
63
70
  const pwPage = context.pages()[0] ?? (await context.newPage());
64
- return makeSession(context, pwPage);
71
+ return makeSession(context, pwPage, this.#hands);
65
72
  }
66
73
  }
67
74
  /** True iff `path` exists and is a directory. */
@@ -86,220 +93,60 @@ function isMissingBrowserBinary(cause) {
86
93
  /please run the following command to download new browsers/i.test(message) ||
87
94
  /playwright install/i.test(message));
88
95
  }
89
- /** Wrap a live Playwright persistent context into the seam's {@link Session}. */
90
- function makeSession(context, pwPage) {
96
+ /**
97
+ * Wrap a live Playwright persistent context into the seam's {@link Session}.
98
+ *
99
+ * The VERB surface comes from the shared hand-host ({@link composeBuiltInPage}),
100
+ * which is the single place the eight built-in verbs are composed (no duplicated
101
+ * page-object literal). Only the SESSION LIFECYCLE is per-transport here: the
102
+ * launch transport listens on the context's `'close'` event and its `close()`
103
+ * calls `context.close()`, which KILLS the browser this transport spawned
104
+ * (contrast the attach transport, which detaches without killing the user's
105
+ * browser, ADR-0002).
106
+ */
107
+ function makeSession(context, pwPage, extraHands) {
91
108
  let closed = false;
92
109
  const ensureOpen = () => {
93
110
  if (closed) {
94
111
  throw new Error('session is closed');
95
112
  }
96
113
  };
97
- const page = {
98
- async navigate(url) {
99
- ensureOpen();
100
- // "Settled" for `goto` = the `load` event: the document and its
101
- // subresources have loaded (PRD story 6, "navigate ... and wait for it
102
- // to settle"). We deliberately do NOT wait for `networkidle`:
103
- // Playwright discourages it, and it hangs forever on pages with
104
- // long-poll / streaming / analytics beacons (exactly the logged-in apps
105
- // this tool targets). Content rendered AFTER load (XHR-injected prices,
106
- // hydrated lists) is the job of the explicit `wait` verb (story 10), not
107
- // of `goto`.
108
- await pwPage.goto(url, { waitUntil: 'load' });
109
- },
110
- async snapshot(options) {
111
- ensureOpen();
112
- const url = pwPage.url();
113
- if (options?.full === true) {
114
- // `--full`: the raw DOM. `documentElement.outerHTML` is the serialized
115
- // live DOM (post-script render), which is what an agent that wants the
116
- // real HTML expects — not the original network response.
117
- const content = await pwPage.evaluate(() => document.documentElement.outerHTML);
118
- return { url, view: 'full', content };
119
- }
120
- // Default: the token-cheap accessibility tree + visible text with stable
121
- // `[ref=...]` element refs. Playwright's `ariaSnapshot({mode: 'ai'})`
122
- // emits exactly that — a YAML aria tree (roles + accessible names +
123
- // text) where each node carries a stable `[ref=eN]` reference, assigned
124
- // deterministically by traversal order so re-snapshotting an unchanged
125
- // page yields the same refs. The string crosses the seam as opaque,
126
- // transport-neutral text (no Playwright type leaks, ADR-0003).
127
- const content = await pwPage.ariaSnapshot({ mode: 'ai' });
128
- return { url, view: 'accessibility', content };
129
- },
130
- async click(t) {
131
- ensureOpen();
132
- await clickLocator(pwPage, t);
133
- },
134
- async type(t, text) {
135
- ensureOpen();
136
- await resolveLocator(pwPage, t).fill(text);
137
- },
138
- async eval(expression) {
139
- ensureOpen();
140
- // The `eval` escape hatch (PRD story 9): run the raw JS EXPRESSION in the
141
- // page and return its serializable result. Playwright's `evaluate`
142
- // already IS the seam's serialization contract (see {@link Page.eval}):
143
- // it passes a string as an expression, awaits a returned Promise, and
144
- // structurally clones the result out of the page by VALUE. That clone is
145
- // richer than JSON: it preserves NaN/Infinity/BigInt and circular
146
- // structures (back-refs become a `[Circular]` marker), yields `undefined`
147
- // for functions/symbols, and returns an opaque preview string for a live
148
- // host object (a DOM node never crosses the process boundary). A page-side
149
- // throw rejects. We pass it straight through rather than re-encode it:
150
- // wrapping the value in a transport-specific envelope would invent a
151
- // dialect the seam deliberately avoids. The thrown error is a plain
152
- // `Error`, so no Playwright/CDP type leaks across the seam (ADR-0003).
153
- return pwPage.evaluate(expression);
154
- },
155
- async wait(condition) {
156
- ensureOpen();
157
- await waitFor(pwPage, condition);
158
- },
159
- async cookies() {
160
- ensureOpen();
161
- const raw = await context.cookies();
162
- return raw.map(toSeamCookie);
163
- },
164
- async setCookies(cookies) {
165
- ensureOpen();
166
- await context.addCookies(cookies.map(fromSeamCookie));
167
- },
114
+ // Resolves the first time the context is gone — whether the USER closed the
115
+ // window (Playwright fires the context 'close' event) or our own close()
116
+ // ran. This is what lets `setup-profile` hold the headed window open and
117
+ // block on waitForClose() until the human is done.
118
+ let resolveClosed;
119
+ const closedSignal = new Promise((resolve) => {
120
+ resolveClosed = resolve;
121
+ });
122
+ const markClosed = () => {
123
+ if (closed)
124
+ return;
125
+ closed = true;
126
+ resolveClosed();
168
127
  };
128
+ context.on('close', markClosed);
129
+ // Build the verb surface from the built-in hands over a live hand-context.
130
+ // The host keeps the live `pwPage`/`context` in-process (they never cross the
131
+ // seam, ADR-0003); the hand-context carries live page access only.
132
+ const handContext = { pwPage, context, ensureOpen };
133
+ const { page, dispose: disposeHands } = composeWithHands(handContext, extraHands);
169
134
  return {
170
135
  page,
171
136
  async close() {
172
- if (closed)
137
+ if (closed) {
173
138
  return;
174
- closed = true;
139
+ }
140
+ // Dispose the hands first (their in-process resources), THEN tear down
141
+ // the browser: context.close() fires the 'close' event, which runs
142
+ // markClosed and KILLS the browser this transport spawned.
143
+ await disposeHands();
175
144
  await context.close();
145
+ markClosed();
146
+ },
147
+ waitForClose() {
148
+ return closedSignal;
176
149
  },
177
- };
178
- }
179
- /**
180
- * Run the `wait` verb's three forms (PRD story 10) against a Playwright page.
181
- *
182
- * - `timeout` — pace by a fixed delay (`waitForTimeout`), so an agent can act
183
- * like a human and let XHR-rendered content land.
184
- * - `locator` — block until the addressed element appears (`Locator.waitFor()`),
185
- * the form for content rendered AFTER `goto` settled on `load`.
186
- * - `navigation` — block until the NEXT navigation settles to `load`. We use
187
- * `waitForNavigation()` even though Playwright marks it `@deprecated` ("racy,
188
- * use waitForURL"): that deprecation targets in-process TEST code that can arm
189
- * the wait BEFORE the action and pass a target URL. Neither holds here. Across
190
- * this seam verbs are DISCRETE sequential calls (`click` then `wait`), so we
191
- * CANNOT arm before the trigger; and the realistic trigger is an async,
192
- * JS-driven transition (a redirect / SPA route change that fires AFTER the
193
- * agent's action, the "let XHR-rendered content load" case of story 10), so
194
- * "wait for the NEXT navigation" is exactly right — whereas `waitForLoadState`
195
- * would see the already-loaded current page and return before the pending
196
- * transition. `waitForURL` is unusable because the verb has no target URL by
197
- * design (the agent waits for "a navigation", not a known address). (See the
198
- * task's ## Decisions note.)
199
- *
200
- * Shared by both Playwright transports so the verb behaviour stays identical
201
- * (the forward-note's "do NOT write a parallel second implementation").
202
- */
203
- export async function waitFor(page, condition) {
204
- switch (condition.kind) {
205
- case 'timeout':
206
- await page.waitForTimeout(condition.ms);
207
- return;
208
- case 'locator':
209
- await resolveLocator(page, condition.target).waitFor();
210
- return;
211
- case 'navigation':
212
- // eslint-disable-next-line @typescript-eslint/no-deprecated
213
- await page.waitForNavigation();
214
- return;
215
- }
216
- }
217
- /**
218
- * Resolve a raw Playwright locator EXPRESSION (ADR-0004) against the page. The
219
- * verb surface passes locator expressions like `getByRole('button', …)`; we
220
- * evaluate them in a small sandbox where `page`/`p` is the page, so the full
221
- * Playwright locator grammar is available without leaking the type across the
222
- * seam.
223
- *
224
- * Exported (with {@link clickLocator}/{@link waitFor}) so the attach transport
225
- * resolves locators IDENTICALLY — one resolution path, no parallel addressing
226
- * scheme (the forward-note's "do NOT write a parallel second implementation").
227
- */
228
- export function resolveLocator(page, expression) {
229
- // eslint-disable-next-line no-new-func
230
- const factory = new Function('page', 'p', `return (${expression});`);
231
- return factory(page, page);
232
- }
233
- /**
234
- * How long a normal, actionability-checked `click` may wait before we treat the
235
- * element as un-clickable and fall back to a dispatched click. Short on purpose:
236
- * a hidden custom input never becomes actionable, so the regular click would
237
- * otherwise burn Playwright's full default timeout (30s) before the escape path
238
- * runs. The visible-element happy path clicks immediately and never hits this;
239
- * this bound is the latency cost paid ONLY on the hidden/non-actionable path,
240
- * and is long enough to tolerate a slow-but-eventually-actionable element
241
- * (animations, late layout) before deciding to dispatch.
242
- */
243
- const NORMAL_CLICK_TIMEOUT_MS = 1_000;
244
- /**
245
- * Run the `click` verb against a Playwright page (PRD story 8), shared by both
246
- * Playwright transports so the verb behaves identically (mirrors {@link waitFor};
247
- * the forward-note's "do NOT write a parallel second implementation").
248
- *
249
- * First try a normal `Locator.click()`, which AUTO-WAITS for the element to be
250
- * visible and actionable — the right behaviour for a real button. A hidden
251
- * custom input (the case the prd calls out) NEVER becomes actionable, so that
252
- * click times out; on a Playwright `TimeoutError` we fall back to
253
- * `dispatchEvent('click')`, which fires a click WITHOUT the actionability
254
- * checks. The fallback is deliberately the documented Playwright escape (a
255
- * sibling to the `eval` hatch, ADR-0004), not a reimplemented click: we keep
256
- * the locator a raw resolved expression and only change HOW the resolved
257
- * locator is clicked.
258
- *
259
- * Only a timeout triggers the fallback. The fallback `dispatchEvent` is itself
260
- * bounded by the same short timeout, so a locator that resolves NO element (a
261
- * bad locator) surfaces its timeout quickly instead of hanging the dispatch on
262
- * Playwright's 30s default — the dispatch escape is for elements that EXIST but
263
- * are not actionable (hidden custom inputs), not for absent ones.
264
- */
265
- export async function clickLocator(page, expression) {
266
- const target = resolveLocator(page, expression);
267
- try {
268
- await target.click({ timeout: NORMAL_CLICK_TIMEOUT_MS });
269
- }
270
- catch (cause) {
271
- if (!(cause instanceof pwErrors.TimeoutError)) {
272
- throw cause;
273
- }
274
- // The element never became actionable (e.g. a hidden custom input). Fire
275
- // the click without actionability checks, the prd's explicit escape path.
276
- await target.dispatchEvent('click', { timeout: NORMAL_CLICK_TIMEOUT_MS });
277
- }
278
- }
279
- /** Map a Playwright cookie to the transport-neutral seam {@link Cookie}. */
280
- function toSeamCookie(c) {
281
- return {
282
- name: c.name,
283
- value: c.value,
284
- domain: c.domain,
285
- path: c.path,
286
- expires: c.expires,
287
- httpOnly: c.httpOnly,
288
- secure: c.secure,
289
- sameSite: c.sameSite,
290
- };
291
- }
292
- /** Map a seam {@link Cookie} to a Playwright cookie shape. */
293
- function fromSeamCookie(c) {
294
- return {
295
- name: c.name,
296
- value: c.value,
297
- domain: c.domain,
298
- path: c.path,
299
- expires: c.expires,
300
- httpOnly: c.httpOnly,
301
- secure: c.secure,
302
- sameSite: c.sameSite,
303
150
  };
304
151
  }
305
152
  //# sourceMappingURL=playwright-launch-transport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-launch-transport.js","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"playwright-launch-transport.js","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import type { Session } from './seam.js';
4
4
  * long-lived `serve` process over HTTP (ADR-0005).
5
5
  *
6
6
  * Each `webhands <verb>` is a thin client: it cannot hold a JS
7
- * reference to the server's live page, so this proxy turns every {@link Page}
7
+ * reference to the server's live page, so this proxy turns every {@link WebHandsPage}
8
8
  * verb into a session-RPC call to the running server (see `session-rpc.ts`) and
9
9
  * returns the result. The verb command code is UNCHANGED — it still calls
10
10
  * `provider(target)` then runs verbs against the returned `Session.page` then
@@ -17,6 +17,16 @@ import type { Session } from './seam.js';
17
17
  * (`stop`), exactly as ADR-0005 requires. This is the whole reason cross-
18
18
  * invocation persistence works: the page state survives because the client's
19
19
  * `close()` does not reach across to the server's session.
20
+ *
21
+ * THIRD-PARTY HAND VERBS (Phase 2, Model B; ADR-0007). Pass the NAMES of the
22
+ * hand verbs the served process loaded as `handVerbs`; each is attached to the
23
+ * returned `page` as a dynamic method forwarding over the RPC via
24
+ * {@link callHandVerb}, so the agent gains those tools WITHOUT ever holding a
25
+ * live page handle. They are NOT on the seam `WebHandsPage` type (the seam knows only
26
+ * the eight built-ins), so a caller reaches them through a cast, exactly as a
27
+ * third-party hand verb is reached on the in-process composed page. The result
28
+ * crosses the wire as a serializable value and a page/in-hand throw rejects
29
+ * faithfully, the same contract as the built-in verbs.
20
30
  */
21
- export declare function connectRemoteSession(baseUrl: string): Session;
31
+ export declare function connectRemoteSession(baseUrl: string, handVerbs?: readonly string[]): Session;
22
32
  //# sourceMappingURL=remote-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote-session.d.ts","sourceRoot":"","sources":["../src/remote-session.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAsC7D"}
1
+ {"version":3,"file":"remote-session.d.ts","sourceRoot":"","sources":["../src/remote-session.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,SAAS,MAAM,EAAO,GAC/B,OAAO,CA8DT"}
@@ -1,10 +1,10 @@
1
- import { makeRpcPage, SESSION_RPC_PATH, } from './session-rpc.js';
1
+ import { callHandVerb, makeRpcPage, SESSION_RPC_PATH, } from './session-rpc.js';
2
2
  /**
3
3
  * A client-side {@link Session} that drives a session living in a SEPARATE
4
4
  * long-lived `serve` process over HTTP (ADR-0005).
5
5
  *
6
6
  * Each `webhands <verb>` is a thin client: it cannot hold a JS
7
- * reference to the server's live page, so this proxy turns every {@link Page}
7
+ * reference to the server's live page, so this proxy turns every {@link WebHandsPage}
8
8
  * verb into a session-RPC call to the running server (see `session-rpc.ts`) and
9
9
  * returns the result. The verb command code is UNCHANGED — it still calls
10
10
  * `provider(target)` then runs verbs against the returned `Session.page` then
@@ -17,8 +17,18 @@ import { makeRpcPage, SESSION_RPC_PATH, } from './session-rpc.js';
17
17
  * (`stop`), exactly as ADR-0005 requires. This is the whole reason cross-
18
18
  * invocation persistence works: the page state survives because the client's
19
19
  * `close()` does not reach across to the server's session.
20
+ *
21
+ * THIRD-PARTY HAND VERBS (Phase 2, Model B; ADR-0007). Pass the NAMES of the
22
+ * hand verbs the served process loaded as `handVerbs`; each is attached to the
23
+ * returned `page` as a dynamic method forwarding over the RPC via
24
+ * {@link callHandVerb}, so the agent gains those tools WITHOUT ever holding a
25
+ * live page handle. They are NOT on the seam `WebHandsPage` type (the seam knows only
26
+ * the eight built-ins), so a caller reaches them through a cast, exactly as a
27
+ * third-party hand verb is reached on the in-process composed page. The result
28
+ * crosses the wire as a serializable value and a page/in-hand throw rejects
29
+ * faithfully, the same contract as the built-in verbs.
20
30
  */
21
- export function connectRemoteSession(baseUrl) {
31
+ export function connectRemoteSession(baseUrl, handVerbs = []) {
22
32
  const endpoint = new URL(SESSION_RPC_PATH, baseUrl).toString();
23
33
  const send = async (request) => {
24
34
  let res;
@@ -46,11 +56,32 @@ export function connectRemoteSession(baseUrl) {
46
56
  // process boundary.
47
57
  throw new Error(reply.error);
48
58
  };
59
+ let resolveClosed;
60
+ const closedSignal = new Promise((resolve) => {
61
+ resolveClosed = resolve;
62
+ });
63
+ const page = makeRpcPage(send);
64
+ // Attach each loaded hand verb as a dynamic method that forwards over the same
65
+ // RPC `send`. The seam `WebHandsPage` type names only the built-ins, so these live on
66
+ // the runtime object alongside them (mirroring how a hand verb composes into
67
+ // the in-process page object); callers reach them through a cast.
68
+ const pageWithHands = page;
69
+ for (const name of handVerbs) {
70
+ pageWithHands[name] = (...args) => callHandVerb(send, name, ...args);
71
+ }
49
72
  return {
50
- page: makeRpcPage(send),
73
+ page,
51
74
  async close() {
52
- // Intentionally a no-op: the served process owns the session's lifetime
53
- // (see this module's overview). Teardown is the explicit `stop` verb.
75
+ // Intentionally a no-op against the SERVER: the served process owns the
76
+ // session's lifetime (see this module's overview). Teardown is the
77
+ // explicit `stop` verb. We still resolve the local close signal so a
78
+ // caller awaiting waitForClose() on this client handle unblocks.
79
+ resolveClosed();
80
+ },
81
+ waitForClose() {
82
+ // A client never waits on the user closing the window — that is the
83
+ // server's concern; this resolves on a local close() call.
84
+ return closedSignal;
54
85
  },
55
86
  };
56
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remote-session.js","sourceRoot":"","sources":["../src/remote-session.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,WAAW,EACX,gBAAgB,GAGhB,MAAM,kBAAkB,CAAC;AAG1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IACnD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAE/D,MAAM,IAAI,GAAG,KAAK,EAAE,OAA0B,EAAoB,EAAE;QACnE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACJ,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAC,cAAc,EAAE,kBAAkB,EAAC;gBAC7C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,qEAAqE;YACrE,yEAAyE;YACzE,wEAAwE;YACxE,mEAAmE;YACnE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,KAAK,CACd,yCAAyC,OAAO,KAAK,OAAO,EAAE,CAC9D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuB,CAAC;QACvD,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;YACd,OAAO,KAAK,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,4EAA4E;QAC5E,0EAA0E;QAC1E,oBAAoB;QACpB,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,OAAO;QACN,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;QACvB,KAAK,CAAC,KAAK;YACV,wEAAwE;YACxE,sEAAsE;QACvE,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"remote-session.js","sourceRoot":"","sources":["../src/remote-session.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,WAAW,EACX,gBAAgB,GAGhB,MAAM,kBAAkB,CAAC;AAG1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAe,EACf,YAA+B,EAAE;IAEjC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAE/D,MAAM,IAAI,GAAG,KAAK,EAAE,OAA0B,EAAoB,EAAE;QACnE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACJ,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAC,cAAc,EAAE,kBAAkB,EAAC;gBAC7C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC7B,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,qEAAqE;YACrE,yEAAyE;YACzE,wEAAwE;YACxE,mEAAmE;YACnE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,KAAK,CACd,yCAAyC,OAAO,KAAK,OAAO,EAAE,CAC9D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuB,CAAC;QACvD,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;YACd,OAAO,KAAK,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,4EAA4E;QAC5E,0EAA0E;QAC1E,oBAAoB;QACpB,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,IAAI,aAA0B,CAAC;IAC/B,MAAM,YAAY,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClD,aAAa,GAAG,OAAO,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,+EAA+E;IAC/E,sFAAsF;IACtF,6EAA6E;IAC7E,kEAAkE;IAClE,MAAM,aAAa,GAAG,IAA0C,CAAC;IACjE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC9B,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAwB,EAAoB,EAAE,CACvE,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,OAAO;QACN,IAAI;QACJ,KAAK,CAAC,KAAK;YACV,wEAAwE;YACxE,mEAAmE;YACnE,qEAAqE;YACrE,iEAAiE;YACjE,aAAa,EAAE,CAAC;QACjB,CAAC;QACD,YAAY;YACX,oEAAoE;YACpE,2DAA2D;YAC3D,OAAO,YAAY,CAAC;QACrB,CAAC;KACD,CAAC;AACH,CAAC"}
package/dist/seam.d.ts CHANGED
@@ -65,7 +65,7 @@ export interface Cookie {
65
65
  readonly secure?: boolean;
66
66
  readonly sameSite?: 'Strict' | 'Lax' | 'None';
67
67
  }
68
- /** What to wait for in the {@link Page.wait} verb. */
68
+ /** What to wait for in the {@link WebHandsPage.wait} verb. */
69
69
  export type WaitCondition = {
70
70
  readonly kind: 'timeout';
71
71
  readonly ms: number;
@@ -87,7 +87,7 @@ export type WaitCondition = {
87
87
  * `needsAnswers` Q3): default is the accessibility view, `--full` is raw DOM.
88
88
  */
89
89
  export type SnapshotView = 'accessibility' | 'full';
90
- /** Options for the {@link Page.snapshot} verb. */
90
+ /** Options for the {@link WebHandsPage.snapshot} verb. */
91
91
  export interface SnapshotOptions {
92
92
  /**
93
93
  * When `true`, return the raw DOM (`view: 'full'`) instead of the default
@@ -124,7 +124,7 @@ export interface Snapshot {
124
124
  * The page-level verb surface. One method per verb in the domain glossary.
125
125
  * All element addressing flows through {@link LocatorString}.
126
126
  */
127
- export interface Page {
127
+ export interface WebHandsPage {
128
128
  /** Navigate the active page to a URL and let it settle. */
129
129
  navigate(url: string): Promise<void>;
130
130
  /**
@@ -187,16 +187,24 @@ export interface Page {
187
187
  setCookies(cookies: readonly Cookie[]): Promise<void>;
188
188
  }
189
189
  /**
190
- * A live browser session owning one active {@link Page}. The session lifetime
190
+ * A live browser session owning one active {@link WebHandsPage}. The session lifetime
191
191
  * spans from {@link Transport.open} to {@link Session.close}; it is the unit a
192
192
  * long-lived controller process keeps between CLI invocations (PRD
193
193
  * "session/daemon question").
194
194
  */
195
195
  export interface Session {
196
196
  /** The active page the verbs act on. */
197
- readonly page: Page;
197
+ readonly page: WebHandsPage;
198
198
  /** Tear down the session and release the underlying browser resources. */
199
199
  close(): Promise<void>;
200
+ /**
201
+ * Resolve when the session is closed — either by the USER closing the browser
202
+ * window/context, or by a {@link Session.close} call. This is what lets a
203
+ * headed flow (notably `setup-profile`) HOLD the window open and block until
204
+ * the human is done, instead of tearing it down immediately. Resolves once
205
+ * (idempotent); resolves immediately if the session is already closed.
206
+ */
207
+ waitForClose(): Promise<void>;
200
208
  }
201
209
  /**
202
210
  * The transport seam. A `Transport` (a.k.a. driver) knows how to OPEN a
@@ -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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"}
@@ -1,9 +1,9 @@
1
1
  import { type Cookie, type WaitCondition } from './seam.js';
2
- import type { Page } from './seam.js';
2
+ import type { WebHandsPage } from './seam.js';
3
3
  /**
4
4
  * The wire protocol for driving the long-lived session over HTTP (ADR-0005).
5
5
  *
6
- * The served process holds ONE live {@link Page} in memory; a thin client verb
6
+ * The served process holds ONE live {@link WebHandsPage} in memory; a thin client verb
7
7
  * cannot hold a JS reference to it across the process boundary, so each verb
8
8
  * call is sent as a small JSON request to the server, which runs it against its
9
9
  * live page and returns the result. This module is the SINGLE source of truth
@@ -11,16 +11,46 @@ import type { Page } from './seam.js';
11
11
  * client proxy so they cannot drift (mirrors how `serializeCookies` is shared
12
12
  * by the cookies verb and its test).
13
13
  *
14
- * It is a thin transport detail, NOT a second verb surface: every request maps
15
- * 1:1 to a {@link Page} method, and the seam's verb semantics (ADR-0003/0004)
16
- * are unchanged. The {@link LocatorString} brand and the structured
17
- * {@link WaitCondition} cross as plain JSON and are re-branded on the server
18
- * with {@link locator}; no Playwright/CDP type is ever named here.
14
+ * It is a thin transport detail, NOT a second verb surface: every built-in
15
+ * request maps 1:1 to a {@link WebHandsPage} method, and the seam's verb semantics
16
+ * (ADR-0003/0004) are unchanged. The {@link LocatorString} brand and the
17
+ * structured {@link WaitCondition} cross as plain JSON and are re-branded on the
18
+ * server with {@link locator}; no Playwright/CDP type is ever named here.
19
+ *
20
+ * THIRD-PARTY HAND VERBS (Phase 2, Model B of the "hands" prd; ADR-0007). The
21
+ * eight built-in verbs stay a CLOSED union (the 1:1 source of truth above). A
22
+ * dynamically-loaded hand contributes a verb whose name `core` does NOT know at
23
+ * compile time, so it cannot be a named member of that closed union without
24
+ * re-meaning "closed". Instead it crosses as a SINGLE generic
25
+ * {@link SessionRpcHandRequest} variant (`{verb: 'hand', name, args}`) that
26
+ * names the contributed verb at runtime and carries its arguments. This is the
27
+ * exact wire parallel of how a hand verb composes into the page object: by name,
28
+ * dynamically, alongside the typed built-ins. The agent thereby gains a new tool
29
+ * over the wire WITHOUT ever holding a live page handle.
30
+ *
31
+ * SERIALIZATION BOUNDARY (the load-bearing rule; prd's resolved Q3). A hand
32
+ * verb's result crosses this RPC, so it MUST be serializable under the same
33
+ * structured-clone contract `eval` documents (see {@link WebHandsPage.eval}): richer
34
+ * than JSON, but a value with no transferable form does not round-trip. This is
35
+ * enforced by CONVENTION + TYPES (a hand author returns serializable values),
36
+ * NOT a blanket runtime clone here — a blanket clone would corrupt legitimate
37
+ * in-process (Model A) returns, where a hand may pass/return live Playwright
38
+ * handles within a single in-process call chain. A host-side runtime clone of
39
+ * agent-verb results is available HARDENING for untrusted hands, not built here.
40
+ * A page/in-hand throw REJECTS faithfully on the client exactly as the `eval`
41
+ * path already does (the server maps it to an `ok: false` reply carrying the
42
+ * message; the client re-throws a faithful `Error`).
19
43
  */
20
44
  /** The path the session RPC is served under, below the server's base URL. */
21
45
  export declare const SESSION_RPC_PATH = "/session/call";
22
46
  /** A single verb call to run against the served live page. */
23
- export type SessionRpcRequest = {
47
+ export type SessionRpcRequest = SessionRpcBuiltInRequest | SessionRpcHandRequest;
48
+ /**
49
+ * The CLOSED union of webhands' eight built-in verbs. Each variant maps 1:1 to a
50
+ * {@link WebHandsPage} method in {@link applySessionRpc}; this is the single source of
51
+ * truth for the built-in verb surface, shared verbatim by server and client.
52
+ */
53
+ export type SessionRpcBuiltInRequest = {
24
54
  readonly verb: 'navigate';
25
55
  readonly url: string;
26
56
  } | {
@@ -45,6 +75,24 @@ export type SessionRpcRequest = {
45
75
  readonly verb: 'setCookies';
46
76
  readonly cookies: readonly Cookie[];
47
77
  };
78
+ /**
79
+ * The OPEN escape for a dynamically-loaded third-party hand verb (Phase 2,
80
+ * Model B; ADR-0007). Unlike the closed built-in union, `core` does not know the
81
+ * verb's name at compile time, so it is carried at runtime: `name` is the
82
+ * contributed verb's plain name (exactly as it composed into the page object,
83
+ * not namespaced — a hand may even deliberately override a built-in, the
84
+ * operator's choice per ADR-0007) and `args` are its JSON arguments.
85
+ *
86
+ * The returned value and any thrown error obey the same serialization +
87
+ * rejection contract as `eval` (see this module's overview and {@link WebHandsPage.eval}).
88
+ */
89
+ export interface SessionRpcHandRequest {
90
+ readonly verb: 'hand';
91
+ /** The hand-contributed verb's name (as it composed into the page object). */
92
+ readonly name: string;
93
+ /** The verb's arguments, carried as plain JSON (must be serializable). */
94
+ readonly args: readonly unknown[];
95
+ }
48
96
  /**
49
97
  * The server's reply to a {@link SessionRpcRequest}. `ok: true` carries the
50
98
  * verb's return value (for verbs that return data); `ok: false` carries the
@@ -59,7 +107,7 @@ export type SessionRpcResponse = {
59
107
  readonly error: string;
60
108
  };
61
109
  /**
62
- * Run one {@link SessionRpcRequest} against a live {@link Page}, returning the
110
+ * Run one {@link SessionRpcRequest} against a live {@link WebHandsPage}, returning the
63
111
  * value the wire should carry back. The server's HTTP handler is just this plus
64
112
  * JSON framing; keeping the dispatch here (not inline in the handler) means the
65
113
  * verb-to-page mapping is in one place and unit-testable without HTTP.
@@ -68,9 +116,9 @@ export type SessionRpcResponse = {
68
116
  * locator with {@link locator} before handing it to the page so the seam's
69
117
  * branded-string contract holds.
70
118
  */
71
- export declare function applySessionRpc(page: Page, request: SessionRpcRequest): Promise<unknown>;
119
+ export declare function applySessionRpc(page: WebHandsPage, request: SessionRpcRequest): Promise<unknown>;
72
120
  /**
73
- * A {@link Page} whose verbs forward to a server via the supplied transport.
121
+ * A {@link WebHandsPage} whose verbs forward to a server via the supplied transport.
74
122
  *
75
123
  * Used by the client-side proxy (see `remote-session.ts`): each verb builds a
76
124
  * {@link SessionRpcRequest}, hands it to `send`, and shapes the reply back into
@@ -78,5 +126,21 @@ export declare function applySessionRpc(page: Page, request: SessionRpcRequest):
78
126
  * the verb-to-request mapping (the other half of {@link applySessionRpc}) in
79
127
  * one place so request and response shapes cannot drift between the two sides.
80
128
  */
81
- export declare function makeRpcPage(send: (request: SessionRpcRequest) => Promise<unknown>): Page;
129
+ export declare function makeRpcPage(send: (request: SessionRpcRequest) => Promise<unknown>): WebHandsPage;
130
+ /**
131
+ * Invoke a dynamically-loaded hand verb over the session RPC by name (Phase 2,
132
+ * Model B; ADR-0007). The client-side mirror of {@link applyHandVerb}: it builds
133
+ * the single generic {@link SessionRpcHandRequest} and hands it to the SAME
134
+ * `send` the built-in verbs use, so request/response shapes cannot drift.
135
+ *
136
+ * This is how the agent gains a new tool over the wire WITHOUT holding a live
137
+ * page handle: it names the contributed verb and passes serializable args; the
138
+ * server runs the hand against its own live page and returns a serializable
139
+ * result. A page/in-hand throw rejects faithfully (the `send` re-throws the
140
+ * server's error message), exactly as the `eval` path does.
141
+ *
142
+ * The result type is `unknown` because the hand decides the shape; callers
143
+ * narrow it (mirrors {@link WebHandsPage.eval}).
144
+ */
145
+ export declare function callHandVerb(send: (request: SessionRpcRequest) => Promise<unknown>, name: string, ...args: readonly unknown[]): Promise<unknown>;
82
146
  //# sourceMappingURL=session-rpc.d.ts.map