waygraph 0.15.44 → 0.15.45

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/dist/analysis/coverage-gap.d.ts +1 -1
  2. package/dist/analysis/coverage-gap.js +1 -1
  3. package/dist/analysis/graph.js +1 -1
  4. package/dist/auto-session/helpers.d.ts +2 -2
  5. package/dist/auto-session/session.js +2 -2
  6. package/dist/cli/usage.d.ts +1 -1
  7. package/dist/commands/session.js +5 -5
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.js +2 -2
  10. package/dist/session/auto-explore-run.d.ts +37 -0
  11. package/dist/session/auto-explore-run.js +670 -0
  12. package/dist/session/auto-explore.d.ts +57 -0
  13. package/dist/session/auto-explore.js +376 -0
  14. package/dist/session/auto-session-ipc.d.ts +150 -0
  15. package/dist/session/auto-session-ipc.js +499 -0
  16. package/dist/session/block-inject.d.ts +21 -0
  17. package/dist/session/block-inject.js +97 -0
  18. package/dist/session/browser.d.ts +15 -0
  19. package/dist/session/browser.js +29 -0
  20. package/dist/session/overlay-beacon.d.ts +44 -0
  21. package/dist/session/overlay-beacon.js +85 -0
  22. package/dist/session/pilot.d.ts +54 -0
  23. package/dist/session/pilot.js +80 -0
  24. package/dist/traverse/run.js +2 -2
  25. package/examples/saucedemo/.waygraph-auto/00b0c55c.log +22 -0
  26. package/examples/saucedemo/.waygraph-auto/00e5d037.log +5 -0
  27. package/examples/saucedemo/.waygraph-auto/015be8f6.log +6 -0
  28. package/examples/saucedemo/.waygraph-auto/026e188e.log +0 -0
  29. package/examples/saucedemo/.waygraph-auto/07845ed1.log +0 -0
  30. package/examples/saucedemo/.waygraph-auto/0c19e232.log +5 -0
  31. package/examples/saucedemo/.waygraph-auto/1122c13c.log +0 -0
  32. package/examples/saucedemo/.waygraph-auto/2b9b56fc.log +20 -0
  33. package/examples/saucedemo/.waygraph-auto/30a5c848.log +20 -0
  34. package/examples/saucedemo/.waygraph-auto/33a50dc0.log +0 -0
  35. package/examples/saucedemo/.waygraph-auto/3a208bb8.log +20 -0
  36. package/examples/saucedemo/.waygraph-auto/3e3c0f3b.log +0 -0
  37. package/examples/saucedemo/.waygraph-auto/756fd326.log +20 -0
  38. package/examples/saucedemo/.waygraph-auto/78043767.log +20 -0
  39. package/examples/saucedemo/.waygraph-auto/976394cd.log +20 -0
  40. package/examples/saucedemo/.waygraph-auto/9d827954.log +20 -0
  41. package/examples/saucedemo/.waygraph-auto/a4f8d355.log +20 -0
  42. package/examples/saucedemo/.waygraph-auto/af2fae12.log +0 -0
  43. package/examples/saucedemo/.waygraph-auto/b0752c49.log +0 -0
  44. package/examples/saucedemo/.waygraph-auto/b14811d7.log +6 -0
  45. package/examples/saucedemo/.waygraph-auto/c9d76e32.log +0 -0
  46. package/examples/saucedemo/.waygraph-auto/cc989184.log +20 -0
  47. package/examples/saucedemo/.waygraph-auto/e5127c03.log +22 -0
  48. package/package.json +1 -1
  49. package/templates/agents/waygraph-author.agent.md +24 -51
  50. package/templates/agents/waygraph-planner.agent.md +12 -5
