@webhands/core 0.1.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 (76) hide show
  1. package/dist/cookies-export.d.ts +56 -0
  2. package/dist/cookies-export.d.ts.map +1 -0
  3. package/dist/cookies-export.js +69 -0
  4. package/dist/cookies-export.js.map +1 -0
  5. package/dist/errors.d.ts +126 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +135 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +15 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/playwright-attach-transport.d.ts +28 -0
  14. package/dist/playwright-attach-transport.d.ts.map +1 -0
  15. package/dist/playwright-attach-transport.js +175 -0
  16. package/dist/playwright-attach-transport.js.map +1 -0
  17. package/dist/playwright-launch-transport.d.ts +90 -0
  18. package/dist/playwright-launch-transport.d.ts.map +1 -0
  19. package/dist/playwright-launch-transport.js +305 -0
  20. package/dist/playwright-launch-transport.js.map +1 -0
  21. package/dist/profile-location.d.ts +61 -0
  22. package/dist/profile-location.d.ts.map +1 -0
  23. package/dist/profile-location.js +61 -0
  24. package/dist/profile-location.js.map +1 -0
  25. package/dist/remote-session.d.ts +22 -0
  26. package/dist/remote-session.d.ts.map +1 -0
  27. package/dist/remote-session.js +57 -0
  28. package/dist/remote-session.js.map +1 -0
  29. package/dist/seam.d.ts +212 -0
  30. package/dist/seam.d.ts.map +1 -0
  31. package/dist/seam.js +25 -0
  32. package/dist/seam.js.map +1 -0
  33. package/dist/session-endpoint.d.ts +53 -0
  34. package/dist/session-endpoint.d.ts.map +1 -0
  35. package/dist/session-endpoint.js +75 -0
  36. package/dist/session-endpoint.js.map +1 -0
  37. package/dist/session-rpc.d.ts +82 -0
  38. package/dist/session-rpc.d.ts.map +1 -0
  39. package/dist/session-rpc.js +107 -0
  40. package/dist/session-rpc.js.map +1 -0
  41. package/dist/session-server.d.ts +79 -0
  42. package/dist/session-server.d.ts.map +1 -0
  43. package/dist/session-server.js +141 -0
  44. package/dist/session-server.js.map +1 -0
  45. package/dist/setup-profile.d.ts +84 -0
  46. package/dist/setup-profile.d.ts.map +1 -0
  47. package/dist/setup-profile.js +52 -0
  48. package/dist/setup-profile.js.map +1 -0
  49. package/dist/stub-transport.d.ts +26 -0
  50. package/dist/stub-transport.d.ts.map +1 -0
  51. package/dist/stub-transport.js +76 -0
  52. package/dist/stub-transport.js.map +1 -0
  53. package/dist/test-fixtures/fixture-pages.d.ts +12 -0
  54. package/dist/test-fixtures/fixture-pages.d.ts.map +1 -0
  55. package/dist/test-fixtures/fixture-pages.js +204 -0
  56. package/dist/test-fixtures/fixture-pages.js.map +1 -0
  57. package/dist/test-fixtures/fixture-server.d.ts +19 -0
  58. package/dist/test-fixtures/fixture-server.d.ts.map +1 -0
  59. package/dist/test-fixtures/fixture-server.js +41 -0
  60. package/dist/test-fixtures/fixture-server.js.map +1 -0
  61. package/package.json +34 -0
  62. package/src/cookies-export.ts +91 -0
  63. package/src/errors.ts +185 -0
  64. package/src/index.ts +89 -0
  65. package/src/playwright-attach-transport.ts +214 -0
  66. package/src/playwright-launch-transport.ts +363 -0
  67. package/src/profile-location.ts +92 -0
  68. package/src/remote-session.ts +66 -0
  69. package/src/seam.ts +222 -0
  70. package/src/session-endpoint.ts +104 -0
  71. package/src/session-rpc.ts +143 -0
  72. package/src/session-server.ts +231 -0
  73. package/src/setup-profile.ts +134 -0
  74. package/src/stub-transport.ts +100 -0
  75. package/src/test-fixtures/fixture-pages.ts +210 -0
  76. package/src/test-fixtures/fixture-server.ts +54 -0
