waygraph 0.16.0 → 0.16.1

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 (107) hide show
  1. package/dist/cli/commands/browser.d.ts +2 -0
  2. package/dist/cli/commands/browser.js +109 -0
  3. package/dist/cli/commands/pilot.d.ts +2 -0
  4. package/dist/cli/commands/pilot.js +102 -0
  5. package/dist/cli/commands/session-ops.d.ts +3 -0
  6. package/dist/cli/commands/session-ops.js +257 -0
  7. package/dist/cli/commands/session.js +20 -461
  8. package/dist/cli/usage.d.ts +1 -1
  9. package/dist/demo/narration/demo-pace.d.ts +1 -1
  10. package/dist/demo/narration/device.d.ts +2 -2
  11. package/dist/demo/narration/merge.d.ts +4 -2
  12. package/dist/demo/narration/merge.js +2 -6
  13. package/dist/demo/narration/narration.d.ts +8 -8
  14. package/dist/demo/narration/todo-dock.d.ts +5 -5
  15. package/dist/demo/overlay/cursor.d.ts +9 -2
  16. package/dist/demo/overlay/cursor.js +1 -1
  17. package/dist/demo/overlay/device-stage.d.ts +15 -2
  18. package/dist/demo/overlay/inpage/banner.d.ts +2 -1
  19. package/dist/demo/overlay/inpage/banner.js +3 -3
  20. package/dist/demo/overlay/inpage/caption.d.ts +1 -1
  21. package/dist/demo/overlay/inpage/core.d.ts +2 -1
  22. package/dist/demo/overlay/inpage/core.js +1 -1
  23. package/dist/demo/overlay/inpage/device-chip.d.ts +1 -1
  24. package/dist/demo/overlay/inpage/device-chip.js +6 -6
  25. package/dist/demo/overlay/inpage/network.d.ts +3 -2
  26. package/dist/demo/overlay/inpage/network.js +1 -12
  27. package/dist/demo/overlay/inpage/panel-chrome.d.ts +1 -1
  28. package/dist/demo/overlay/inpage/panel-chrome.js +1 -1
  29. package/dist/demo/overlay/inpage/stamp.d.ts +1 -1
  30. package/dist/demo/overlay/inpage/stamp.js +1 -1
  31. package/dist/demo/overlay/inpage/swipe-zoom.d.ts +1 -1
  32. package/dist/demo/overlay/inpage/swipe-zoom.js +1 -1
  33. package/dist/demo/overlay/inpage/todos.d.ts +2 -1
  34. package/dist/demo/overlay/inpage/todos.js +1 -2
  35. package/dist/demo/overlay/network-capture.d.ts +2 -2
  36. package/dist/demo/overlay/network-capture.js +1 -1
  37. package/dist/demo/overlay/overlay-stage.d.ts +19 -11
  38. package/dist/demo/overlay/overlay.d.ts +2 -2
  39. package/dist/demo/overlay/rings.d.ts +38 -10
  40. package/dist/demo/overlay/rings.js +21 -47
  41. package/dist/demo/stepper/after.js +9 -19
  42. package/dist/demo/stepper/banner-log.d.ts +4 -6
  43. package/dist/demo/stepper/banner-log.js +3 -1
  44. package/dist/demo/stepper/before.js +3 -3
  45. package/dist/demo/stepper/demo-log.d.ts +26 -9
  46. package/dist/demo/stepper/demo-log.js +9 -26
  47. package/dist/demo/stepper/interaction-patch.d.ts +3 -2
  48. package/dist/demo/stepper/interaction-patch.js +19 -19
  49. package/dist/demo/stepper/panel-after.d.ts +17 -0
  50. package/dist/demo/stepper/panel-after.js +166 -0
  51. package/dist/demo/stepper/panel-before.d.ts +12 -0
  52. package/dist/demo/stepper/panel-before.js +241 -0
  53. package/dist/demo/stepper/slides.d.ts +13 -1
  54. package/dist/demo/stepper/slides.js +3 -3
  55. package/dist/demo/stepper/step-panels.d.ts +57 -9
  56. package/dist/demo/stepper/step-panels.js +10 -427
  57. package/dist/demo/stepper/stepper.js +1 -1
  58. package/dist/demo/stepper/teardown.d.ts +4 -5
  59. package/dist/demo/stepper/teardown.js +4 -5
  60. package/dist/demo/stepper/todo-advance.d.ts +3 -2
  61. package/dist/demo/stepper/todo-advance.js +2 -2
  62. package/dist/demo/stepper/view.d.ts +4 -14
  63. package/dist/demo/types.d.ts +69 -1
  64. package/dist/engine/block/action-page.d.ts +53 -0
  65. package/dist/engine/block/action-page.js +1 -0
  66. package/dist/engine/block/block.d.ts +24 -79
  67. package/dist/engine/block/block.js +2 -77
  68. package/dist/engine/block/branch.d.ts +17 -0
  69. package/dist/engine/block/branch.js +40 -0
  70. package/dist/engine/block/dialog.d.ts +13 -0
  71. package/dist/engine/block/dialog.js +37 -0
  72. package/dist/engine/flow/map-branch.d.ts +9 -0
  73. package/dist/engine/flow/map-branch.js +57 -0
  74. package/dist/engine/flow/map-builder.d.ts +7 -22
  75. package/dist/engine/flow/map-builder.js +6 -145
  76. package/dist/engine/flow/map-steps.d.ts +30 -0
  77. package/dist/engine/flow/map-steps.js +87 -0
  78. package/dist/engine/trait/trait.js +1 -1
  79. package/dist/index.d.ts +13 -9
  80. package/dist/index.js +7 -5
  81. package/dist/live/auto/auto.d.ts +3 -15
  82. package/dist/live/auto/auto.js +10 -420
  83. package/dist/live/auto/explore.d.ts +2 -2
  84. package/dist/live/auto/mem.d.ts +11 -0
  85. package/dist/live/auto/mem.js +85 -0
  86. package/dist/live/auto/menu.d.ts +30 -0
  87. package/dist/live/auto/menu.js +335 -0
  88. package/dist/live/auto-session/auto-session.d.ts +23 -103
  89. package/dist/live/auto-session/auto-session.js +100 -370
  90. package/dist/live/auto-session/console.d.ts +4 -0
  91. package/dist/live/auto-session/console.js +41 -0
  92. package/dist/live/auto-session/files.d.ts +44 -0
  93. package/dist/live/auto-session/files.js +145 -0
  94. package/dist/live/auto-session/helpers.d.ts +5 -2
  95. package/dist/live/auto-session/helpers.js +17 -0
  96. package/dist/live/auto-session/inspect.d.ts +4 -0
  97. package/dist/live/auto-session/inspect.js +75 -0
  98. package/dist/live/auto-session/ipc.d.ts +3 -22
  99. package/dist/live/auto-session/ipc.js +7 -147
  100. package/dist/live/auto-session/open-browser.d.ts +6 -0
  101. package/dist/live/auto-session/open-browser.js +47 -0
  102. package/dist/live/pilot/pilot.d.ts +1 -1
  103. package/dist/live/pilot/pilot.js +2 -1
  104. package/dist/live/traverse/traverse.js +6 -200
  105. package/dist/live/traverse/worker.d.ts +37 -0
  106. package/dist/live/traverse/worker.js +200 -0
  107. package/package.json +5 -1
