waygraph 0.15.10 → 0.15.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/auto-explore.d.ts +7 -6
- package/dist/auto-explore.js +43 -32
- package/dist/auto-session-ipc.d.ts +13 -0
- package/dist/auto-session-ipc.js +119 -7
- package/dist/auto-session.d.ts +16 -13
- package/dist/auto-session.js +72 -53
- package/dist/block-inject.d.ts +15 -0
- package/dist/block-inject.js +91 -0
- package/dist/browser.d.ts +15 -0
- package/dist/browser.js +29 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +323 -47
- package/dist/coverage-gap.d.ts +11 -0
- package/dist/coverage-gap.js +119 -0
- package/dist/graph.d.ts +1 -0
- package/dist/graph.js +99 -91
- package/dist/pilot-overlay.d.ts +7 -1
- package/dist/pilot-overlay.js +59 -41
- package/dist/pilot.d.ts +7 -2
- package/dist/pilot.js +34 -7
- package/dist/practices-check.d.ts +14 -0
- package/dist/practices-check.js +115 -0
- package/examples/saucedemo/.waygraph-auto/0b937f26.log +4 -0
- package/examples/saucedemo/.waygraph-auto/2b608ed3.log +0 -0
- package/examples/saucedemo/.waygraph-auto/2c13567b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/2d9622a5.log +4 -0
- package/examples/saucedemo/.waygraph-auto/305f6e5b.log +4 -0
- package/examples/saucedemo/.waygraph-auto/418befbf.log +4 -0
- package/examples/saucedemo/.waygraph-auto/483bd763.log +4 -0
- package/examples/saucedemo/.waygraph-auto/48e3596a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4adfbe91.json +8 -0
- package/examples/saucedemo/.waygraph-auto/4adfbe91.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4f0e33e7.log +4 -0
- package/examples/saucedemo/.waygraph-auto/5bd8fedf.log +4 -0
- package/examples/saucedemo/.waygraph-auto/5dde8331.log +4 -0
- package/examples/saucedemo/.waygraph-auto/68903903.log +0 -0
- package/examples/saucedemo/.waygraph-auto/78bbf43c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7efa646f.log +4 -0
- package/examples/saucedemo/.waygraph-auto/9cecb0c8.log +4 -0
- package/examples/saucedemo/.waygraph-auto/aae57d51.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b61a08d8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/c946f66c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/cd0d5b50.log +4 -0
- package/examples/saucedemo/.waygraph-auto/cd5b203c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ea2df316.log +4 -0
- package/examples/saucedemo/.waygraph-auto/ee60ed24.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f2b6cb1b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f392cdc8.log +0 -0
- package/package.json +2 -2
- package/templates/scaffold/package.json +1 -1
- package/templates/skills/waygraph-convention.skill.md +11 -1
- package/templates/skills/waygraph-pilot.skill.md +104 -10
package/dist/auto-session.js
CHANGED
|
@@ -5,7 +5,8 @@ import { buildExploreContext, buildExploreMenu, } from "./auto-explore.js";
|
|
|
5
5
|
import { ensureLivePage, detectHere, seedDefaultMem, ensureMem, runOneBlock, resolveBaseUrl, } from "./auto-explore-run.js";
|
|
6
6
|
import { runStubPhase } from "./highlights.js";
|
|
7
7
|
import { findBlockPathDetailed } from "./graph.js";
|
|
8
|
-
import { updatePilotOverlay, showPilotActivity, showPilotVision, showPilotFixtures } from "./pilot-overlay.js";
|
|
8
|
+
import { updatePilotOverlay, showPilotActivity, showPilotVision, showPilotFixtures, installPersistentPilotOverlay, } from "./pilot-overlay.js";
|
|
9
|
+
import { collectKnownInteractionSelectors, unmappedInteractionsPageScript, } from "./coverage-gap.js";
|
|
9
10
|
/** True when a stub-phase result actually carries authored content worth keeping. */
|
|
10
11
|
function stubPhaseHasContent(r) {
|
|
11
12
|
return (Object.keys(r.highlights).length > 0 ||
|
|
@@ -142,6 +143,7 @@ const CONSOLE_LOG_MAX_ENTRIES = 200;
|
|
|
142
143
|
*/
|
|
143
144
|
export class AutoSession {
|
|
144
145
|
projectDir;
|
|
146
|
+
inject;
|
|
145
147
|
blocksSelect;
|
|
146
148
|
graph;
|
|
147
149
|
library;
|
|
@@ -156,8 +158,12 @@ export class AutoSession {
|
|
|
156
158
|
lastRunNote = null;
|
|
157
159
|
trace = [];
|
|
158
160
|
consoleLog = [];
|
|
159
|
-
|
|
161
|
+
knownInteractionSelectors;
|
|
162
|
+
headless;
|
|
163
|
+
lastOverlayInfo = null;
|
|
164
|
+
constructor(projectDir, inject, blocksSelect, graph, library, mem, engine, browser, context, page, startUrl, sessionId, knownInteractionSelectors, headless) {
|
|
160
165
|
this.projectDir = projectDir;
|
|
166
|
+
this.inject = inject;
|
|
161
167
|
this.blocksSelect = blocksSelect;
|
|
162
168
|
this.graph = graph;
|
|
163
169
|
this.library = library;
|
|
@@ -168,11 +174,18 @@ export class AutoSession {
|
|
|
168
174
|
this.page = page;
|
|
169
175
|
this.startUrl = startUrl;
|
|
170
176
|
this.sessionId = sessionId;
|
|
177
|
+
this.knownInteractionSelectors = knownInteractionSelectors;
|
|
178
|
+
this.headless = headless;
|
|
171
179
|
}
|
|
172
180
|
static async start(init) {
|
|
173
181
|
const baseURL = init.baseURL ?? resolveBaseUrl(init.projectDir);
|
|
174
|
-
const
|
|
175
|
-
const
|
|
182
|
+
const skipNav = init.skipInitialNavigation === true;
|
|
183
|
+
const startUrl = skipNav ? undefined : (init.startUrl ?? baseURL);
|
|
184
|
+
const exploreOpts = {
|
|
185
|
+
...(init.blocksSelect ? { blocksSelect: init.blocksSelect } : {}),
|
|
186
|
+
...(init.inject?.length ? { inject: init.inject } : {}),
|
|
187
|
+
};
|
|
188
|
+
const { graph, library } = await buildExploreContext(init.projectDir, Object.keys(exploreOpts).length ? exploreOpts : undefined);
|
|
176
189
|
const mem = new MemPage();
|
|
177
190
|
seedDefaultMem(library.byName, mem);
|
|
178
191
|
const headless = init.headless ?? true;
|
|
@@ -188,6 +201,9 @@ export class AutoSession {
|
|
|
188
201
|
// headless keeps the fixed viewport since there's no real window to size.
|
|
189
202
|
const launchOpts = {
|
|
190
203
|
headless,
|
|
204
|
+
// Keep Playwright's default --no-startup-window for headful: without it
|
|
205
|
+
// Chromium opens its own "New Tab" window AND our context.newPage() opens
|
|
206
|
+
// a second about:blank window. bringToFront() below surfaces the real page.
|
|
191
207
|
args: headless ? [] : ["--start-maximized"],
|
|
192
208
|
};
|
|
193
209
|
if (executablePath)
|
|
@@ -199,12 +215,24 @@ export class AutoSession {
|
|
|
199
215
|
if (baseURL)
|
|
200
216
|
contextOpts.baseURL = baseURL;
|
|
201
217
|
const context = await browser.newContext(contextOpts);
|
|
218
|
+
await installPersistentPilotOverlay(context);
|
|
202
219
|
const page = await context.newPage();
|
|
203
220
|
if (startUrl) {
|
|
204
221
|
await page.goto(startUrl, { waitUntil: "domcontentloaded" }).catch(() => { });
|
|
205
222
|
await page.waitForLoadState("load").catch(() => { });
|
|
206
223
|
}
|
|
207
|
-
|
|
224
|
+
else {
|
|
225
|
+
await page.goto("about:blank").catch(() => { });
|
|
226
|
+
}
|
|
227
|
+
const knownInteractionSelectors = collectKnownInteractionSelectors(init.projectDir, library, init.inject);
|
|
228
|
+
const session = new AutoSession(init.projectDir, init.inject, init.blocksSelect, graph, library, mem, engine, browser, context, page, startUrl, init.sessionId, knownInteractionSelectors, headless);
|
|
229
|
+
page.on("load", () => {
|
|
230
|
+
void session.repaintOverlayAfterNavigation();
|
|
231
|
+
});
|
|
232
|
+
if (!headless) {
|
|
233
|
+
await session.bootstrapOverlay().catch(() => { });
|
|
234
|
+
await page.bringToFront().catch(() => { });
|
|
235
|
+
}
|
|
208
236
|
// Attached once, on the page created here - `ensureLivePage` only swaps
|
|
209
237
|
// to a fresh Page if the current one closed (a rare case, e.g. the human
|
|
210
238
|
// closing the tab), not on ordinary same-page SPA navigation, which is
|
|
@@ -277,65 +305,52 @@ export class AutoSession {
|
|
|
277
305
|
// whole point is a human watching the screen sees the SAME state a
|
|
278
306
|
// concurrent API caller just got back, not a stale frame that catches
|
|
279
307
|
// up moments later.
|
|
280
|
-
|
|
308
|
+
this.lastOverlayInfo = {
|
|
281
309
|
sessionId: this.sessionId,
|
|
282
310
|
snapshot: buildSessionSnapshot(menu, this.library.byName, this.here, this.lastRunNote),
|
|
283
311
|
graph: this.graph,
|
|
284
|
-
}
|
|
285
|
-
await this.
|
|
312
|
+
};
|
|
313
|
+
await updatePilotOverlay(this.page, this.lastOverlayInfo);
|
|
314
|
+
await this.warnUnmappedInteractions();
|
|
286
315
|
return menu;
|
|
287
316
|
}
|
|
317
|
+
/** Paint the overlay on about:blank / immediately after session start (headful browser). */
|
|
318
|
+
async bootstrapOverlay() {
|
|
319
|
+
await this.currentMenu();
|
|
320
|
+
}
|
|
321
|
+
/** Re-apply cached overlay state after navigation (init script restores shell only). */
|
|
322
|
+
async repaintOverlayAfterNavigation() {
|
|
323
|
+
if (this.lastOverlayInfo) {
|
|
324
|
+
await updatePilotOverlay(this.page, this.lastOverlayInfo).catch(() => { });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (!this.headless) {
|
|
328
|
+
await this.bootstrapOverlay().catch(() => { });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
288
331
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* had been doing by hand the whole time this feature was built. Compares
|
|
293
|
-
* every same-origin `<a href>` on the page against every NavBlock's own
|
|
294
|
-
* static url (`__waygraphNavUrl` - see `defineNavBlock`'s own comment for
|
|
295
|
-
* why only plain-string urls qualify), and `console.warn()`s each pathname
|
|
296
|
-
* with no NavBlock at all - surfaced through the same real console capture
|
|
297
|
-
* `auto console` already reads, not a separate channel. Deduped per path
|
|
298
|
-
* per page load (a `Set` living on `window`, so it survives repeated
|
|
299
|
-
* `currentMenu()` calls but resets on a real navigation) - once is enough
|
|
300
|
-
* to flag a gap, repeating it on every status/send call would just be noise.
|
|
332
|
+
* Flags same-origin `<a href>` paths and visible buttons with no NavBlock
|
|
333
|
+
* URL / Block selector in the loaded library — read via `auto console`.
|
|
334
|
+
* Deduped per page load so repeated status/send calls stay quiet.
|
|
301
335
|
*/
|
|
302
|
-
async
|
|
303
|
-
const
|
|
336
|
+
async warnUnmappedInteractions() {
|
|
337
|
+
const knownPathnames = [];
|
|
304
338
|
for (const entry of this.library.navBlocks) {
|
|
305
339
|
const url = entry.block.__waygraphNavUrl;
|
|
306
340
|
if (!url)
|
|
307
341
|
continue;
|
|
308
342
|
try {
|
|
309
|
-
|
|
343
|
+
knownPathnames.push(new URL(url, this.page.url()).pathname);
|
|
310
344
|
}
|
|
311
345
|
catch {
|
|
312
346
|
/* not a resolvable URL (relative to an unset base, etc.) - skip */
|
|
313
347
|
}
|
|
314
348
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
for (const a of Array.from(document.querySelectorAll("a[href]"))) {
|
|
321
|
-
let url;
|
|
322
|
-
try {
|
|
323
|
-
url = new URL(a.getAttribute("href") || "", location.href);
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
if (url.origin !== location.origin)
|
|
329
|
-
continue;
|
|
330
|
-
const path = url.pathname;
|
|
331
|
-
if (seenThisPass.has(path) || knownPathnames.includes(path) || w.__wgWarnedPaths.has(path))
|
|
332
|
-
continue;
|
|
333
|
-
seenThisPass.add(path);
|
|
334
|
-
w.__wgWarnedPaths.add(path);
|
|
335
|
-
console.warn(`[waygraph] unmapped nav link on this page: ${path} - no NavBlock covers this URL`);
|
|
336
|
-
}
|
|
337
|
-
}, Array.from(known))
|
|
338
|
-
.catch(() => { });
|
|
349
|
+
const payload = {
|
|
350
|
+
knownPathnames,
|
|
351
|
+
knownSelectors: this.knownInteractionSelectors,
|
|
352
|
+
};
|
|
353
|
+
await this.page.evaluate(unmappedInteractionsPageScript, payload).catch(() => { });
|
|
339
354
|
}
|
|
340
355
|
/** Pure getter - no Block runs, no mem/page mutation. */
|
|
341
356
|
async currentSnapshot() {
|
|
@@ -514,7 +529,11 @@ export class AutoSession {
|
|
|
514
529
|
async reloadLibrary() {
|
|
515
530
|
if (this.page)
|
|
516
531
|
await showPilotActivity(this.page, "Running: reload");
|
|
517
|
-
const
|
|
532
|
+
const exploreOpts = {
|
|
533
|
+
...(this.blocksSelect ? { blocksSelect: this.blocksSelect } : {}),
|
|
534
|
+
...(this.inject?.length ? { inject: this.inject } : {}),
|
|
535
|
+
};
|
|
536
|
+
const { graph, library } = await buildExploreContext(this.projectDir, Object.keys(exploreOpts).length ? exploreOpts : undefined);
|
|
518
537
|
this.graph = graph;
|
|
519
538
|
this.library = library;
|
|
520
539
|
if (this.page)
|
|
@@ -561,7 +580,7 @@ export class AutoSession {
|
|
|
561
580
|
*/
|
|
562
581
|
async rawClick(selector) {
|
|
563
582
|
this.page = await ensureLivePage(this.context, this.page, this.startUrl);
|
|
564
|
-
await showPilotActivity(this.page, `Running (
|
|
583
|
+
await showPilotActivity(this.page, `Running (no Block): click "${selector}"`);
|
|
565
584
|
const locator = this.page.locator(selector).first();
|
|
566
585
|
if ((await locator.count()) === 0) {
|
|
567
586
|
return { ok: false, error: `no element matches selector "${selector}"` };
|
|
@@ -579,7 +598,7 @@ export class AutoSession {
|
|
|
579
598
|
}
|
|
580
599
|
async rawType(selector, text) {
|
|
581
600
|
this.page = await ensureLivePage(this.context, this.page, this.startUrl);
|
|
582
|
-
await showPilotActivity(this.page, `Running (
|
|
601
|
+
await showPilotActivity(this.page, `Running (no Block): type into "${selector}"`);
|
|
583
602
|
const locator = this.page.locator(selector).first();
|
|
584
603
|
if ((await locator.count()) === 0) {
|
|
585
604
|
return { ok: false, error: `no element matches selector "${selector}"` };
|
|
@@ -604,7 +623,7 @@ export class AutoSession {
|
|
|
604
623
|
*/
|
|
605
624
|
async rawPress(selector, key) {
|
|
606
625
|
this.page = await ensureLivePage(this.context, this.page, this.startUrl);
|
|
607
|
-
await showPilotActivity(this.page, `Running (
|
|
626
|
+
await showPilotActivity(this.page, `Running (no Block): press "${key}" on "${selector}"`);
|
|
608
627
|
const locator = this.page.locator(selector).first();
|
|
609
628
|
if ((await locator.count()) === 0) {
|
|
610
629
|
return { ok: false, error: `no element matches selector "${selector}"` };
|
|
@@ -622,7 +641,7 @@ export class AutoSession {
|
|
|
622
641
|
}
|
|
623
642
|
async rawGoto(url) {
|
|
624
643
|
this.page = await ensureLivePage(this.context, this.page, this.startUrl);
|
|
625
|
-
await showPilotActivity(this.page, `Running (
|
|
644
|
+
await showPilotActivity(this.page, `Running (no Block): goto "${url}"`);
|
|
626
645
|
try {
|
|
627
646
|
await this.page.goto(url, { waitUntil: "domcontentloaded" });
|
|
628
647
|
}
|
|
@@ -649,7 +668,7 @@ export class AutoSession {
|
|
|
649
668
|
this.page = await ensureLivePage(this.context, this.page, this.startUrl);
|
|
650
669
|
const filePath = typeof stub === "string" ? stubFilePath(stub) : stub.filePath;
|
|
651
670
|
const label = typeof stub === "string" ? stub : filePath;
|
|
652
|
-
await showPilotActivity(this.page, `Running (
|
|
671
|
+
await showPilotActivity(this.page, `Running (no Block): upload ${label} into "${selector}"`);
|
|
653
672
|
const locator = this.page.locator(selector).first();
|
|
654
673
|
if ((await locator.count()) === 0) {
|
|
655
674
|
return { ok: false, error: `no element matches selector "${selector}"` };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function packageRoot(): string;
|
|
2
|
+
/** Human label for an injected root (used in merged `file` paths). */
|
|
3
|
+
export declare function injectLabel(root: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Display path for a Block discovered under an inject root (not the host cwd).
|
|
6
|
+
* Primary project files stay relative as-is; injected ones are tagged `@label/...`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function injectTaggedFile(hostDir: string, root: string, relFile: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one `--inject` token to an absolute directory (preset name or path).
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveInjectRoot(token: string, cwd?: string): string;
|
|
13
|
+
export declare function resolveInjectRoots(tokens: string[], cwd?: string): string[];
|
|
14
|
+
/** Roots to scan: host project first, then each inject path (deduped). */
|
|
15
|
+
export declare function exploreRoots(projectDir: string, inject?: string[]): string[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge Block libraries / graphs from extra project roots into a host session
|
|
3
|
+
* (waygraph browser `--inject`). Presets name bundled or well-known checkouts.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
export function packageRoot() {
|
|
10
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
}
|
|
12
|
+
/** Human label for an injected root (used in merged `file` paths). */
|
|
13
|
+
export function injectLabel(root) {
|
|
14
|
+
return basename(root.replace(/\/$/, ""));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Display path for a Block discovered under an inject root (not the host cwd).
|
|
18
|
+
* Primary project files stay relative as-is; injected ones are tagged `@label/...`.
|
|
19
|
+
*/
|
|
20
|
+
export function injectTaggedFile(hostDir, root, relFile) {
|
|
21
|
+
const host = resolve(hostDir);
|
|
22
|
+
const r = resolve(root);
|
|
23
|
+
if (r === host)
|
|
24
|
+
return relFile.replace(/\\/g, "/");
|
|
25
|
+
return `@${injectLabel(r)}/${relFile.replace(/\\/g, "/")}`;
|
|
26
|
+
}
|
|
27
|
+
const PRESETS = {
|
|
28
|
+
saucedemo: () => join(packageRoot(), "examples", "saucedemo"),
|
|
29
|
+
demosauce: () => join(packageRoot(), "examples", "saucedemo"),
|
|
30
|
+
veciro: () => {
|
|
31
|
+
if (process.env.WAYGRAPH_INJECT_VECIRO) {
|
|
32
|
+
return resolve(process.env.WAYGRAPH_INJECT_VECIRO);
|
|
33
|
+
}
|
|
34
|
+
for (const c of [
|
|
35
|
+
join(homedir(), "Desktop", "Work", "veciro-waygraph"),
|
|
36
|
+
join(homedir(), "Desktop", "Projects", "veciro-waygraph"),
|
|
37
|
+
]) {
|
|
38
|
+
if (existsSync(c))
|
|
39
|
+
return c;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolve one `--inject` token to an absolute directory (preset name or path).
|
|
46
|
+
*/
|
|
47
|
+
export function resolveInjectRoot(token, cwd = process.cwd()) {
|
|
48
|
+
const raw = token.trim();
|
|
49
|
+
if (!raw)
|
|
50
|
+
throw new Error("waygraph inject: empty --inject value");
|
|
51
|
+
const preset = PRESETS[raw.toLowerCase()];
|
|
52
|
+
if (preset) {
|
|
53
|
+
const dir = preset();
|
|
54
|
+
if (!dir || !existsSync(dir)) {
|
|
55
|
+
throw new Error(`waygraph inject: preset "${raw}" not found on disk (pass an absolute path to the Block library root)`);
|
|
56
|
+
}
|
|
57
|
+
return resolve(dir);
|
|
58
|
+
}
|
|
59
|
+
const abs = resolve(cwd, raw);
|
|
60
|
+
if (!existsSync(abs)) {
|
|
61
|
+
throw new Error(`waygraph inject: no such directory: ${abs}`);
|
|
62
|
+
}
|
|
63
|
+
return abs;
|
|
64
|
+
}
|
|
65
|
+
export function resolveInjectRoots(tokens, cwd = process.cwd()) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const t of tokens) {
|
|
69
|
+
const abs = resolveInjectRoot(t, cwd);
|
|
70
|
+
if (seen.has(abs))
|
|
71
|
+
continue;
|
|
72
|
+
seen.add(abs);
|
|
73
|
+
out.push(abs);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** Roots to scan: host project first, then each inject path (deduped). */
|
|
78
|
+
export function exploreRoots(projectDir, inject) {
|
|
79
|
+
const host = resolve(projectDir);
|
|
80
|
+
const extra = inject ?? [];
|
|
81
|
+
const seen = new Set([host]);
|
|
82
|
+
const roots = [host];
|
|
83
|
+
for (const r of extra) {
|
|
84
|
+
const abs = resolve(r);
|
|
85
|
+
if (seen.has(abs))
|
|
86
|
+
continue;
|
|
87
|
+
seen.add(abs);
|
|
88
|
+
roots.push(abs);
|
|
89
|
+
}
|
|
90
|
+
return roots;
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AutoSessionInit } from "./auto-session.js";
|
|
2
|
+
export interface BrowserStartInit extends AutoSessionInit {
|
|
3
|
+
/** Preset names or paths; resolved before spawn. */
|
|
4
|
+
injectTokens?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface BrowserStartResult {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
socketPath: string;
|
|
9
|
+
headless: boolean;
|
|
10
|
+
/** Resolved inject roots merged into this session (host project excluded). */
|
|
11
|
+
inject: string[];
|
|
12
|
+
}
|
|
13
|
+
/** Always spawns a new detached session — does not stop or reuse existing ones. */
|
|
14
|
+
export declare function browserStart(init: BrowserStartInit): Promise<BrowserStartResult>;
|
|
15
|
+
export { listBrowserSessions, stopBrowserSession, stopAllBrowserSessions, type SessionMeta, } from "./auto-session-ipc.js";
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `waygraph browser` — persistent Playwright session with the project's Block
|
|
3
|
+
* library loaded (and optional `--inject` roots). Pilot drives these sessions
|
|
4
|
+
* via `browser send|status|highlight|…`; it does not spawn its own browser.
|
|
5
|
+
*
|
|
6
|
+
* Session lifecycle is explicit: `browser sessions` lists what's live;
|
|
7
|
+
* `browser start` always opens another; `browser attach` / `browser stop` pick one up or shut it down.
|
|
8
|
+
*/
|
|
9
|
+
import { spawnDetachedSession } from "./auto-session-ipc.js";
|
|
10
|
+
import { resolveInjectRoots } from "./block-inject.js";
|
|
11
|
+
/** Always spawns a new detached session — does not stop or reuse existing ones. */
|
|
12
|
+
export async function browserStart(init) {
|
|
13
|
+
const inject = init.inject ??
|
|
14
|
+
(init.injectTokens?.length ? resolveInjectRoots(init.injectTokens, init.projectDir) : []);
|
|
15
|
+
const headless = init.headless ?? false;
|
|
16
|
+
const meta = await spawnDetachedSession({
|
|
17
|
+
...init,
|
|
18
|
+
inject,
|
|
19
|
+
headless,
|
|
20
|
+
skipInitialNavigation: init.skipInitialNavigation ?? true,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
sessionId: meta.sessionId,
|
|
24
|
+
socketPath: meta.socketPath,
|
|
25
|
+
headless: meta.headless,
|
|
26
|
+
inject,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export { listBrowserSessions, stopBrowserSession, stopAllBrowserSessions, } from "./auto-session-ipc.js";
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
|
|
11
|
+
* Skills: --skill / --skill-pilot / --skill-pilot-blind / --skill-convention
|
|
11
12
|
* Aliases (one release): `chain` -> run/demo --blocks; `--autoplay` -> `--auto-next`
|
|
12
13
|
*
|
|
13
14
|
* "project" defaults to cwd. Flags beat WAYGRAPH_* env.
|