@@ -0,0 +1,57 @@
1
+ import type { Page } from "@playwright/test";
2
+ import type { Block, Checkpoint, WaygraphInstanceOption } from "../types.js";
3
+ import { type WaygraphGraph, type WaygraphEdge } from "../analysis/graph.js";
4
+ export interface ExploreContextOpts {
5
+ blocksSelect?: import("../blocks-select.js").BlocksSelect;
6
+ /** Absolute paths of extra Block trees to merge (host projectDir is always first). */
7
+ inject?: string[];
8
+ }
9
+ export interface BlockEntry {
10
+ block: Block<Checkpoint<string>, Checkpoint<string>>;
11
+ exportName: string;
12
+ file: string;
13
+ kind: "nav" | "action";
14
+ description: string;
15
+ }
16
+ /**
17
+ * One menu row. Usually just a `WaygraphEdge` as discovered statically; when
18
+ * a Block declares `instanceOptions`, one `ExploreEdge` is produced per live
19
+ * option instead of a single generic row - `label` overrides the default
20
+ * "block -> checkpoint" text, and `instanceOption` is what the runner
21
+ * `mem.set()`s before running that row's Block.
22
+ */
23
+ export interface ExploreEdge extends WaygraphEdge {
24
+ label?: string;
25
+ instanceOption?: WaygraphInstanceOption;
26
+ }
27
+ export interface ExploreSection {
28
+ title: string;
29
+ edges: ExploreEdge[];
30
+ }
31
+ /** Grouped menu for auto explore — `flat` preserves pick indices for CLI/headful. */
32
+ export interface ExploreMenu {
33
+ here: string | null;
34
+ sections: ExploreSection[];
35
+ flat: ExploreEdge[];
36
+ }
37
+ /**
38
+ * Import every `*.block.ts` export keyed by runtime Block `.name`.
39
+ */
40
+ export declare function loadBlockLibrary(projectDir: string, opts?: ExploreContextOpts): Promise<{
41
+ byName: Map<string, BlockEntry>;
42
+ navBlocks: BlockEntry[];
43
+ }>;
44
+ /**
45
+ * @deprecated Use {@link buildExploreMenu} — kept for unit tests.
46
+ */
47
+ export declare function exploreChoices(graph: WaygraphGraph, here: string | null): WaygraphEdge[];
48
+ /**
49
+ * Runtime-filtered menu: only Blocks that can run on the live page, grouped forward/back.
50
+ */
51
+ export declare function buildExploreMenu(page: Page, graph: WaygraphGraph, library: {
52
+ byName: Map<string, BlockEntry>;
53
+ }, here: string | null): Promise<ExploreMenu>;
54
+ export declare function buildExploreContext(projectDir: string, opts?: ExploreContextOpts): Promise<{
55
+ graph: WaygraphGraph;
56
+ library: Awaited<ReturnType<typeof loadBlockLibrary>>;
57
+ }>;
@@ -0,0 +1,376 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import { discoverGraph } from "../analysis/graph.js";
4
+ import { exploreRoots, injectTaggedFile } from "./block-inject.js";
5
+ function walkDir(dir, pattern) {
6
+ const results = [];
7
+ let entries;
8
+ try {
9
+ entries = readdirSync(dir, { withFileTypes: true });
10
+ }
11
+ catch {
12
+ return results;
13
+ }
14
+ for (const entry of entries) {
15
+ const full = join(dir, entry.name);
16
+ if (entry.isDirectory()) {
17
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
18
+ continue;
19
+ results.push(...walkDir(full, pattern));
20
+ }
21
+ else if (pattern.test(entry.name)) {
22
+ results.push(full);
23
+ }
24
+ }
25
+ return results;
26
+ }
27
+ /**
28
+ * Real, confirmed bug this fixes: a bare `import(url.href)` is permanently
29
+ * cached by Node's ESM loader for the lifetime of the process, keyed on the
30
+ * exact specifier. `AutoSession.reloadLibrary()` exists specifically so a
31
+ * Block edited on disk mid-session takes effect without restarting the
32
+ * session - but a SECOND `reload` of a file already imported once in this
33
+ * process silently kept returning the FIRST version's content, no matter
34
+ * how many times the file changed on disk after that. A one-shot CLI
35
+ * process (`waygraph graph`, etc.) never hits this - it only ever imports
36
+ * each file once anyway - but a long-running `--detach` session calling
37
+ * `reloadLibrary()` repeatedly does.
38
+ *
39
+ * Fix: bust the cache with the file's own mtime as a query param - a
40
+ * genuinely changed file gets a new specifier (forcing a fresh import); an
41
+ * UNCHANGED file between two reloads keeps hitting the SAME specifier (no
42
+ * unbounded accumulation of dead module instances across many reloads).
43
+ */
44
+ async function importModule(filePath) {
45
+ const resolved = resolve(filePath);
46
+ const url = new URL(`file://${resolved}`);
47
+ const mtimeMs = statSync(resolved).mtimeMs;
48
+ url.searchParams.set("t", String(mtimeMs));
49
+ return (await import(url.href));
50
+ }
51
+ function isBlockLike(val) {
52
+ if (val === null || typeof val !== "object")
53
+ return false;
54
+ const obj = val;
55
+ if (typeof obj.name !== "string")
56
+ return false;
57
+ const instruction = obj.instruction;
58
+ return instruction !== null && typeof instruction === "object" && typeof instruction?.act === "function";
59
+ }
60
+ function isNavBlockMarked(val) {
61
+ const kind = val.__waygraphKind;
62
+ return kind === "nav" || kind === "page";
63
+ }
64
+ function isUrlNav(entry) {
65
+ return entry.block.__waygraphNavClick === undefined;
66
+ }
67
+ function isBackEdge(edge) {
68
+ return /^nav-back/.test(edge.block) || edge.block === "submit-logout";
69
+ }
70
+ async function navRunnable(page, entry) {
71
+ const click = entry.block.__waygraphNavClick;
72
+ if (click === undefined)
73
+ return true;
74
+ if (typeof click !== "string")
75
+ return false;
76
+ try {
77
+ // Short timeout: menu scan must stay snappy (many edges miss on this page).
78
+ return await page.locator(click).first().isVisible({ timeout: 150 });
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ async function wildcardActionRunnable(page, entry, here) {
85
+ if (here === null)
86
+ return false;
87
+ const name = entry.block.name;
88
+ // Bulk / non-instance * actions: show when matching live buttons exist.
89
+ try {
90
+ if (name === "add-all-to-cart" || name === "add-to-cart") {
91
+ return await page.locator('button[data-test^="add-to-cart-"]').first().isVisible({ timeout: 150 });
92
+ }
93
+ if (name === "remove-all-from-cart" || name === "remove-from-cart") {
94
+ return await page.locator('button[data-test^="remove-"]').first().isVisible({ timeout: 150 });
95
+ }
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ return false;
101
+ }
102
+ /**
103
+ * Import every `*.block.ts` export keyed by runtime Block `.name`.
104
+ */
105
+ export async function loadBlockLibrary(projectDir, opts) {
106
+ const { matchBlocksSelect } = await import("../blocks-select.js");
107
+ const byName = new Map();
108
+ const navBlocks = [];
109
+ const navSeen = new Set();
110
+ const select = opts?.blocksSelect;
111
+ for (const root of exploreRoots(projectDir, opts?.inject)) {
112
+ for (const file of walkDir(root, /\.block\.ts$/)) {
113
+ const relFile = relative(root, file).replace(/\\/g, "/");
114
+ const taggedFile = injectTaggedFile(projectDir, root, relFile);
115
+ // Glob/bare: path filter before import. Regex: need block name — filter after.
116
+ if (select && select.kind !== "regex" && !matchBlocksSelect(select, { relFile })) {
117
+ continue;
118
+ }
119
+ let mod;
120
+ try {
121
+ mod = await importModule(file);
122
+ }
123
+ catch (err) {
124
+ const msg = err instanceof Error ? err.message : String(err);
125
+ if (/second time|playwright/i.test(msg)) {
126
+ console.error(`waygraph auto: skip ${relFile}: ${msg.split("\n")[0]}`);
127
+ }
128
+ continue;
129
+ }
130
+ for (const [exportName, exported] of Object.entries(mod)) {
131
+ if (!isBlockLike(exported))
132
+ continue;
133
+ if (select &&
134
+ !matchBlocksSelect(select, { relFile, blockName: exported.name })) {
135
+ continue;
136
+ }
137
+ const kind = isNavBlockMarked(exported) ? "nav" : "action";
138
+ const entry = {
139
+ block: exported,
140
+ exportName,
141
+ file: taggedFile,
142
+ kind,
143
+ description: typeof exported.description === "string" ? exported.description : "",
144
+ };
145
+ if (byName.has(exported.name)) {
146
+ console.error(`waygraph: inject skipped duplicate Block name "${exported.name}" (${taggedFile})`);
147
+ continue;
148
+ }
149
+ byName.set(exported.name, entry);
150
+ if (kind === "nav" && !navSeen.has(exported.name)) {
151
+ navSeen.add(exported.name);
152
+ navBlocks.push(entry);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ navBlocks.sort((a, b) => {
158
+ const aUrl = isUrlNav(a) ? 0 : 1;
159
+ const bUrl = isUrlNav(b) ? 0 : 1;
160
+ return aUrl - bUrl || a.block.name.localeCompare(b.block.name);
161
+ });
162
+ return { byName, navBlocks };
163
+ }
164
+ /**
165
+ * @deprecated Use {@link buildExploreMenu} — kept for unit tests.
166
+ */
167
+ export function exploreChoices(graph, here) {
168
+ const seen = new Set();
169
+ const out = [];
170
+ for (const edge of graph.edges) {
171
+ if (here === null) {
172
+ if (edge.kind !== "nav")
173
+ continue;
174
+ }
175
+ else if (edge.from !== here && edge.from !== "*") {
176
+ continue;
177
+ }
178
+ if (seen.has(edge.block))
179
+ continue;
180
+ seen.add(edge.block);
181
+ out.push(edge);
182
+ }
183
+ return out.sort((a, b) => a.block.localeCompare(b.block));
184
+ }
185
+ /**
186
+ * Runtime-filtered menu: only Blocks that can run on the live page, grouped forward/back.
187
+ */
188
+ export async function buildExploreMenu(page, graph, library, here) {
189
+ const forward = [];
190
+ const anywhere = [];
191
+ const navigate = [];
192
+ const back = [];
193
+ const seen = new Set();
194
+ // Union Out tags create duplicate edges for the same Block - cache instanceOptions
195
+ // so we only scrape the DOM once per Block name per menu build.
196
+ const instanceOptionsCache = new Map();
197
+ const push = (bucket, edge) => {
198
+ if (seen.has(edge.block))
199
+ return;
200
+ seen.add(edge.block);
201
+ bucket.push(edge);
202
+ };
203
+ async function loadInstanceOptions(entry) {
204
+ const fn = entry.block.instanceOptions;
205
+ if (!fn)
206
+ return [];
207
+ const cached = instanceOptionsCache.get(entry.block.name);
208
+ if (cached)
209
+ return cached;
210
+ let options = [];
211
+ try {
212
+ options = await fn(page);
213
+ }
214
+ catch {
215
+ options = [];
216
+ }
217
+ instanceOptionsCache.set(entry.block.name, options);
218
+ return options;
219
+ }
220
+ for (const edge of graph.edges) {
221
+ const entry = library.byName.get(edge.block);
222
+ if (!entry)
223
+ continue;
224
+ if (edge.kind === "action") {
225
+ if (here !== null && edge.from === here) {
226
+ if (entry.block.instanceOptions) {
227
+ const options = await loadInstanceOptions(entry);
228
+ for (const option of options) {
229
+ const seenKey = `${edge.block}::${option.id}`;
230
+ if (seen.has(seenKey))
231
+ continue;
232
+ seen.add(seenKey);
233
+ (isBackEdge(edge) ? back : forward).push({
234
+ ...edge,
235
+ label: option.label,
236
+ instanceOption: option,
237
+ });
238
+ }
239
+ }
240
+ else {
241
+ push(isBackEdge(edge) ? back : forward, edge);
242
+ }
243
+ }
244
+ else if (here !== null && edge.from === "*") {
245
+ if (entry.block.instanceOptions) {
246
+ const options = await loadInstanceOptions(entry);
247
+ for (const option of options) {
248
+ const seenKey = `${edge.block}::${option.id}`;
249
+ if (seen.has(seenKey))
250
+ continue;
251
+ seen.add(seenKey);
252
+ anywhere.push({ ...edge, label: option.label, instanceOption: option });
253
+ }
254
+ }
255
+ else if (await wildcardActionRunnable(page, entry, here)) {
256
+ const bulkLabel = {
257
+ "add-all-to-cart": "Add all to cart",
258
+ "remove-all-from-cart": "Remove all from cart",
259
+ };
260
+ const label = bulkLabel[entry.block.name] ?? (entry.description?.trim() || undefined);
261
+ push(anywhere, label ? { ...edge, label } : edge);
262
+ }
263
+ }
264
+ continue;
265
+ }
266
+ // Nav: MemNavBlock (instanceOptions) expands one row per live target;
267
+ // plain NavBlock still uses click-visibility (navRunnable).
268
+ if (here !== null && (edge.from === here || edge.from === "*") && edge.to !== here) {
269
+ if (entry.block.instanceOptions) {
270
+ const options = await loadInstanceOptions(entry);
271
+ const bucket = isBackEdge(edge) ? back : navigate;
272
+ for (const option of options) {
273
+ const seenKey = `${edge.block}::${option.id}`;
274
+ if (seen.has(seenKey))
275
+ continue;
276
+ seen.add(seenKey);
277
+ bucket.push({ ...edge, label: option.label, instanceOption: option });
278
+ }
279
+ continue;
280
+ }
281
+ }
282
+ if (!(await navRunnable(page, entry)))
283
+ continue;
284
+ if (here === null) {
285
+ // Bootstrap only: URL deep-links as "Start here". Click-nav needs a live
286
+ // control so it cannot seed an unknown page.
287
+ if (!isUrlNav(entry))
288
+ continue;
289
+ push(navigate, edge);
290
+ continue;
291
+ }
292
+ if (edge.to === here)
293
+ continue;
294
+ // Known screen: do NOT dump every defineNavBlock({ url }) under Navigate.
295
+ // Graph still marks NavBlocks from:"*" for pathfinding/traverse; auto menu
296
+ // only offers click-nav whose control is visible on this page (navRunnable).
297
+ // Otherwise LoginForm lists every app route + mailpit as if legal moves.
298
+ if (isUrlNav(entry))
299
+ continue;
300
+ if (isBackEdge(edge) && (edge.from === here || edge.from === "*")) {
301
+ push(back, edge);
302
+ }
303
+ else if (edge.from === here || edge.from === "*") {
304
+ push(navigate, edge);
305
+ }
306
+ }
307
+ const sections = [];
308
+ if (here === null) {
309
+ if (navigate.length > 0) {
310
+ sections.push({ title: "Start here", edges: navigate.sort((a, b) => a.block.localeCompare(b.block)) });
311
+ }
312
+ }
313
+ else {
314
+ if (forward.length > 0) {
315
+ sections.push({
316
+ title: `Methods from ${here}`,
317
+ edges: forward.sort((a, b) => a.block.localeCompare(b.block)),
318
+ });
319
+ }
320
+ if (anywhere.length > 0) {
321
+ sections.push({
322
+ title: "Also on this page",
323
+ edges: anywhere.sort((a, b) => (a.label ?? a.block).localeCompare(b.label ?? b.block)),
324
+ });
325
+ }
326
+ if (navigate.length > 0) {
327
+ sections.push({
328
+ title: "Navigate",
329
+ edges: navigate.sort((a, b) => (a.label ?? a.block).localeCompare(b.label ?? b.block)),
330
+ });
331
+ }
332
+ if (back.length > 0) {
333
+ sections.push({
334
+ title: "Go back",
335
+ edges: back.sort((a, b) => a.block.localeCompare(b.block)),
336
+ });
337
+ }
338
+ }
339
+ const flat = sections.flatMap((s) => s.edges);
340
+ // Stuck screen: graph knows actions from `here` but none made it into the menu
341
+ // (failed library load, empty instanceOptions, etc.). Surface them so LoginPage
342
+ // never shows a dead "No moves" when submit-login is in the graph.
343
+ if (here !== null && flat.length === 0) {
344
+ const rescue = [];
345
+ const rescued = new Set();
346
+ for (const edge of graph.edges) {
347
+ if (edge.kind !== "action" || edge.from !== here)
348
+ continue;
349
+ if (rescued.has(edge.block))
350
+ continue;
351
+ const entry = library.byName.get(edge.block);
352
+ if (!entry)
353
+ continue;
354
+ if (entry.block.instanceOptions)
355
+ continue; // still need live rows
356
+ rescued.add(edge.block);
357
+ rescue.push(edge);
358
+ }
359
+ if (rescue.length > 0) {
360
+ sections.push({
361
+ title: `Methods from ${here}`,
362
+ edges: rescue.sort((a, b) => a.block.localeCompare(b.block)),
363
+ });
364
+ return { here, sections, flat: sections.flatMap((s) => s.edges) };
365
+ }
366
+ }
367
+ return { here, sections, flat };
368
+ }
369
+ export async function buildExploreContext(projectDir, opts) {
370
+ // Sequential: parallel import of the same *.block.ts tree raced Playwright's
371
+ // dual-copy guard in some installs and left graph edges without library entries
372
+ // (LoginPage → "No moves").
373
+ const library = await loadBlockLibrary(projectDir, opts);
374
+ const graph = await discoverGraph(projectDir, opts);
375
+ return { graph, library };
376
+ }
@@ -0,0 +1,150 @@
1
+ import { AutoSession, type AutoSessionInit, type SessionSnapshot, type InspectDomOptions, type DomSnapshot, type TraceStep, type ApplyPathResult, type ConsoleLogEntry, type StorageSnapshot, type StubFileKind } from "../auto-session.js";
2
+ import type { PilotHighlightFixtures } from "../pilot-overlay.js";
3
+ export type { PilotHighlightFixtures, PilotFixtureRing } from "../pilot-overlay.js";
4
+ export interface SessionMeta {
5
+ sessionId: string;
6
+ socketPath: string;
7
+ pid: number;
8
+ projectDir: string;
9
+ startedAt: string;
10
+ headless: boolean;
11
+ }
12
+ export type StatusOrSendResponse = {
13
+ ok: true;
14
+ snapshot: SessionSnapshot;
15
+ quit: boolean;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ export type DomResponse = {
21
+ ok: true;
22
+ snapshot: DomSnapshot;
23
+ } | {
24
+ ok: false;
25
+ error: string;
26
+ };
27
+ export type TraceResponse = {
28
+ ok: true;
29
+ trace: TraceStep[];
30
+ } | {
31
+ ok: false;
32
+ error: string;
33
+ };
34
+ export type ConsoleResponse = {
35
+ ok: true;
36
+ consoleLog: ConsoleLogEntry[];
37
+ } | {
38
+ ok: false;
39
+ error: string;
40
+ };
41
+ export type StorageResponse = ({
42
+ ok: true;
43
+ } & StorageSnapshot) | {
44
+ ok: false;
45
+ error: string;
46
+ };
47
+ export type ReachResponse = ApplyPathResult;
48
+ export type HighlightResponse = {
49
+ ok: true;
50
+ painted: number;
51
+ missing: string[];
52
+ snapshot: SessionSnapshot;
53
+ } | {
54
+ ok: false;
55
+ error: string;
56
+ };
57
+ export declare function readSessionMeta(projectDir: string, sessionId: string): SessionMeta | null;
58
+ /**
59
+ * Resolve session metadata from a project hint (cwd) or the global registry.
60
+ * Fixes "session unreachable" when the agent calls status/send from a different
61
+ * directory or pane than the one that ran `pilot start`.
62
+ */
63
+ export declare function resolveSessionMeta(sessionId: string, projectDirHint?: string): SessionMeta | null;
64
+ /**
65
+ * Client-side: sends one request to a running session and returns its
66
+ * response. Reports a stale/unreachable session as an error within
67
+ * `timeoutMs`, never hangs indefinitely.
68
+ */
69
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
70
+ op: "status";
71
+ } | {
72
+ op: "send";
73
+ pick: string;
74
+ } | {
75
+ op: "click";
76
+ selector: string;
77
+ } | {
78
+ op: "type";
79
+ selector: string;
80
+ text: string;
81
+ } | {
82
+ op: "press";
83
+ selector: string;
84
+ key: string;
85
+ } | {
86
+ op: "goto";
87
+ url: string;
88
+ } | {
89
+ op: "reload";
90
+ } | {
91
+ op: "resync";
92
+ } | {
93
+ op: "upload";
94
+ selector: string;
95
+ stub: StubFileKind | {
96
+ filePath: string;
97
+ };
98
+ }, timeoutMs?: number): Promise<StatusOrSendResponse>;
99
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
100
+ op: "dom";
101
+ } & InspectDomOptions, timeoutMs?: number): Promise<DomResponse>;
102
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
103
+ op: "trace";
104
+ }, timeoutMs?: number): Promise<TraceResponse>;
105
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
106
+ op: "console";
107
+ }, timeoutMs?: number): Promise<ConsoleResponse>;
108
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
109
+ op: "storage";
110
+ }, timeoutMs?: number): Promise<StorageResponse>;
111
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
112
+ op: "reach";
113
+ checkpoint: string;
114
+ }, timeoutMs?: number): Promise<ReachResponse>;
115
+ export declare function requestSession(projectDir: string, sessionId: string, request: {
116
+ op: "highlight";
117
+ } & PilotHighlightFixtures, timeoutMs?: number): Promise<HighlightResponse>;
118
+ /**
119
+ * Server-side: runs `session` behind a unix socket at
120
+ * `.waygraph-auto/<sessionId>.sock`, writing session metadata only once the
121
+ * socket is actually listening (the metadata file's existence is the
122
+ * "session is ready" signal a spawning parent polls for). Requests are
123
+ * processed one at a time via a queue - Node's single-threaded event loop
124
+ * already prevents true parallelism, but two overlapping `await`-heavy
125
+ * handlers could still interleave against the same page/mem.
126
+ */
127
+ export declare function serveSession(session: AutoSession, projectDir: string, sessionId: string, headless?: boolean): Promise<void>;
128
+ /** Entry point for the hidden `__auto-serve` CLI command (runs in the detached child). */
129
+ export declare function runAutoServeCommand(init: AutoSessionInit, sessionId: string): Promise<void>;
130
+ /**
131
+ * Parent-side: spawns a detached background process running the same
132
+ * explore session as a socket server, waits for it to signal readiness (its
133
+ * metadata file appearing), and returns its identity. Recurses through the
134
+ * existing `bin/waygraph` launcher (same tsx/esm registration every other
135
+ * invocation already gets) with the hidden `__auto-serve` command, rather
136
+ * than re-implementing that resolution here.
137
+ */
138
+ export declare function spawnDetachedSession(init: AutoSessionInit, readyTimeoutMs?: number): Promise<SessionMeta>;
139
+ /** Stop one detached session (SIGTERM + cleanup metadata/socket). */
140
+ export declare function stopBrowserSession(projectDir: string, sessionId: string): boolean;
141
+ /** Stop every live session registered for a project. */
142
+ export declare function stopAllBrowserSessions(projectDir: string): number;
143
+ /** Live browser sessions for a project (metadata + socket + pid still present). */
144
+ export declare function listBrowserSessions(projectDir: string): SessionMeta[];
145
+ /**
146
+ * Client-only interactive loop against a running detached session - reopens
147
+ * the same rendering/parsing the foreground `--cli` loop uses, driven purely
148
+ * through `status`/`send`. No server-side support beyond those two ops.
149
+ */
150
+ export declare function runAttachLoop(projectDir: string, sessionId: string): Promise<void>;