@@ -1,207 +1,13 @@
1
1
  import { Engine } from "../../engine/engine.js";
2
- import { buildFlow } from "../../engine/flow/flow.js";
2
+ import { envFlag, envString } from "../../engine/env.js";
3
3
  import { MemPage } from "../../engine/mem/mem.js";
4
- import { buildExploreContext, buildExploreMenu, } from "../auto/explore.js";
5
- import { detectHere, memStubValue } from "../auto/auto.js";
4
+ import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
5
+ import { chromiumExecutablePath } from "../../runtime/browser.js";
6
6
  import { resolveBaseUrl } from "../../runtime/waygraph-config.js";
7
- import { EdgeLeaseCoordinator } from "./lease.js";
7
+ import { buildExploreContext, } from "../auto/explore.js";
8
8
  import { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, writeCoverageReport, } from "./coverage.js";
9
- import { chromiumExecutablePath } from "../../runtime/browser.js";
10
- import { envFlag, envString } from "../../engine/env.js";
11
- import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
12
- function seedMem(library, mem, dataJson) {
13
- const raw = dataJson ?? envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
14
- let envParsed = {};
15
- if (raw) {
16
- try {
17
- envParsed = JSON.parse(raw);
18
- }
19
- catch {
20
- throw new Error(`waygraph traverse: --data / WAYGRAPH_DATA is not valid JSON`);
21
- }
22
- }
23
- for (const entry of library.values()) {
24
- for (const k of entry.block.requires ?? []) {
25
- if (mem.has(k))
26
- continue;
27
- if (k.name in envParsed) {
28
- mem.set(k, envParsed[k.name]);
29
- continue;
30
- }
31
- const def = memStubValue(k);
32
- if (def !== undefined)
33
- mem.set(k, def);
34
- }
35
- }
36
- }
37
- function snapshotMem(library, mem) {
38
- const out = {};
39
- for (const entry of library.values()) {
40
- for (const k of entry.block.requires ?? []) {
41
- if (mem.has(k) && !(k.name in out)) {
42
- out[k.name] = mem.get(k);
43
- }
44
- }
45
- }
46
- return out;
47
- }
48
- function restoreMem(library, mem, snap, dataJson) {
49
- seedMem(library, mem, dataJson);
50
- for (const entry of library.values()) {
51
- for (const k of entry.block.requires ?? []) {
52
- if (k.name in snap) {
53
- mem.set(k, snap[k.name]);
54
- }
55
- }
56
- }
57
- }
58
- function missingMemKeys(entry, mem) {
59
- return (entry.block.requires ?? []).filter((k) => !mem.has(k));
60
- }
61
- function ensureMem(entry, mem) {
62
- const missing = missingMemKeys(entry, mem);
63
- if (missing.length === 0)
64
- return;
65
- throw new Error(`Block "${entry.block.name}" needs Mem keys: ${missing.map((k) => k.name).join(", ")} ` +
66
- `(pass --data '{...}' or WAYGRAPH_DATA)`);
67
- }
68
- function edgeKey(edge) {
69
- const id = edge.instanceOption?.id;
70
- return id ? `${edge.block}::${id}` : edge.block;
71
- }
72
- async function runOneBlock(engine, entry, context, page, mem) {
73
- const flow = buildFlow([entry.block], engine.config);
74
- const BLOCK_TIMEOUT_MS = 45_000;
75
- let timer;
76
- try {
77
- const ran = await Promise.race([
78
- flow.run(context, mem, { page, closeOnFinish: false }),
79
- new Promise((_, reject) => {
80
- timer = setTimeout(() => reject(new Error(`Block "${entry.block.name}" timed out after ${BLOCK_TIMEOUT_MS / 1000}s`)), BLOCK_TIMEOUT_MS);
81
- }),
82
- ]);
83
- return ran.result;
84
- }
85
- finally {
86
- if (timer)
87
- clearTimeout(timer);
88
- }
89
- }
90
- async function runTraverseWorker(opts) {
91
- const { traverseId, workerIndex, parallel, maxSteps, maxVisitsPerNode, maxVisitsPerEdge, timeoutMs, graph, library, engine, context, mem, leases, } = opts;
92
- let page = opts.page;
93
- const startUrl = opts.startUrl;
94
- const nodeVisits = new Map();
95
- const edgeVisits = new Map();
96
- const edgesHit = new Set();
97
- let steps = 0;
98
- const startedAt = Date.now();
99
- let lastHere = opts.from ?? null;
100
- let exitCode = 0;
101
- let leaf;
102
- try {
103
- for (;;) {
104
- if (Date.now() - startedAt > timeoutMs) {
105
- console.log(`[Killed timeout[${traverseId}] node=${lastHere ?? "?"} wallMs=${timeoutMs}]`);
106
- exitCode = 1;
107
- break;
108
- }
109
- if (steps >= maxSteps) {
110
- console.log(`[Killed max-steps[${traverseId}] node=${lastHere ?? "?"} steps=${steps} max=${maxSteps}]`);
111
- exitCode = 1;
112
- break;
113
- }
114
- if (page.isClosed()) {
115
- page = await context.newPage();
116
- if (startUrl) {
117
- await page.goto(startUrl, { waitUntil: "domcontentloaded" }).catch(ignorePageGone);
118
- }
119
- }
120
- const detected = await detectHere(page, library.navBlocks);
121
- const here = lastHere ?? detected ?? opts.from ?? null;
122
- if (here && (nodeVisits.get(here) ?? 0) >= maxVisitsPerNode) {
123
- console.log(`[Killed loop[${traverseId}] node=${here} visits=${nodeVisits.get(here)} max=${maxVisitsPerNode}]`);
124
- exitCode = 1;
125
- break;
126
- }
127
- const menu = await buildExploreMenu(page, graph, library, here);
128
- const candidates = menu.flat.filter((edge) => {
129
- const key = edgeKey(edge);
130
- if ((edgeVisits.get(key) ?? 0) >= maxVisitsPerEdge)
131
- return false;
132
- if (parallel > 1) {
133
- if (EdgeLeaseCoordinator.partitionIndex(key, parallel) !== workerIndex) {
134
- return false;
135
- }
136
- }
137
- return true;
138
- });
139
- if (candidates.length === 0) {
140
- const label = here ?? detected ?? "unknown";
141
- leaf = label;
142
- console.log(`[Reached Leaf Node[${traverseId}] at=${label} steps=${steps}]`);
143
- console.error(`waygraph traverse[${traverseId}]: PASS leaf edgesHit=${edgesHit.size} steps=${steps}`);
144
- break;
145
- }
146
- const pick = [...candidates].sort((a, b) => {
147
- const aWild = a.from === "*" ? 1 : 0;
148
- const bWild = b.from === "*" ? 1 : 0;
149
- if (aWild !== bWild)
150
- return aWild - bWild;
151
- return edgeKey(a).localeCompare(edgeKey(b));
152
- })[0];
153
- const key = edgeKey(pick);
154
- if (!leases.tryClaim(key, traverseId)) {
155
- // Another worker won a race (instance fan-out); treat as unavailable.
156
- edgeVisits.set(key, maxVisitsPerEdge);
157
- continue;
158
- }
159
- const entry = library.byName.get(pick.block);
160
- if (!entry) {
161
- console.log(`[Broke at edge[${traverseId}] block=${pick.block} from=${here ?? "?"} to=${pick.to} reason=not-loaded]`);
162
- exitCode = 1;
163
- break;
164
- }
165
- const fromLabel = here ?? pick.from;
166
- try {
167
- if (pick.instanceOption) {
168
- mem.set(pick.instanceOption.key, pick.instanceOption.value);
169
- }
170
- ensureMem(entry, mem);
171
- console.error(`waygraph traverse[${traverseId}]: step ${steps + 1} ${fromLabel} -[${pick.block}]-> ${pick.to}` +
172
- (pick.label ? ` (${pick.label})` : ""));
173
- const out = await runOneBlock(engine, entry, context, page, mem);
174
- const observed = typeof out === "object" && out && "__state" in out
175
- ? String(out.__state)
176
- : pick.to;
177
- edgeVisits.set(key, (edgeVisits.get(key) ?? 0) + 1);
178
- edgesHit.add(key);
179
- if (here)
180
- nodeVisits.set(here, (nodeVisits.get(here) ?? 0) + 1);
181
- lastHere = observed;
182
- steps += 1;
183
- if (observed !== pick.to && pick.to !== "*") {
184
- console.error(`waygraph traverse[${traverseId}]: WARN static to=${pick.to} observed=${observed}`);
185
- }
186
- }
187
- catch (err) {
188
- const msg = err instanceof Error ? err.message : String(err);
189
- console.log(`[Broke at edge[${traverseId}] block=${pick.block} from=${fromLabel} to=${pick.to}] ${msg}`);
190
- exitCode = 1;
191
- break;
192
- }
193
- }
194
- }
195
- finally {
196
- console.error(`waygraph traverse[${traverseId}]: done exit=${exitCode} steps=${steps} edgesHit=${edgesHit.size}`);
197
- }
198
- return {
199
- exitCode,
200
- steps,
201
- edgesHit: [...edgesHit],
202
- ...(leaf ? { leaf } : {}),
203
- };
204
- }
9
+ import { EdgeLeaseCoordinator } from "./lease.js";
10
+ import { restoreMem, runTraverseWorker, seedMem, snapshotMem } from "./worker.js";
205
11
  /**
206
12
  * Serial or parallel graph crawl. Prints greppable PASS/FAIL + Coverage lines.
207
13
  * Exit: 0 leaf ok (+ coverage ok), 1 break/timeout, 2 coverage gate fail (Phase E).
@@ -0,0 +1,37 @@
1
+ import type { BrowserContext, Page } from "@playwright/test";
2
+ import type { WaygraphGraph } from "../../analysis/graph.js";
3
+ import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
4
+ import { Engine } from "../../engine/engine.js";
5
+ import { MemPage, type MemKey } from "../../engine/mem/mem.js";
6
+ import type { BlockEntry } from "../../manifest/project.js";
7
+ import { buildExploreContext, type ExploreEdge } from "../auto/explore.js";
8
+ import { EdgeLeaseCoordinator } from "./lease.js";
9
+ import type { TraverseWorkerResult } from "./traverse.js";
10
+ export declare function seedMem(library: Map<string, BlockEntry>, mem: MemPage, dataJson?: string): void;
11
+ export declare function snapshotMem(library: Map<string, BlockEntry>, mem: MemPage): Record<string, unknown>;
12
+ export declare function restoreMem(library: Map<string, BlockEntry>, mem: MemPage, snap: Record<string, unknown>, dataJson?: string): void;
13
+ export declare function missingMemKeys(entry: BlockEntry, mem: MemPage): MemKey<unknown>[];
14
+ export declare function ensureMem(entry: BlockEntry, mem: MemPage): void;
15
+ export declare function edgeKey(edge: ExploreEdge): string;
16
+ export declare function runOneBlock(engine: Engine, entry: BlockEntry, context: BrowserContext, page: Page, mem: MemPage): Promise<Checkpoint<string>>;
17
+ export interface WorkerOpts {
18
+ traverseId: string;
19
+ workerIndex: number;
20
+ parallel: number;
21
+ maxSteps: number;
22
+ maxVisitsPerNode: number;
23
+ maxVisitsPerEdge: number;
24
+ timeoutMs: number;
25
+ from?: string;
26
+ startUrl?: string;
27
+ baseURL?: string;
28
+ data?: string;
29
+ graph: WaygraphGraph;
30
+ library: Awaited<ReturnType<typeof buildExploreContext>>["library"];
31
+ engine: Engine;
32
+ context: BrowserContext;
33
+ page: Page;
34
+ mem: MemPage;
35
+ leases: EdgeLeaseCoordinator;
36
+ }
37
+ export declare function runTraverseWorker(opts: WorkerOpts): Promise<TraverseWorkerResult>;
@@ -0,0 +1,200 @@
1
+ import { envString } from "../../engine/env.js";
2
+ import { buildFlow } from "../../engine/flow/flow.js";
3
+ import { ignorePageGone } from "../../engine/page-gone.js";
4
+ import { detectHere } from "../auto/auto.js";
5
+ import { buildExploreMenu } from "../auto/explore.js";
6
+ import { memStubValue } from "../auto/mem.js";
7
+ import { EdgeLeaseCoordinator } from "./lease.js";
8
+ export function seedMem(library, mem, dataJson) {
9
+ const raw = dataJson ?? envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
10
+ let envParsed = {};
11
+ if (raw) {
12
+ try {
13
+ envParsed = JSON.parse(raw);
14
+ }
15
+ catch {
16
+ throw new Error(`waygraph traverse: --data / WAYGRAPH_DATA is not valid JSON`);
17
+ }
18
+ }
19
+ for (const entry of library.values()) {
20
+ for (const k of entry.block.requires ?? []) {
21
+ if (mem.has(k))
22
+ continue;
23
+ if (k.name in envParsed) {
24
+ mem.set(k, envParsed[k.name]);
25
+ continue;
26
+ }
27
+ const def = memStubValue(k);
28
+ if (def !== undefined)
29
+ mem.set(k, def);
30
+ }
31
+ }
32
+ }
33
+ export function snapshotMem(library, mem) {
34
+ const out = {};
35
+ for (const entry of library.values()) {
36
+ for (const k of entry.block.requires ?? []) {
37
+ if (mem.has(k) && !(k.name in out)) {
38
+ out[k.name] = mem.get(k);
39
+ }
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ export function restoreMem(library, mem, snap, dataJson) {
45
+ seedMem(library, mem, dataJson);
46
+ for (const entry of library.values()) {
47
+ for (const k of entry.block.requires ?? []) {
48
+ if (k.name in snap) {
49
+ mem.set(k, snap[k.name]);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ export function missingMemKeys(entry, mem) {
55
+ return (entry.block.requires ?? []).filter((k) => !mem.has(k));
56
+ }
57
+ export function ensureMem(entry, mem) {
58
+ const missing = missingMemKeys(entry, mem);
59
+ if (missing.length === 0)
60
+ return;
61
+ throw new Error(`Block "${entry.block.name}" needs Mem keys: ${missing.map((k) => k.name).join(", ")} ` +
62
+ `(pass --data '{...}' or WAYGRAPH_DATA)`);
63
+ }
64
+ export function edgeKey(edge) {
65
+ const id = edge.instanceOption?.id;
66
+ return id ? `${edge.block}::${id}` : edge.block;
67
+ }
68
+ export async function runOneBlock(engine, entry, context, page, mem) {
69
+ const flow = buildFlow([entry.block], engine.config);
70
+ const BLOCK_TIMEOUT_MS = 45000;
71
+ let timer;
72
+ try {
73
+ const ran = await Promise.race([
74
+ flow.run(context, mem, { page, closeOnFinish: false }),
75
+ new Promise((_, reject) => {
76
+ timer = setTimeout(() => reject(new Error(`Block "${entry.block.name}" timed out after ${BLOCK_TIMEOUT_MS / 1000}s`)), BLOCK_TIMEOUT_MS);
77
+ }),
78
+ ]);
79
+ return ran.result;
80
+ }
81
+ finally {
82
+ if (timer)
83
+ clearTimeout(timer);
84
+ }
85
+ }
86
+ export async function runTraverseWorker(opts) {
87
+ const { traverseId, workerIndex, parallel, maxSteps, maxVisitsPerNode, maxVisitsPerEdge, timeoutMs, graph, library, engine, context, mem, leases, } = opts;
88
+ let page = opts.page;
89
+ const startUrl = opts.startUrl;
90
+ const nodeVisits = new Map();
91
+ const edgeVisits = new Map();
92
+ const edgesHit = new Set();
93
+ let steps = 0;
94
+ const startedAt = Date.now();
95
+ let lastHere = opts.from ?? null;
96
+ let exitCode = 0;
97
+ let leaf;
98
+ try {
99
+ for (;;) {
100
+ if (Date.now() - startedAt > timeoutMs) {
101
+ console.log(`[Killed timeout[${traverseId}] node=${lastHere ?? "?"} wallMs=${timeoutMs}]`);
102
+ exitCode = 1;
103
+ break;
104
+ }
105
+ if (steps >= maxSteps) {
106
+ console.log(`[Killed max-steps[${traverseId}] node=${lastHere ?? "?"} steps=${steps} max=${maxSteps}]`);
107
+ exitCode = 1;
108
+ break;
109
+ }
110
+ if (page.isClosed()) {
111
+ page = await context.newPage();
112
+ if (startUrl) {
113
+ await page.goto(startUrl, { waitUntil: "domcontentloaded" }).catch(ignorePageGone);
114
+ }
115
+ }
116
+ const detected = await detectHere(page, library.navBlocks);
117
+ const here = lastHere ?? detected ?? opts.from ?? null;
118
+ if (here && (nodeVisits.get(here) ?? 0) >= maxVisitsPerNode) {
119
+ console.log(`[Killed loop[${traverseId}] node=${here} visits=${nodeVisits.get(here)} max=${maxVisitsPerNode}]`);
120
+ exitCode = 1;
121
+ break;
122
+ }
123
+ const menu = await buildExploreMenu(page, graph, library, here);
124
+ const candidates = menu.flat.filter((edge) => {
125
+ const key = edgeKey(edge);
126
+ if ((edgeVisits.get(key) ?? 0) >= maxVisitsPerEdge)
127
+ return false;
128
+ if (parallel > 1) {
129
+ if (EdgeLeaseCoordinator.partitionIndex(key, parallel) !== workerIndex) {
130
+ return false;
131
+ }
132
+ }
133
+ return true;
134
+ });
135
+ if (candidates.length === 0) {
136
+ const label = here ?? detected ?? "unknown";
137
+ leaf = label;
138
+ console.log(`[Reached Leaf Node[${traverseId}] at=${label} steps=${steps}]`);
139
+ console.error(`waygraph traverse[${traverseId}]: PASS leaf edgesHit=${edgesHit.size} steps=${steps}`);
140
+ break;
141
+ }
142
+ const pick = [...candidates].sort((a, b) => {
143
+ const aWild = a.from === "*" ? 1 : 0;
144
+ const bWild = b.from === "*" ? 1 : 0;
145
+ if (aWild !== bWild)
146
+ return aWild - bWild;
147
+ return edgeKey(a).localeCompare(edgeKey(b));
148
+ })[0];
149
+ const key = edgeKey(pick);
150
+ if (!leases.tryClaim(key, traverseId)) {
151
+ // Another worker won a race (instance fan-out); treat as unavailable.
152
+ edgeVisits.set(key, maxVisitsPerEdge);
153
+ continue;
154
+ }
155
+ const entry = library.byName.get(pick.block);
156
+ if (!entry) {
157
+ console.log(`[Broke at edge[${traverseId}] block=${pick.block} from=${here ?? "?"} to=${pick.to} reason=not-loaded]`);
158
+ exitCode = 1;
159
+ break;
160
+ }
161
+ const fromLabel = here ?? pick.from;
162
+ try {
163
+ if (pick.instanceOption) {
164
+ mem.set(pick.instanceOption.key, pick.instanceOption.value);
165
+ }
166
+ ensureMem(entry, mem);
167
+ console.error(`waygraph traverse[${traverseId}]: step ${steps + 1} ${fromLabel} -[${pick.block}]-> ${pick.to}` +
168
+ (pick.label ? ` (${pick.label})` : ""));
169
+ const out = await runOneBlock(engine, entry, context, page, mem);
170
+ const observed = typeof out === "object" && out && "__state" in out
171
+ ? String(out.__state)
172
+ : pick.to;
173
+ edgeVisits.set(key, (edgeVisits.get(key) ?? 0) + 1);
174
+ edgesHit.add(key);
175
+ if (here)
176
+ nodeVisits.set(here, (nodeVisits.get(here) ?? 0) + 1);
177
+ lastHere = observed;
178
+ steps += 1;
179
+ if (observed !== pick.to && pick.to !== "*") {
180
+ console.error(`waygraph traverse[${traverseId}]: WARN static to=${pick.to} observed=${observed}`);
181
+ }
182
+ }
183
+ catch (err) {
184
+ const msg = err instanceof Error ? err.message : String(err);
185
+ console.log(`[Broke at edge[${traverseId}] block=${pick.block} from=${fromLabel} to=${pick.to}] ${msg}`);
186
+ exitCode = 1;
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ finally {
192
+ console.error(`waygraph traverse[${traverseId}]: done exit=${exitCode} steps=${steps} edgesHit=${edgesHit.size}`);
193
+ }
194
+ return {
195
+ exitCode,
196
+ steps,
197
+ edgesHit: [...edgesHit],
198
+ ...(leaf ? { leaf } : {}),
199
+ };
200
+ }
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
5
8
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
9
  "license": "MIT",
7
10
  "main": "./dist/index.js",
@@ -54,6 +57,7 @@
54
57
  "test:ui": "playwright test --ui",
55
58
  "test:unit": "node --import tsx/esm --test tests/unit/*.spec.ts",
56
59
  "example:saucedemo:install": "npm --prefix examples/saucedemo install",
60
+ "examples:link": "node scripts/link-examples.mjs",
57
61
  "example:saucedemo:test": "npm --prefix examples/saucedemo test",
58
62
  "example:saucedemo:auto": "npm --prefix examples/saucedemo run auto",
59
63
  "check:structure": "node scripts/check-structure.mjs",