@@ -0,0 +1,61 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Where the controller's dedicated profiles (and other config state) live.
5
+ *
6
+ * This is a SHARED/GLOBAL, per-user location: by default
7
+ * `~/.webhands`. Profiles are dedicated browser user-data dirs
8
+ * under `<root>/profiles/<name>` (PRD "Profile management"; ADR-0002: never the
9
+ * OS default Chrome profile). The endpoint file from ADR-0005 also lives under
10
+ * this root, owned by a later task.
11
+ *
12
+ * Because writing here touches a real, shared location, TESTS MUST override the
13
+ * root to a scratch dir and assert the real one is untouched. The override is
14
+ * the {@link CONTROLLER_HOME_ENV} environment variable (or an explicit
15
+ * `root` passed to {@link resolveProfileLocation}); nothing else points a
16
+ * launch at the real home.
17
+ */
18
+ /** The directory name appended to the user's home for the default root. */
19
+ export const DEFAULT_HOME_DIRNAME = '.webhands';
20
+ /**
21
+ * Environment variable that overrides the controller home root. Set this (to a
22
+ * temp dir) in tests, or to relocate state in production. When set to a
23
+ * non-empty value it fully replaces the `~/.webhands` default.
24
+ */
25
+ export const CONTROLLER_HOME_ENV = 'WEBHANDS_HOME';
26
+ /** The subdirectory under the home root that holds dedicated profiles. */
27
+ export const PROFILES_DIRNAME = 'profiles';
28
+ /**
29
+ * Resolve the controller home root. Precedence:
30
+ * 1. an explicit `options.root`,
31
+ * 2. the {@link CONTROLLER_HOME_ENV} env var (if non-empty),
32
+ * 3. `~/.webhands`.
33
+ */
34
+ export function resolveHomeRoot(options = {}) {
35
+ if (options.root !== undefined && options.root !== '') {
36
+ return options.root;
37
+ }
38
+ const env = options.env ?? process.env;
39
+ const fromEnv = env[CONTROLLER_HOME_ENV];
40
+ if (fromEnv !== undefined && fromEnv !== '') {
41
+ return fromEnv;
42
+ }
43
+ return join(homedir(), DEFAULT_HOME_DIRNAME);
44
+ }
45
+ /**
46
+ * Resolve every path for a named profile. Does NOT touch the filesystem (no
47
+ * dir is created or checked here) so it is pure and safe to call freely; the
48
+ * transport decides what to do when the dir is absent (raise
49
+ * `MissingProfileError`) or present.
50
+ */
51
+ export function resolveProfileLocation(profile, options = {}) {
52
+ const homeRoot = resolveHomeRoot(options);
53
+ const profilesRoot = join(homeRoot, PROFILES_DIRNAME);
54
+ return {
55
+ homeRoot,
56
+ profilesRoot,
57
+ profileDir: join(profilesRoot, profile),
58
+ profile,
59
+ };
60
+ }
61
+ //# sourceMappingURL=profile-location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-location.js","sourceRoot":"","sources":["../src/profile-location.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAE/B;;;;;;;;;;;;;;GAcG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAyB3C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IACnE,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC7C,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,oBAAoB,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACrC,OAAe,EACf,UAAkC,EAAE;IAEpC,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACtD,OAAO;QACN,QAAQ;QACR,YAAY;QACZ,UAAU,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;QACvC,OAAO;KACP,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Session } from './seam.js';
2
+ /**
3
+ * A client-side {@link Session} that drives a session living in a SEPARATE
4
+ * long-lived `serve` process over HTTP (ADR-0005).
5
+ *
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}
8
+ * verb into a session-RPC call to the running server (see `session-rpc.ts`) and
9
+ * returns the result. The verb command code is UNCHANGED — it still calls
10
+ * `provider(target)` then runs verbs against the returned `Session.page` then
11
+ * calls `Session.close()`; only WHAT the session is changes.
12
+ *
13
+ * The critical difference from a local session: {@link Session.close} here is a
14
+ * NO-OP. The served process owns the single live session's lifetime; a thin
15
+ * client closing after one verb must NOT tear down the shared browser, or the
16
+ * next verb invocation would have nothing to drive. Teardown is explicit
17
+ * (`stop`), exactly as ADR-0005 requires. This is the whole reason cross-
18
+ * invocation persistence works: the page state survives because the client's
19
+ * `close()` does not reach across to the server's session.
20
+ */
21
+ export declare function connectRemoteSession(baseUrl: string): Session;
22
+ //# sourceMappingURL=remote-session.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,57 @@
1
+ import { makeRpcPage, SESSION_RPC_PATH, } from './session-rpc.js';
2
+ /**
3
+ * A client-side {@link Session} that drives a session living in a SEPARATE
4
+ * long-lived `serve` process over HTTP (ADR-0005).
5
+ *
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}
8
+ * verb into a session-RPC call to the running server (see `session-rpc.ts`) and
9
+ * returns the result. The verb command code is UNCHANGED — it still calls
10
+ * `provider(target)` then runs verbs against the returned `Session.page` then
11
+ * calls `Session.close()`; only WHAT the session is changes.
12
+ *
13
+ * The critical difference from a local session: {@link Session.close} here is a
14
+ * NO-OP. The served process owns the single live session's lifetime; a thin
15
+ * client closing after one verb must NOT tear down the shared browser, or the
16
+ * next verb invocation would have nothing to drive. Teardown is explicit
17
+ * (`stop`), exactly as ADR-0005 requires. This is the whole reason cross-
18
+ * invocation persistence works: the page state survives because the client's
19
+ * `close()` does not reach across to the server's session.
20
+ */
21
+ export function connectRemoteSession(baseUrl) {
22
+ const endpoint = new URL(SESSION_RPC_PATH, baseUrl).toString();
23
+ const send = async (request) => {
24
+ let res;
25
+ try {
26
+ res = await fetch(endpoint, {
27
+ method: 'POST',
28
+ headers: { 'content-type': 'application/json' },
29
+ body: JSON.stringify(request),
30
+ });
31
+ }
32
+ catch (cause) {
33
+ // The advertised server is unreachable (it died without clearing its
34
+ // endpoint file, say). Surface a plain Error; the CLI maps the discovery
35
+ // MISS (no endpoint file) to "run serve first", but a stale-but-present
36
+ // endpoint that no longer answers is a genuine connection failure.
37
+ const message = cause instanceof Error ? cause.message : String(cause);
38
+ throw new Error(`could not reach the session server at ${baseUrl}: ${message}`);
39
+ }
40
+ const reply = (await res.json());
41
+ if (reply.ok) {
42
+ return reply.value;
43
+ }
44
+ // Re-throw a faithful Error so a page-side throw REJECTS on the client too,
45
+ // preserving the seam's `eval` "a page throw rejects" contract across the
46
+ // process boundary.
47
+ throw new Error(reply.error);
48
+ };
49
+ return {
50
+ page: makeRpcPage(send),
51
+ 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.
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=remote-session.js.map
@@ -0,0 +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"}
package/dist/seam.d.ts ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The verb-level transport seam.
3
+ *
4
+ * This is the highest test seam and the internal structure boundary of
5
+ * `core` (see PRD "Testing Decisions" and `docs/adr/0003`). It is expressed
6
+ * purely in terms of high-level VERBS (navigate, snapshot, click, type, eval,
7
+ * wait, cookies), NOT in terms of CDP or Playwright primitives, so that a
8
+ * future browser-extension transport or a non-Chromium (Firefox) transport
9
+ * can implement it without changing the verb surface.
10
+ *
11
+ * RULES (load-bearing, do not relax without an ADR):
12
+ * - No CDP / Chromium-only types may appear in this public surface
13
+ * (`docs/adr/0003`).
14
+ * - Element addressing is a RAW PLAYWRIGHT LOCATOR STRING the active
15
+ * transport resolves (`docs/adr/0004`): "transport-neutral" means
16
+ * Playwright-equivalent addressing, not a reduced selector subset and not a
17
+ * structured JSON locator. We deliberately type it as a branded `string`
18
+ * rather than importing any Playwright `Locator` type, so no Playwright
19
+ * type leaks across the seam.
20
+ */
21
+ /**
22
+ * A raw Playwright locator string, e.g. `getByRole('button', { name: 'Search' })`.
23
+ *
24
+ * It is a plain `string` at runtime; the brand exists only so call sites are
25
+ * explicit that this is a locator EXPRESSION the transport resolves (a sibling
26
+ * to the `eval` escape hatch), not an opaque CSS selector or a structured
27
+ * locator. Construct one with {@link locator}.
28
+ */
29
+ export type LocatorString = string & {
30
+ readonly __brand: 'PlaywrightLocatorString';
31
+ };
32
+ /** Tag a raw Playwright locator string as a {@link LocatorString}. */
33
+ export declare function locator(expression: string): LocatorString;
34
+ /**
35
+ * How a {@link Transport} should obtain a browser session.
36
+ *
37
+ * Expressed in domain terms (a profile to launch, or a target to attach to),
38
+ * never in CDP/Playwright terms. Concrete transports map these to their own
39
+ * mechanism (Playwright `launchPersistentContext` / `connectOverCDP`, an
40
+ * extension bridge, ...).
41
+ */
42
+ export type OpenTarget = {
43
+ readonly mode: 'launch';
44
+ /** Name of the dedicated profile the controller owns. */
45
+ readonly profile: string;
46
+ /** Whether the browser is visible. Defaults are a transport concern. */
47
+ readonly headed?: boolean;
48
+ } | {
49
+ readonly mode: 'attach';
50
+ /**
51
+ * Opaque, transport-resolved endpoint of an already-running browser
52
+ * (e.g. a remote-debugging URL). Kept as a plain string so no
53
+ * CDP type leaks across the seam.
54
+ */
55
+ readonly endpoint: string;
56
+ };
57
+ /** A single browser cookie, in transport-neutral terms. */
58
+ export interface Cookie {
59
+ readonly name: string;
60
+ readonly value: string;
61
+ readonly domain?: string;
62
+ readonly path?: string;
63
+ readonly expires?: number;
64
+ readonly httpOnly?: boolean;
65
+ readonly secure?: boolean;
66
+ readonly sameSite?: 'Strict' | 'Lax' | 'None';
67
+ }
68
+ /** What to wait for in the {@link Page.wait} verb. */
69
+ export type WaitCondition = {
70
+ readonly kind: 'timeout';
71
+ readonly ms: number;
72
+ } | {
73
+ readonly kind: 'locator';
74
+ readonly target: LocatorString;
75
+ } | {
76
+ readonly kind: 'navigation';
77
+ };
78
+ /**
79
+ * Which page view a {@link Snapshot} carries.
80
+ *
81
+ * - `'accessibility'` — the DEFAULT, token-cheap structured view: the
82
+ * accessibility tree (roles + names) plus visible text, with stable element
83
+ * refs (see {@link Snapshot.content}). This is the cheap view an agent reads
84
+ * to decide what to act on WITHOUT parsing raw HTML.
85
+ * - `'full'` — the raw DOM (serialized outer HTML), returned when the verb is
86
+ * called with {@link SnapshotOptions.full}. A settled PRD decision (story 7,
87
+ * `needsAnswers` Q3): default is the accessibility view, `--full` is raw DOM.
88
+ */
89
+ export type SnapshotView = 'accessibility' | 'full';
90
+ /** Options for the {@link Page.snapshot} verb. */
91
+ export interface SnapshotOptions {
92
+ /**
93
+ * When `true`, return the raw DOM (`view: 'full'`) instead of the default
94
+ * accessibility-tree + visible-text view. Maps to the CLI `--full` flag.
95
+ */
96
+ readonly full?: boolean;
97
+ }
98
+ /**
99
+ * A structured, token-cheap view of the current page with stable element refs.
100
+ *
101
+ * In the default `'accessibility'` view, {@link Snapshot.content} is the
102
+ * accessibility tree (roles + accessible names) plus visible text, with each
103
+ * actionable node carrying a stable `[ref=...]` element reference. The refs
104
+ * are stable for an unchanged page (re-snapshotting yields the same refs), so
105
+ * an agent can read the cheap view, pick a ref, and address that element
106
+ * later. Snapshot refs and the raw Playwright-locator addressing (ADR-0004)
107
+ * are COMPLEMENTARY ways to address elements, not competitors.
108
+ *
109
+ * The `content` string is a transport-neutral, human/agent-readable text
110
+ * serialization (no CDP/Playwright types cross the seam, per ADR-0003). Its
111
+ * concrete grammar is a transport detail; callers treat it as opaque,
112
+ * token-cheap text to read, and parse refs out of it only by the documented
113
+ * `[ref=...]` convention.
114
+ */
115
+ export interface Snapshot {
116
+ /** The page URL at snapshot time. */
117
+ readonly url: string;
118
+ /** Which view this snapshot carries (default vs `--full` raw DOM). */
119
+ readonly view: SnapshotView;
120
+ /** Human/agent-readable structured page content (see {@link Snapshot}). */
121
+ readonly content: string;
122
+ }
123
+ /**
124
+ * The page-level verb surface. One method per verb in the domain glossary.
125
+ * All element addressing flows through {@link LocatorString}.
126
+ */
127
+ export interface Page {
128
+ /** Navigate the active page to a URL and let it settle. */
129
+ navigate(url: string): Promise<void>;
130
+ /**
131
+ * Return a structured, token-cheap view of the page. Defaults to the
132
+ * accessibility-tree + visible-text view with stable refs; pass
133
+ * `{full: true}` to get the raw DOM instead (PRD story 7).
134
+ */
135
+ 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>;
140
+ /**
141
+ * Run a JavaScript EXPRESSION in the active page's context and return its
142
+ * result, the `eval` escape hatch for cases no other verb covers (PRD story
143
+ * 9). It sits naturally beside the raw-locator addressing (ADR-0004): both are
144
+ * page-context expressions the transport resolves.
145
+ *
146
+ * `expression` is evaluated AS AN EXPRESSION (not a function body), so its
147
+ * value is the result: `'1 + 2'` yields `3`, `"document.title"` yields the
148
+ * title. If it evaluates to a Promise, the transport awaits it and returns the
149
+ * resolved value.
150
+ *
151
+ * SERIALIZATION CONTRACT (the load-bearing part). The result must cross the
152
+ * seam by VALUE: it is structurally cloned out of the page context, not
153
+ * handed back as a live reference. The transport, not this verb, owns the
154
+ * serialization, and its behaviour is the documented contract callers rely
155
+ * on. It is RICHER than `JSON.stringify` (do not reason about it as JSON):
156
+ * - Primitives, plain objects, and arrays round-trip faithfully, including
157
+ * nested structures.
158
+ * - `undefined` round-trips as `undefined`; `null` as `null`.
159
+ * - Non-finite numbers (`NaN`, `Infinity`, `-0`) and `BigInt` are PRESERVED
160
+ * as their real JS values (unlike JSON, which would lose them).
161
+ * - A circular structure is PRESERVED, with each back-reference replaced by a
162
+ * `[Circular]` marker (it does NOT throw).
163
+ * - Values with no transferable form (functions, symbols) come back as
164
+ * `undefined`; a `Date` comes back as a `Date`; `Map`/`Set` come back as an
165
+ * empty object `{}` (their entries do not survive the clone).
166
+ * - Live host objects (a DOM node, `window`) come back as an OPAQUE PREVIEW
167
+ * STRING, NOT the live object: it cannot cross the process boundary, so the
168
+ * escape hatch hands back a readable stand-in rather than a broken handle.
169
+ * An agent that needs a DOM value reads a serializable property of it
170
+ * (`...textContent`, `...value`) inside the expression, exactly as the
171
+ * tests do.
172
+ * - An expression that THROWS in the page REJECTS with a transport-neutral
173
+ * `Error` carrying the page-side message (no CDP/Playwright type leaks
174
+ * across the seam, ADR-0003).
175
+ *
176
+ * The return type is `unknown` because the page decides the shape; callers
177
+ * narrow it. This is deliberately a thin passthrough to the transport's
178
+ * serialize-and-return: `eval` does not re-encode or wrap the result, so an
179
+ * agent gets exactly what the page produced.
180
+ */
181
+ eval(expression: string): Promise<unknown>;
182
+ /** Pace actions by waiting for a condition. */
183
+ wait(condition: WaitCondition): Promise<void>;
184
+ /** Read the session's cookies. */
185
+ cookies(): Promise<readonly Cookie[]>;
186
+ /** Seed the session's cookies. */
187
+ setCookies(cookies: readonly Cookie[]): Promise<void>;
188
+ }
189
+ /**
190
+ * A live browser session owning one active {@link Page}. The session lifetime
191
+ * spans from {@link Transport.open} to {@link Session.close}; it is the unit a
192
+ * long-lived controller process keeps between CLI invocations (PRD
193
+ * "session/daemon question").
194
+ */
195
+ export interface Session {
196
+ /** The active page the verbs act on. */
197
+ readonly page: Page;
198
+ /** Tear down the session and release the underlying browser resources. */
199
+ close(): Promise<void>;
200
+ }
201
+ /**
202
+ * The transport seam. A `Transport` (a.k.a. driver) knows how to OPEN a
203
+ * {@link Session} for a given {@link OpenTarget}. v1 concrete transport is
204
+ * Playwright (built in a later task); an extension or Firefox transport can
205
+ * implement the same interface.
206
+ */
207
+ export interface Transport {
208
+ open(target: OpenTarget): Promise<Session>;
209
+ }
210
+ /** Alias: the seam is referred to as the `Driver` in the domain glossary. */
211
+ export type Driver = Transport;
212
+ //# sourceMappingURL=seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seam.d.ts","sourceRoot":"","sources":["../src/seam.ts"],"names":[],"mappings":"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"}
package/dist/seam.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The verb-level transport seam.
3
+ *
4
+ * This is the highest test seam and the internal structure boundary of
5
+ * `core` (see PRD "Testing Decisions" and `docs/adr/0003`). It is expressed
6
+ * purely in terms of high-level VERBS (navigate, snapshot, click, type, eval,
7
+ * wait, cookies), NOT in terms of CDP or Playwright primitives, so that a
8
+ * future browser-extension transport or a non-Chromium (Firefox) transport
9
+ * can implement it without changing the verb surface.
10
+ *
11
+ * RULES (load-bearing, do not relax without an ADR):
12
+ * - No CDP / Chromium-only types may appear in this public surface
13
+ * (`docs/adr/0003`).
14
+ * - Element addressing is a RAW PLAYWRIGHT LOCATOR STRING the active
15
+ * transport resolves (`docs/adr/0004`): "transport-neutral" means
16
+ * Playwright-equivalent addressing, not a reduced selector subset and not a
17
+ * structured JSON locator. We deliberately type it as a branded `string`
18
+ * rather than importing any Playwright `Locator` type, so no Playwright
19
+ * type leaks across the seam.
20
+ */
21
+ /** Tag a raw Playwright locator string as a {@link LocatorString}. */
22
+ export function locator(expression) {
23
+ return expression;
24
+ }
25
+ //# sourceMappingURL=seam.js.map
@@ -0,0 +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"}
@@ -0,0 +1,53 @@
1
+ import { type ProfileLocationOptions } from './profile-location.js';
2
+ /**
3
+ * Cross-invocation session DISCOVERY (ADR-0005).
4
+ *
5
+ * The long-lived `incur serve` process owns the one live browser session; each
6
+ * `webhands <verb>` is a thin client that must FIND that running
7
+ * server. The server advertises itself by writing a small endpoint file under
8
+ * the controller home root (the same SHARED location profiles live under, see
9
+ * {@link resolveHomeRoot}); client verbs read it to learn where to send their
10
+ * verb calls. When no endpoint file exists, no server is live, and a verb errors
11
+ * with "run `serve` first" rather than auto-spawning a browser (ADR-0005:
12
+ * lifecycle is EXPLICIT in v1).
13
+ *
14
+ * Because this writes under the real `~/.webhands` by default,
15
+ * TESTS MUST override the root to a temp dir (via {@link ProfileLocationOptions})
16
+ * and assert the real location is untouched, exactly as the profile location
17
+ * does.
18
+ */
19
+ /** The endpoint file name under the controller home root. */
20
+ export declare const SESSION_ENDPOINT_FILENAME = "session-endpoint.json";
21
+ /**
22
+ * What the served process advertises about itself for client discovery. Kept
23
+ * deliberately small: the base `url` a client posts verb calls to, plus the
24
+ * `pid` so a human/test can confirm or signal the owning process.
25
+ */
26
+ export interface SessionEndpoint {
27
+ /** The base HTTP URL the served session listens on (e.g. `http://127.0.0.1:53113`). */
28
+ readonly url: string;
29
+ /** The PID of the served process, for confirmation / signalling. */
30
+ readonly pid: number;
31
+ }
32
+ /**
33
+ * Resolve the absolute path of the endpoint file for a given home root. Pure
34
+ * (touches no filesystem); precedence matches {@link resolveHomeRoot} so the
35
+ * endpoint file always sits beside the profiles dir under the same root.
36
+ */
37
+ export declare function resolveSessionEndpointPath(options?: ProfileLocationOptions): string;
38
+ /**
39
+ * Advertise a live served session by writing its endpoint file (creating the
40
+ * home root if absent). Overwrites any stale file; the server owns this file's
41
+ * lifetime and clears it on stop.
42
+ */
43
+ export declare function writeSessionEndpoint(endpoint: SessionEndpoint, options?: ProfileLocationOptions): Promise<string>;
44
+ /**
45
+ * Read the advertised endpoint, or `undefined` when no server is live (the file
46
+ * is absent or unreadable). Discovery is best-effort: a malformed file is
47
+ * treated as "no live server" so a client falls through to the clear
48
+ * "run `serve` first" error rather than crashing on a partial write.
49
+ */
50
+ export declare function readSessionEndpoint(options?: ProfileLocationOptions): Promise<SessionEndpoint | undefined>;
51
+ /** Remove the endpoint file (teardown). Absent file is not an error. */
52
+ export declare function clearSessionEndpoint(options?: ProfileLocationOptions): Promise<void>;
53
+ //# sourceMappingURL=session-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-endpoint.d.ts","sourceRoot":"","sources":["../src/session-endpoint.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;;GAgBG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACzC,OAAO,GAAE,sBAA2B,GAClC,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,sBAA2B,GAClC,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACxC,OAAO,GAAE,sBAA2B,GAClC,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAqBtC;AAED,wEAAwE;AACxE,wBAAsB,oBAAoB,CACzC,OAAO,GAAE,sBAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -0,0 +1,75 @@
1
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { resolveHomeRoot, } from './profile-location.js';
4
+ /**
5
+ * Cross-invocation session DISCOVERY (ADR-0005).
6
+ *
7
+ * The long-lived `incur serve` process owns the one live browser session; each
8
+ * `webhands <verb>` is a thin client that must FIND that running
9
+ * server. The server advertises itself by writing a small endpoint file under
10
+ * the controller home root (the same SHARED location profiles live under, see
11
+ * {@link resolveHomeRoot}); client verbs read it to learn where to send their
12
+ * verb calls. When no endpoint file exists, no server is live, and a verb errors
13
+ * with "run `serve` first" rather than auto-spawning a browser (ADR-0005:
14
+ * lifecycle is EXPLICIT in v1).
15
+ *
16
+ * Because this writes under the real `~/.webhands` by default,
17
+ * TESTS MUST override the root to a temp dir (via {@link ProfileLocationOptions})
18
+ * and assert the real location is untouched, exactly as the profile location
19
+ * does.
20
+ */
21
+ /** The endpoint file name under the controller home root. */
22
+ export const SESSION_ENDPOINT_FILENAME = 'session-endpoint.json';
23
+ /**
24
+ * Resolve the absolute path of the endpoint file for a given home root. Pure
25
+ * (touches no filesystem); precedence matches {@link resolveHomeRoot} so the
26
+ * endpoint file always sits beside the profiles dir under the same root.
27
+ */
28
+ export function resolveSessionEndpointPath(options = {}) {
29
+ return join(resolveHomeRoot(options), SESSION_ENDPOINT_FILENAME);
30
+ }
31
+ /**
32
+ * Advertise a live served session by writing its endpoint file (creating the
33
+ * home root if absent). Overwrites any stale file; the server owns this file's
34
+ * lifetime and clears it on stop.
35
+ */
36
+ export async function writeSessionEndpoint(endpoint, options = {}) {
37
+ const path = resolveSessionEndpointPath(options);
38
+ await mkdir(dirname(path), { recursive: true });
39
+ await writeFile(path, JSON.stringify(endpoint, null, 2), 'utf8');
40
+ return path;
41
+ }
42
+ /**
43
+ * Read the advertised endpoint, or `undefined` when no server is live (the file
44
+ * is absent or unreadable). Discovery is best-effort: a malformed file is
45
+ * treated as "no live server" so a client falls through to the clear
46
+ * "run `serve` first" error rather than crashing on a partial write.
47
+ */
48
+ export async function readSessionEndpoint(options = {}) {
49
+ const path = resolveSessionEndpointPath(options);
50
+ let text;
51
+ try {
52
+ text = await readFile(path, 'utf8');
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ try {
58
+ const parsed = JSON.parse(text);
59
+ if (typeof parsed.url === 'string' &&
60
+ parsed.url !== '' &&
61
+ typeof parsed.pid === 'number') {
62
+ return { url: parsed.url, pid: parsed.pid };
63
+ }
64
+ }
65
+ catch {
66
+ // fall through
67
+ }
68
+ return undefined;
69
+ }
70
+ /** Remove the endpoint file (teardown). Absent file is not an error. */
71
+ export async function clearSessionEndpoint(options = {}) {
72
+ const path = resolveSessionEndpointPath(options);
73
+ await rm(path, { force: true });
74
+ }
75
+ //# sourceMappingURL=session-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-endpoint.js","sourceRoot":"","sources":["../src/session-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,WAAW,CAAC;AACxC,OAAO,EACN,eAAe,GAEf,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;;GAgBG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAcjE;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACzC,UAAkC,EAAE;IAEpC,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,yBAAyB,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,QAAyB,EACzB,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;IAC9C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACJ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA6B,CAAC;QAC5D,IACC,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,MAAM,CAAC,GAAG,KAAK,EAAE;YACjB,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAC7B,CAAC;YACF,OAAO,EAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAC,CAAC;QAC3C,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,eAAe;IAChB,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { type Cookie, type WaitCondition } from './seam.js';
2
+ import type { Page } from './seam.js';
3
+ /**
4
+ * The wire protocol for driving the long-lived session over HTTP (ADR-0005).
5
+ *
6
+ * The served process holds ONE live {@link Page} in memory; a thin client verb
7
+ * cannot hold a JS reference to it across the process boundary, so each verb
8
+ * call is sent as a small JSON request to the server, which runs it against its
9
+ * live page and returns the result. This module is the SINGLE source of truth
10
+ * for that request/response shape, imported by BOTH the server handler and the
11
+ * client proxy so they cannot drift (mirrors how `serializeCookies` is shared
12
+ * by the cookies verb and its test).
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.
19
+ */
20
+ /** The path the session RPC is served under, below the server's base URL. */
21
+ export declare const SESSION_RPC_PATH = "/session/call";
22
+ /** A single verb call to run against the served live page. */
23
+ export type SessionRpcRequest = {
24
+ readonly verb: 'navigate';
25
+ readonly url: string;
26
+ } | {
27
+ readonly verb: 'snapshot';
28
+ readonly full?: boolean;
29
+ } | {
30
+ readonly verb: 'click';
31
+ readonly locator: string;
32
+ } | {
33
+ readonly verb: 'type';
34
+ readonly locator: string;
35
+ readonly text: string;
36
+ } | {
37
+ readonly verb: 'eval';
38
+ readonly expression: string;
39
+ } | {
40
+ readonly verb: 'wait';
41
+ readonly condition: WaitCondition;
42
+ } | {
43
+ readonly verb: 'cookies';
44
+ } | {
45
+ readonly verb: 'setCookies';
46
+ readonly cookies: readonly Cookie[];
47
+ };
48
+ /**
49
+ * The server's reply to a {@link SessionRpcRequest}. `ok: true` carries the
50
+ * verb's return value (for verbs that return data); `ok: false` carries the
51
+ * page-side error message so the client can re-throw a faithful `Error` (a
52
+ * page throw must REJECT on the client too, per the seam's `eval` contract).
53
+ */
54
+ export type SessionRpcResponse = {
55
+ readonly ok: true;
56
+ readonly value?: unknown;
57
+ } | {
58
+ readonly ok: false;
59
+ readonly error: string;
60
+ };
61
+ /**
62
+ * Run one {@link SessionRpcRequest} against a live {@link Page}, returning the
63
+ * value the wire should carry back. The server's HTTP handler is just this plus
64
+ * JSON framing; keeping the dispatch here (not inline in the handler) means the
65
+ * verb-to-page mapping is in one place and unit-testable without HTTP.
66
+ *
67
+ * The locator string and wait condition arrive as plain JSON; we re-brand the
68
+ * locator with {@link locator} before handing it to the page so the seam's
69
+ * branded-string contract holds.
70
+ */
71
+ export declare function applySessionRpc(page: Page, request: SessionRpcRequest): Promise<unknown>;
72
+ /**
73
+ * A {@link Page} whose verbs forward to a server via the supplied transport.
74
+ *
75
+ * Used by the client-side proxy (see `remote-session.ts`): each verb builds a
76
+ * {@link SessionRpcRequest}, hands it to `send`, and shapes the reply back into
77
+ * the verb's return type. The `send` function owns the actual HTTP; this keeps
78
+ * the verb-to-request mapping (the other half of {@link applySessionRpc}) in
79
+ * one place so request and response shapes cannot drift between the two sides.
80
+ */
81
+ export declare function makeRpcPage(send: (request: SessionRpcRequest) => Promise<unknown>): Page;
82
+ //# sourceMappingURL=session-rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-rpc.d.ts","sourceRoot":"","sources":["../src/session-rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,MAAM,EAEX,KAAK,aAAa,EAClB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD,8DAA8D;AAC9D,MAAM,MAAM,iBAAiB,GAC1B;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAC,GACjD;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAC,GACpD;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAClD;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GACxE;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAC,GACpD;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;CAAC,GAC1D;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GAC1B;IAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC3B;IAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAC,GAC7C;IAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,iBAAiB,GACxB,OAAO,CAAC,OAAO,CAAC,CAwBlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,GACpD,IAAI,CA8BN"}