waygraph 0.16.0 → 0.16.2
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/dist/cli/commands/browser.d.ts +2 -0
- package/dist/cli/commands/browser.js +109 -0
- package/dist/cli/commands/codemod-stubs.js +90 -2
- package/dist/cli/commands/pilot.d.ts +2 -0
- package/dist/cli/commands/pilot.js +102 -0
- package/dist/cli/commands/session-ops.d.ts +3 -0
- package/dist/cli/commands/session-ops.js +257 -0
- package/dist/cli/commands/session.js +20 -461
- package/dist/cli/run-chain.js +2 -2
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/util.js +1 -1
- package/dist/demo/narration/demo-pace.d.ts +2 -2
- package/dist/demo/narration/demo-pace.js +1 -1
- package/dist/demo/narration/device.d.ts +2 -2
- package/dist/demo/narration/merge.d.ts +5 -3
- package/dist/demo/narration/merge.js +3 -7
- package/dist/demo/narration/narration.d.ts +9 -13
- package/dist/demo/narration/narration.js +5 -17
- package/dist/demo/narration/stub-ctx.js +3 -3
- package/dist/demo/narration/todo-dock.d.ts +5 -5
- package/dist/demo/overlay/cursor.d.ts +9 -2
- package/dist/demo/overlay/cursor.js +1 -1
- package/dist/demo/overlay/device-stage.d.ts +15 -2
- package/dist/demo/overlay/inpage/banner.d.ts +2 -1
- package/dist/demo/overlay/inpage/banner.js +3 -3
- package/dist/demo/overlay/inpage/caption.d.ts +1 -1
- package/dist/demo/overlay/inpage/core.d.ts +2 -1
- package/dist/demo/overlay/inpage/core.js +1 -7
- package/dist/demo/overlay/inpage/device-chip.d.ts +1 -1
- package/dist/demo/overlay/inpage/device-chip.js +6 -6
- package/dist/demo/overlay/inpage/network.d.ts +3 -2
- package/dist/demo/overlay/inpage/network.js +1 -12
- package/dist/demo/overlay/inpage/panel-chrome.d.ts +1 -1
- package/dist/demo/overlay/inpage/panel-chrome.js +1 -1
- package/dist/demo/overlay/inpage/stamp.d.ts +1 -1
- package/dist/demo/overlay/inpage/stamp.js +1 -1
- package/dist/demo/overlay/inpage/swipe-zoom.d.ts +1 -1
- package/dist/demo/overlay/inpage/swipe-zoom.js +1 -1
- package/dist/demo/overlay/inpage/todos.d.ts +2 -1
- package/dist/demo/overlay/inpage/todos.js +1 -2
- package/dist/demo/overlay/network-capture.d.ts +2 -2
- package/dist/demo/overlay/network-capture.js +1 -1
- package/dist/demo/overlay/overlay-css.js +1 -55
- package/dist/demo/overlay/overlay-stage.d.ts +19 -11
- package/dist/demo/overlay/overlay.d.ts +2 -2
- package/dist/demo/overlay/overlay.js +3 -2
- package/dist/demo/overlay/rings.d.ts +38 -18
- package/dist/demo/overlay/rings.js +21 -62
- package/dist/demo/overlay/ui/overlay.css +40 -0
- package/dist/demo/stepper/after.js +11 -34
- package/dist/demo/stepper/banner-log.d.ts +4 -6
- package/dist/demo/stepper/banner-log.js +3 -1
- package/dist/demo/stepper/before.js +6 -4
- package/dist/demo/stepper/demo-log.d.ts +26 -9
- package/dist/demo/stepper/demo-log.js +10 -27
- package/dist/demo/stepper/interaction-patch.d.ts +3 -2
- package/dist/demo/stepper/interaction-patch.js +22 -34
- package/dist/demo/stepper/panel-after.d.ts +17 -0
- package/dist/demo/stepper/panel-after.js +166 -0
- package/dist/demo/stepper/panel-before.d.ts +12 -0
- package/dist/demo/stepper/panel-before.js +241 -0
- package/dist/demo/stepper/slides.d.ts +16 -1
- package/dist/demo/stepper/slides.js +21 -5
- package/dist/demo/stepper/step-panels.d.ts +57 -9
- package/dist/demo/stepper/step-panels.js +10 -427
- package/dist/demo/stepper/stepper.js +1 -1
- package/dist/demo/stepper/teardown.d.ts +4 -5
- package/dist/demo/stepper/teardown.js +4 -6
- package/dist/demo/stepper/todo-advance.d.ts +3 -2
- package/dist/demo/stepper/todo-advance.js +2 -2
- package/dist/demo/stepper/view.d.ts +4 -14
- package/dist/demo/types.d.ts +69 -1
- package/dist/demo/verbose-log.js +1 -1
- package/dist/engine/block/action-page.d.ts +53 -0
- package/dist/engine/block/action-page.js +1 -0
- package/dist/engine/block/block.d.ts +24 -90
- package/dist/engine/block/block.js +2 -77
- package/dist/engine/block/branch.d.ts +17 -0
- package/dist/engine/block/branch.js +40 -0
- package/dist/engine/block/dialog.d.ts +13 -0
- package/dist/engine/block/dialog.js +37 -0
- package/dist/engine/block/kinds/method.d.ts +0 -2
- package/dist/engine/block/kinds/method.js +0 -1
- package/dist/engine/block/kinds/nav.js +1 -1
- package/dist/engine/flow/map-branch.d.ts +9 -0
- package/dist/engine/flow/map-branch.js +57 -0
- package/dist/engine/flow/map-builder.d.ts +7 -22
- package/dist/engine/flow/map-builder.js +6 -145
- package/dist/engine/flow/map-steps.d.ts +30 -0
- package/dist/engine/flow/map-steps.js +87 -0
- package/dist/engine/run/run.d.ts +1 -19
- package/dist/engine/run/run.js +0 -88
- package/dist/engine/stub/ring.d.ts +2 -4
- package/dist/engine/stub/ring.js +1 -0
- package/dist/engine/stub/stub.d.ts +16 -8
- package/dist/engine/trait/trait.js +1 -1
- package/dist/index.d.ts +15 -12
- package/dist/index.js +9 -7
- package/dist/live/auto/auto.d.ts +3 -15
- package/dist/live/auto/auto.js +10 -420
- package/dist/live/auto/explore.d.ts +2 -2
- package/dist/live/auto/mem.d.ts +11 -0
- package/dist/live/auto/mem.js +85 -0
- package/dist/live/auto/menu.d.ts +30 -0
- package/dist/live/auto/menu.js +335 -0
- package/dist/live/auto-session/auto-session.d.ts +23 -103
- package/dist/live/auto-session/auto-session.js +100 -370
- package/dist/live/auto-session/console.d.ts +4 -0
- package/dist/live/auto-session/console.js +41 -0
- package/dist/live/auto-session/files.d.ts +44 -0
- package/dist/live/auto-session/files.js +145 -0
- package/dist/live/auto-session/helpers.d.ts +5 -2
- package/dist/live/auto-session/helpers.js +17 -0
- package/dist/live/auto-session/inspect.d.ts +4 -0
- package/dist/live/auto-session/inspect.js +75 -0
- package/dist/live/auto-session/ipc.d.ts +3 -22
- package/dist/live/auto-session/ipc.js +7 -147
- package/dist/live/auto-session/open-browser.d.ts +6 -0
- package/dist/live/auto-session/open-browser.js +47 -0
- package/dist/live/pilot/pilot.d.ts +1 -1
- package/dist/live/pilot/pilot.js +2 -1
- package/dist/live/traverse/traverse.js +6 -200
- package/dist/live/traverse/worker.d.ts +37 -0
- package/dist/live/traverse/worker.js +200 -0
- package/dist/runner/plan/find.js +2 -2
- package/dist/runner/plan/plan.js +1 -1
- package/dist/runner/plan/seed-mem.js +5 -5
- package/dist/runner/plan/spec.js +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/examples/saucedemo/src/flows/checkout.flow.ts +2 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +2 -1
- package/examples/saucedemo/src/stubs/device-showcase.ts +37 -43
- package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
- package/package.json +5 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/templates/quickstart/src/flows/checkout.flow.ts +2 -1
- package/templates/quickstart/src/flows/shop.flow.ts +2 -1
- package/templates/quickstart/src/stubs/device-showcase.ts +37 -43
- package/templates/quickstart/src/stubs/finish-order.ts +38 -0
- package/templates/scaffold/src/flows/home/hello.flow.ts +3 -0
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
package/dist/index.js
CHANGED
|
@@ -8,10 +8,12 @@ export { MemKey, MemPage, key, keyGroup } from "./engine/mem/mem.js";
|
|
|
8
8
|
export { registerMemStub, seedMemStub, getMemStub } from "./engine/mem/mem-stub.js";
|
|
9
9
|
export { css, byText, byRole } from "./engine/trait/selector.js";
|
|
10
10
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./engine/errors.js";
|
|
11
|
-
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText
|
|
12
|
-
export { runGraph, spawnTab,
|
|
11
|
+
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText } from "./engine/trait/trait.js";
|
|
12
|
+
export { runGraph, spawnTab, preflight, defineLayout } from "./engine/run/run.js";
|
|
13
13
|
export { Engine, map } from "./engine/engine.js";
|
|
14
|
-
export { branch
|
|
14
|
+
export { branch } from "./engine/block/branch.js";
|
|
15
|
+
export { withVerify, modVerify, modVerifyAll, defineBlock } from "./engine/block/block.js";
|
|
16
|
+
export { withDialog } from "./engine/block/dialog.js";
|
|
15
17
|
export { defineNavBlock, defineNavClickBlock, defineMemNavBlock } from "./engine/block/kinds/nav.js";
|
|
16
18
|
export { defineMethodBlock, defineAssertBlock } from "./engine/block/kinds/method.js";
|
|
17
19
|
export { definePageBlock } from "./engine/block/kinds/page.js";
|
|
@@ -21,7 +23,7 @@ export { locate } from "./engine/locate.js";
|
|
|
21
23
|
export { composeBlock, fastForwardComposeBlock, isFastForwardBlock } from "./engine/block/compose.js";
|
|
22
24
|
export { MapBuilder } from "./engine/flow/map-builder.js";
|
|
23
25
|
export { branchRoutes, collectBranchFlows, runBranchRegression } from "./engine/flow/branch-regression.js";
|
|
24
|
-
export { resolveHighlightSlots,
|
|
26
|
+
export { resolveHighlightSlots, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
|
|
25
27
|
export { resolveFixtureDwellMs, normalizeDemoPace, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT } from "./demo/narration/demo-pace.js";
|
|
26
28
|
export { formatHighlightCaption, applyHighlightStyleDefaults, toneIconPrefix } from "./demo/narration/style.js";
|
|
27
29
|
export { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight } from "./engine/stub/ring.js";
|
|
@@ -31,11 +33,11 @@ export { WAYGRAPH_RING_CSS } from "./demo/narration/css.js";
|
|
|
31
33
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
|
|
32
34
|
export { buildManifest } from "./manifest/manifest.js";
|
|
33
35
|
export { onRunEvent } from "./engine/run/events.js";
|
|
34
|
-
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp
|
|
36
|
+
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp } from "./manifest/select.js";
|
|
35
37
|
export { EdgeLeaseCoordinator } from "./live/traverse/lease.js";
|
|
36
38
|
export { runTraverse } from "./live/traverse/traverse.js";
|
|
37
|
-
export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport
|
|
38
|
-
export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady
|
|
39
|
+
export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport } from "./live/traverse/coverage.js";
|
|
40
|
+
export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady } from "./demo/overlay/beacon.js";
|
|
39
41
|
export { AutoSession } from "./live/auto-session/auto-session.js";
|
|
40
42
|
export { stubFilePath } from "./live/auto-session/helpers.js";
|
|
41
43
|
export { pilotStart } from "./live/pilot/pilot.js";
|
package/dist/live/auto/auto.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { BrowserContext, Page } from "@playwright/test";
|
|
2
|
+
import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
|
|
3
3
|
import { Engine } from "../../engine/engine.js";
|
|
4
4
|
import { MemPage } from "../../engine/mem/mem.js";
|
|
5
|
-
import type {
|
|
6
|
-
import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
|
|
5
|
+
import type { BlockEntry } from "../../manifest/project.js";
|
|
7
6
|
export interface AutoExploreOptions {
|
|
8
7
|
/** Terminal menu (browser still runs for act()). Default false = headful picker panel. */
|
|
9
8
|
cli?: boolean;
|
|
@@ -16,18 +15,7 @@ export interface AutoExploreOptions {
|
|
|
16
15
|
/** Which Checkpoint the page is on, asked of the project's own Nav / Page Blocks (their verify). */
|
|
17
16
|
export declare function detectHere(page: Page, navBlocks: BlockEntry[]): Promise<string | null>;
|
|
18
17
|
export declare function ensureLivePage(context: BrowserContext, page: Page, startUrl: string | undefined): Promise<Page>;
|
|
19
|
-
/** The project's own default for a key: its `registerMemStub` fake (live sessions load every `*.mem-stubs.ts`). */
|
|
20
|
-
export declare function memStubValue(key: MemKey<unknown>): unknown | undefined;
|
|
21
|
-
/** Pre-seeds required keys from WAYGRAPH_DATA and the project's mem stubs, so a session does not stop for mem forms. */
|
|
22
|
-
export declare function seedDefaultMem(library: Map<string, BlockEntry>, mem: MemPage): void;
|
|
23
|
-
export declare function ensureMem(entry: BlockEntry, mem: MemPage, cli: boolean): Promise<void>;
|
|
24
18
|
export declare function runOneBlock(engine: Engine, entry: BlockEntry, context: BrowserContext, page: Page, mem: MemPage, headful: boolean): Promise<Checkpoint<string>>;
|
|
25
|
-
export type PickResult = {
|
|
26
|
-
type: "pick";
|
|
27
|
-
index: number;
|
|
28
|
-
} | {
|
|
29
|
-
type: "quit";
|
|
30
|
-
};
|
|
31
19
|
/**
|
|
32
20
|
* Interactive waygraph auto: locate current Checkpoint, list runnable Blocks, pick one, repeat.
|
|
33
21
|
*/
|
package/dist/live/auto/auto.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { getMemStub } from "../../engine/mem/mem-stub.js";
|
|
2
|
-
import { createInterface } from "node:readline/promises";
|
|
3
|
-
import { ensureShadowRoot } from "../../demo/overlay/ui/shadow.js";
|
|
4
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { installDemoChrome, instrumentAutoInteractions } from "../../demo/overlay/auto-chrome.js";
|
|
4
|
+
import { keepOverlayOnNavigation } from "../../demo/overlay/reattach.js";
|
|
5
5
|
import { Engine } from "../../engine/engine.js";
|
|
6
|
+
import { envString } from "../../engine/env.js";
|
|
6
7
|
import { buildFlow } from "../../engine/flow/flow.js";
|
|
7
8
|
import { locate } from "../../engine/locate.js";
|
|
8
9
|
import { MemPage } from "../../engine/mem/mem.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { keepOverlayOnNavigation } from "../../demo/overlay/reattach.js";
|
|
10
|
+
import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
|
|
11
|
+
import { chromiumExecutablePath, launchContext } from "../../runtime/browser.js";
|
|
12
12
|
import { configuredExtensions, resolveBaseUrl } from "../../runtime/waygraph-config.js";
|
|
13
13
|
import { withWebServer } from "../../runtime/webserver.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { buildExploreContext, buildExploreMenu, } from "./explore.js";
|
|
15
|
+
import { ensureMem, seedDefaultMem } from "./mem.js";
|
|
16
|
+
import { armPickWait, cliPick, headfulPick, printCliMenu, setAutoPanelRunning, syncPanelMem } from "./menu.js";
|
|
17
17
|
/** Which Checkpoint the page is on, asked of the project's own Nav / Page Blocks (their verify). */
|
|
18
18
|
export async function detectHere(page, navBlocks) {
|
|
19
19
|
const tags = navBlocks.map((n) => n.block);
|
|
@@ -29,112 +29,6 @@ export async function ensureLivePage(context, page, startUrl) {
|
|
|
29
29
|
}
|
|
30
30
|
return fresh;
|
|
31
31
|
}
|
|
32
|
-
function missingMemKeys(entry, mem) {
|
|
33
|
-
const requires = entry.block.requires ?? [];
|
|
34
|
-
return requires.filter((k) => !mem.has(k));
|
|
35
|
-
}
|
|
36
|
-
/** The project's own default for a key: its `registerMemStub` fake (live sessions load every `*.mem-stubs.ts`). */
|
|
37
|
-
export function memStubValue(key) {
|
|
38
|
-
return getMemStub(key)?.();
|
|
39
|
-
}
|
|
40
|
-
function defaultMemJsonPrompt(key) {
|
|
41
|
-
const def = memStubValue(key);
|
|
42
|
-
return def === undefined ? "{}" : JSON.stringify(def);
|
|
43
|
-
}
|
|
44
|
-
function seedMemFromEnv(mem, entry) {
|
|
45
|
-
const raw = envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
|
|
46
|
-
if (!raw)
|
|
47
|
-
return false;
|
|
48
|
-
try {
|
|
49
|
-
const parsed = JSON.parse(raw);
|
|
50
|
-
for (const k of entry.block.requires ?? []) {
|
|
51
|
-
if (k.name in parsed)
|
|
52
|
-
mem.set(k, parsed[k.name]);
|
|
53
|
-
}
|
|
54
|
-
return missingMemKeys(entry, mem).length === 0;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** Pre-seeds required keys from WAYGRAPH_DATA and the project's mem stubs, so a session does not stop for mem forms. */
|
|
61
|
-
export function seedDefaultMem(library, mem) {
|
|
62
|
-
const raw = envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
|
|
63
|
-
let envParsed = {};
|
|
64
|
-
if (raw) {
|
|
65
|
-
try {
|
|
66
|
-
envParsed = JSON.parse(raw);
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
throw new Error(`waygraph: --data / WAYGRAPH_DATA is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const entry of library.values()) {
|
|
73
|
-
for (const k of entry.block.requires ?? []) {
|
|
74
|
-
if (mem.has(k))
|
|
75
|
-
continue;
|
|
76
|
-
if (k.name in envParsed) {
|
|
77
|
-
mem.set(k, envParsed[k.name]);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const def = memStubValue(k);
|
|
81
|
-
if (def !== undefined)
|
|
82
|
-
mem.set(k, def);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async function promptMemCli(entry, mem) {
|
|
87
|
-
const rl = createInterface({ input, output });
|
|
88
|
-
try {
|
|
89
|
-
for (const k of missingMemKeys(entry, mem)) {
|
|
90
|
-
const def = defaultMemJsonPrompt(k);
|
|
91
|
-
const line = await rl.question(` ${k.name} JSON [${def}]: `);
|
|
92
|
-
const json = line.trim() || def;
|
|
93
|
-
mem.set(k, JSON.parse(json));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
finally {
|
|
97
|
-
rl.close();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
export async function ensureMem(entry, mem, cli) {
|
|
101
|
-
if (missingMemKeys(entry, mem).length === 0)
|
|
102
|
-
return;
|
|
103
|
-
if (seedMemFromEnv(mem, entry))
|
|
104
|
-
return;
|
|
105
|
-
if (cli) {
|
|
106
|
-
console.log(`\nBlock "${entry.block.name}" needs MemPage keys:`);
|
|
107
|
-
await promptMemCli(entry, mem);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
throw new Error(`Block "${entry.block.name}" needs MemPage keys - pass --data '{...}' / WAYGRAPH_DATA (or WAYGRAPH_AUTO_MEM), or enter values in --cli`);
|
|
111
|
-
}
|
|
112
|
-
async function setAutoPanelRunning(page, label) {
|
|
113
|
-
await page
|
|
114
|
-
.evaluate((label) => {
|
|
115
|
-
const panel = __wgById("wg-auto-panel");
|
|
116
|
-
if (!panel)
|
|
117
|
-
return;
|
|
118
|
-
__wgSet(panel, __wgEl("h3", null, "waygraph auto"), __wgEl("div", { style: "padding:12px 0;color:#c9a6ff" }, "Running: ", __wgEl("strong", null, label)));
|
|
119
|
-
if (typeof window.__wgWirePanelChrome === "function") {
|
|
120
|
-
window.__wgWirePanelChrome(panel, "wg-auto-panel-hidden", "waygraph auto");
|
|
121
|
-
}
|
|
122
|
-
if (typeof window.__wgStampModal === "function") {
|
|
123
|
-
window.__wgStampModal(panel, "auto-panel", {
|
|
124
|
-
phase: "running",
|
|
125
|
-
block: label,
|
|
126
|
-
ready: true,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
panel.setAttribute("data-wg-ui", "1");
|
|
131
|
-
panel.setAttribute("data-wg-modal", "auto-panel");
|
|
132
|
-
panel.setAttribute("data-wg-phase", "running");
|
|
133
|
-
panel.setAttribute("data-wg-ready", "1");
|
|
134
|
-
}
|
|
135
|
-
}, label)
|
|
136
|
-
.catch(ignorePageGone);
|
|
137
|
-
}
|
|
138
32
|
export async function runOneBlock(engine, entry, context, page, mem, headful) {
|
|
139
33
|
const navClick = entry.block.__waygraphNavClick;
|
|
140
34
|
if (entry.kind === "nav" && navClick !== undefined) {
|
|
@@ -167,312 +61,8 @@ export async function runOneBlock(engine, entry, context, page, mem, headful) {
|
|
|
167
61
|
await page.evaluate("delete window.__wgPendingNavClickLabel").catch(ignorePageGone);
|
|
168
62
|
return result;
|
|
169
63
|
}
|
|
170
|
-
const PANEL_CSS = "#wg-auto-panel{position:fixed;z-index:2147483647;left:50%;bottom:12px;transform:translateX(-50%);" +
|
|
171
|
-
"max-width:min(92vw,720px);max-height:calc(100vh - 24px);overflow-y:auto;box-sizing:border-box;" +
|
|
172
|
-
"background:rgba(20,10,40,.94);color:#fff;border-radius:14px;padding:16px 20px;" +
|
|
173
|
-
"font:14px/1.4 system-ui,sans-serif;box-shadow:0 12px 30px rgba(0,0,0,.35);}" +
|
|
174
|
-
"#wg-auto-panel h3{margin:0 0 10px;font-size:16px;}" +
|
|
175
|
-
"#wg-auto-panel .wg-here{color:#c9a6ff;margin-bottom:12px;}" +
|
|
176
|
-
"#wg-auto-panel .wg-actions{display:flex;flex-direction:column;gap:8px;}" +
|
|
177
|
-
"#wg-auto-panel button.wg-act{text-align:left;padding:10px 12px;border-radius:10px;border:1px solid #5b3aa8;" +
|
|
178
|
-
"background:#2a1450;color:#fff;font:13px/1.3 system-ui,sans-serif;cursor:pointer;}" +
|
|
179
|
-
"#wg-auto-panel button.wg-act:hover{background:#3a1d70;}" +
|
|
180
|
-
"#wg-auto-panel button.wg-act .wg-kind{color:#a78bfa;font-size:11px;text-transform:uppercase;}" +
|
|
181
|
-
"#wg-auto-panel button.wg-quit{margin-top:12px;background:#3a1a1a;border-color:#7f1d1d;}" +
|
|
182
|
-
"#wg-auto-panel textarea{width:100%;box-sizing:border-box;margin:6px 0 10px;background:#1a1030;color:#fff;" +
|
|
183
|
-
"border:1px solid #5b3aa8;border-radius:8px;padding:8px;font:12px/1.3 monospace;}" +
|
|
184
|
-
"#wg-auto-panel label.wg-mem{display:block;margin-top:8px;font-size:12px;color:#ddd;}" +
|
|
185
|
-
"#wg-auto-panel .wg-section{margin:12px 0 6px;font-size:11px;font-weight:700;color:#a78bfa;text-transform:uppercase;letter-spacing:.04em;}" +
|
|
186
|
-
"#wg-auto-panel .wg-map{margin:8px 0 12px;padding:8px 10px;border-radius:8px;background:rgba(0,0,0,.25);font-size:11px;line-height:1.5;}" +
|
|
187
|
-
"#wg-auto-panel .wg-map .wg-cp{opacity:.55;}#wg-auto-panel .wg-map .wg-cp-here{color:#fff;opacity:1;font-weight:700;}" +
|
|
188
|
-
"#wg-auto-panel .wg-chrome{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px;}" +
|
|
189
|
-
"#wg-auto-panel .wg-chrome-title{font:700 11px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
|
|
190
|
-
"letter-spacing:.04em;text-transform:uppercase;}" +
|
|
191
|
-
"#wg-auto-panel button.wg-hide-btn{margin:0;padding:4px 10px;font:600 11px system-ui,sans-serif;" +
|
|
192
|
-
"background:#3a2a60;color:#e8dcff;border:1px solid #5b3aa8;border-radius:6px;cursor:pointer;}" +
|
|
193
|
-
"#wg-auto-panel button.wg-hide-btn:hover{background:#4b2a80;}" +
|
|
194
|
-
"#wg-auto-panel.wg-collapsed{width:auto;max-width:90vw;padding:8px 12px;max-height:none;overflow:hidden;}" +
|
|
195
|
-
"#wg-auto-panel.wg-collapsed .wg-body{display:none;}" +
|
|
196
|
-
"#wg-auto-panel.wg-collapsed .wg-chrome{margin:0;}" +
|
|
197
|
-
"@media (max-width:640px){" +
|
|
198
|
-
"#wg-auto-panel{left:8px;right:8px;bottom:8px;transform:none;max-width:none;width:auto;" +
|
|
199
|
-
"max-height:min(55vh,calc(100vh - 16px));padding:12px 14px;border-radius:12px;}" +
|
|
200
|
-
"#wg-auto-panel.wg-collapsed{left:50%;right:auto;transform:translateX(-50%);width:auto;}" +
|
|
201
|
-
"#wg-auto-panel button.wg-act{padding:12px;font-size:14px;}" +
|
|
202
|
-
"}";
|
|
203
64
|
/** Shown in the headful panel after each Block run. */
|
|
204
65
|
let lastRunNote = null;
|
|
205
|
-
/**
|
|
206
|
-
* Mem keys any Block on the current menu might read this turn - minus keys a
|
|
207
|
-
* dynamic `instanceOptions` row already answers by picking it (e.g. "which
|
|
208
|
-
* item to add to cart" comes from the menu row itself, not a hand-typed JSON
|
|
209
|
-
* textarea).
|
|
210
|
-
*/
|
|
211
|
-
function memKeysForMenu(menu, library) {
|
|
212
|
-
const instanceKeys = new Set();
|
|
213
|
-
for (const edge of menu.flat) {
|
|
214
|
-
if (edge.instanceOption)
|
|
215
|
-
instanceKeys.add(edge.instanceOption.key);
|
|
216
|
-
}
|
|
217
|
-
const keys = new Map();
|
|
218
|
-
for (const edge of menu.flat) {
|
|
219
|
-
const entry = library.get(edge.block);
|
|
220
|
-
for (const k of entry?.block.requires ?? []) {
|
|
221
|
-
if (instanceKeys.has(k))
|
|
222
|
-
continue;
|
|
223
|
-
keys.set(k.name, k);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return [...keys.values()];
|
|
227
|
-
}
|
|
228
|
-
async function syncPanelMem(page, mem, keys) {
|
|
229
|
-
if (keys.length === 0)
|
|
230
|
-
return;
|
|
231
|
-
await ensureShadowRoot(page);
|
|
232
|
-
const edits = (await page.evaluate(() => {
|
|
233
|
-
const out = {};
|
|
234
|
-
__wgQA("textarea.wg-mem-input").forEach((ta) => {
|
|
235
|
-
const key = ta.getAttribute("data-mem-key");
|
|
236
|
-
if (key)
|
|
237
|
-
out[key] = ta.value;
|
|
238
|
-
});
|
|
239
|
-
return out;
|
|
240
|
-
}));
|
|
241
|
-
for (const k of keys) {
|
|
242
|
-
const raw = edits[k.name];
|
|
243
|
-
if (raw === undefined)
|
|
244
|
-
continue;
|
|
245
|
-
try {
|
|
246
|
-
mem.set(k, JSON.parse(raw));
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
throw new Error(`Invalid JSON for mem key "${k.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function armPickWait() {
|
|
254
|
-
let resolve;
|
|
255
|
-
const promise = new Promise((res) => {
|
|
256
|
-
resolve = res;
|
|
257
|
-
});
|
|
258
|
-
return { promise, resolve };
|
|
259
|
-
}
|
|
260
|
-
async function headfulPick(page, menu, library, mem, waitPick) {
|
|
261
|
-
const here = menu.here;
|
|
262
|
-
const hereLabel = here ?? (menu.flat.length > 0 ? "Unknown - pick Start here" : "Unknown - no moves available");
|
|
263
|
-
const menuMemKeys = memKeysForMenu(menu, library);
|
|
264
|
-
const memFields = menuMemKeys.map((k) => {
|
|
265
|
-
let value = "{}";
|
|
266
|
-
try {
|
|
267
|
-
if (mem.has(k))
|
|
268
|
-
value = JSON.stringify(mem.get(k), null, 2);
|
|
269
|
-
else {
|
|
270
|
-
const def = memStubValue(k);
|
|
271
|
-
if (def !== undefined)
|
|
272
|
-
value = JSON.stringify(def, null, 2);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
/* a value JSON can't encode shows as {} for the person to fill in */
|
|
277
|
-
}
|
|
278
|
-
return { name: k.name, value };
|
|
279
|
-
});
|
|
280
|
-
// Pre-resolve every candidate's hover-preview box THROUGH Playwright's own locator (>>
|
|
281
|
-
// piercing / :has-text() / :visible support), once, here on the Node side - not a raw selector
|
|
282
|
-
// string handed to the page for a per-mouseenter document.querySelector to fail silently on
|
|
283
|
-
// (same class of bug as rings.js's showRing; see its own comment for the full story). A
|
|
284
|
-
// mouseenter handler can't itself await a Node-side locator without visible hover lag, so the
|
|
285
|
-
// box is embedded as a data attribute instead and the in-page handler just reads it.
|
|
286
|
-
const hlSelectors = new Set();
|
|
287
|
-
for (const section of menu.sections) {
|
|
288
|
-
for (const c of section.edges) {
|
|
289
|
-
const entry = library.get(c.block);
|
|
290
|
-
const navClick = entry?.block?.__waygraphNavClick;
|
|
291
|
-
const hl = c.instanceOption?.highlight ?? (typeof navClick === "string" ? navClick : undefined);
|
|
292
|
-
if (hl)
|
|
293
|
-
hlSelectors.add(hl);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const hlBoxes = new Map();
|
|
297
|
-
await Promise.all([...hlSelectors].map(async (sel) => {
|
|
298
|
-
const loc = page.locator(sel).first();
|
|
299
|
-
const box = (await loc.count().catch(() => 0)) > 0
|
|
300
|
-
? await loc
|
|
301
|
-
.evaluate((el) => {
|
|
302
|
-
const r = el.getBoundingClientRect();
|
|
303
|
-
if (r.width < 1 && r.height < 1)
|
|
304
|
-
return null;
|
|
305
|
-
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
306
|
-
})
|
|
307
|
-
.catch(() => null)
|
|
308
|
-
: null;
|
|
309
|
-
hlBoxes.set(sel, box);
|
|
310
|
-
}));
|
|
311
|
-
let idx = 0;
|
|
312
|
-
const sections = menu.sections.map((section) => ({
|
|
313
|
-
title: section.title,
|
|
314
|
-
buttons: section.edges.map((c) => {
|
|
315
|
-
const entry = library.get(c.block);
|
|
316
|
-
const navClick = entry?.block?.__waygraphNavClick;
|
|
317
|
-
const hl = c.instanceOption?.highlight ?? (typeof navClick === "string" ? navClick : undefined);
|
|
318
|
-
const hlBox = hl ? hlBoxes.get(hl) : null;
|
|
319
|
-
const base = { idx: idx++, hlBox: hlBox ? JSON.stringify(hlBox) : undefined };
|
|
320
|
-
if (c.label)
|
|
321
|
-
return { ...base, label: c.label };
|
|
322
|
-
return { ...base, kind: c.kind, block: c.block, to: c.to, ...(entry?.description ? { description: entry.description } : {}) };
|
|
323
|
-
}),
|
|
324
|
-
}));
|
|
325
|
-
await ensureShadowRoot(page);
|
|
326
|
-
await page.evaluate(({ css, note, hereLabel, memFields, sections }) => {
|
|
327
|
-
__wgCss(css, "wg-auto-style");
|
|
328
|
-
__wgById("wg-auto-panel")?.remove();
|
|
329
|
-
const root = __wgEl("div", { id: "wg-auto-panel" }, __wgEl("h3", null, "waygraph auto"), note ? __wgEl("div", { style: "margin-bottom:8px;font-size:12px;color:#a7f3d0" }, note) : null, __wgEl("div", { class: "wg-here" }, "You are here: ", __wgEl("strong", null, hereLabel)), memFields.length ? __wgEl("div", { class: "wg-section" }, "Mem - edit credentials, then click a block below") : null, memFields.map((f, i) => {
|
|
330
|
-
const field = __wgEl("textarea", { class: "wg-mem-input", id: "wg-mem-" + i, data: { memKey: f.name }, attrs: { rows: 4 } });
|
|
331
|
-
field.value = f.value;
|
|
332
|
-
return [__wgEl("label", { class: "wg-mem" }, f.name), field];
|
|
333
|
-
}), __wgEl("div", { class: "wg-actions" }, sections.length === 0 ? __wgEl("div", { style: "opacity:.85;padding:8px 0" }, "No moves from this screen right now.") : null, sections.map((section) => [
|
|
334
|
-
__wgEl("div", { class: "wg-section" }, section.title),
|
|
335
|
-
section.buttons.map((b) => __wgEl("button", { class: "wg-act", data: { idx: b.idx, hlBox: b.hlBox } }, b.label !== undefined
|
|
336
|
-
? b.label
|
|
337
|
-
: [
|
|
338
|
-
__wgEl("span", { class: "wg-kind" }, b.kind ?? ""),
|
|
339
|
-
" ",
|
|
340
|
-
__wgEl("strong", null, b.block ?? ""),
|
|
341
|
-
" -> " + (b.to ?? ""),
|
|
342
|
-
b.description ? [__wgEl("br"), __wgEl("span", { style: "opacity:.85" }, b.description)] : null,
|
|
343
|
-
])),
|
|
344
|
-
])), __wgEl("button", { class: "wg-act wg-quit", data: { quit: "1" } }, "Quit"));
|
|
345
|
-
__wgAdd(root);
|
|
346
|
-
// Chrome + Hide/Show, shared with setAutoPanelRunning.
|
|
347
|
-
window.__wgWirePanelChrome = (el, storageKey, chromeTitle) => {
|
|
348
|
-
if (!el)
|
|
349
|
-
return;
|
|
350
|
-
if (!el.querySelector(":scope > .wg-chrome")) {
|
|
351
|
-
const body = __wgEl("div", { class: "wg-body" }, Array.from(el.childNodes));
|
|
352
|
-
const chrome = __wgEl("div", { class: "wg-chrome" }, __wgEl("span", { class: "wg-chrome-title" }, chromeTitle || "waygraph auto"), __wgEl("button", { class: "wg-hide-btn", data: { wgToggle: "1" }, attrs: { type: "button" } }, "Hide"));
|
|
353
|
-
el.append(chrome, body);
|
|
354
|
-
}
|
|
355
|
-
const apply = (hidden) => {
|
|
356
|
-
el.classList.toggle("wg-collapsed", hidden);
|
|
357
|
-
el.setAttribute("data-wg-collapsed", hidden ? "1" : "0");
|
|
358
|
-
const t = el.querySelector("[data-wg-toggle]");
|
|
359
|
-
if (t)
|
|
360
|
-
t.textContent = hidden ? "Show" : "Hide";
|
|
361
|
-
try {
|
|
362
|
-
localStorage.setItem(storageKey, hidden ? "1" : "0");
|
|
363
|
-
}
|
|
364
|
-
catch {
|
|
365
|
-
/* blocked storage: Hide still works for this page */
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
let hidden = false;
|
|
369
|
-
try {
|
|
370
|
-
hidden = localStorage.getItem(storageKey) === "1";
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
/* blocked storage: starts shown */
|
|
374
|
-
}
|
|
375
|
-
apply(hidden);
|
|
376
|
-
const toggle = el.querySelector("[data-wg-toggle]");
|
|
377
|
-
if (toggle && !toggle.dataset.wgWired) {
|
|
378
|
-
toggle.dataset.wgWired = "1";
|
|
379
|
-
toggle.addEventListener("click", (e) => {
|
|
380
|
-
e.stopPropagation();
|
|
381
|
-
apply(!el.classList.contains("wg-collapsed"));
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
window.__wgWirePanelChrome(root, "wg-auto-panel-hidden", "waygraph auto");
|
|
386
|
-
if (typeof window.__wgStampModal === "function") {
|
|
387
|
-
window.__wgStampModal(root, "auto-panel", { phase: "pick", ready: true });
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
root.setAttribute("data-wg-ui", "1");
|
|
391
|
-
root.setAttribute("data-wg-modal", "auto-panel");
|
|
392
|
-
root.setAttribute("data-wg-phase", "pick");
|
|
393
|
-
root.setAttribute("data-wg-collapsed", root.classList.contains("wg-collapsed") ? "1" : "0");
|
|
394
|
-
root.setAttribute("data-wg-ready", "1");
|
|
395
|
-
window.__wgOverlayBeacon =
|
|
396
|
-
window.__wgOverlayBeacon ||
|
|
397
|
-
(() => Array.from(__wgQA('[data-wg-ui="1"]')).map((el) => {
|
|
398
|
-
const r = el.getBoundingClientRect();
|
|
399
|
-
const st = getComputedStyle(el);
|
|
400
|
-
return {
|
|
401
|
-
id: el.id || null,
|
|
402
|
-
modal: el.getAttribute("data-wg-modal"),
|
|
403
|
-
ready: el.getAttribute("data-wg-ready") === "1",
|
|
404
|
-
phase: el.getAttribute("data-wg-phase"),
|
|
405
|
-
step: el.getAttribute("data-wg-step"),
|
|
406
|
-
block: el.getAttribute("data-wg-block"),
|
|
407
|
-
collapsed: el.getAttribute("data-wg-collapsed") === "1" || el.classList.contains("wg-collapsed"),
|
|
408
|
-
opacity: st.opacity,
|
|
409
|
-
textLen: (el.innerText || "").trim().length,
|
|
410
|
-
w: Math.round(r.width),
|
|
411
|
-
h: Math.round(r.height),
|
|
412
|
-
visible: r.width > 0 && r.height > 0 && Number(st.opacity) > 0.05,
|
|
413
|
-
};
|
|
414
|
-
}));
|
|
415
|
-
}
|
|
416
|
-
root.querySelectorAll("button[data-idx]").forEach((btn) => {
|
|
417
|
-
btn.addEventListener("click", () => window.__wgAutoPick?.(Number(btn.dataset.idx)));
|
|
418
|
-
btn.addEventListener("mouseenter", () => {
|
|
419
|
-
// Pre-resolved on the Node side via Playwright's own locator (>> piercing / :has-text()
|
|
420
|
-
// support) when this menu was built - see headfulPick's own comment for why a
|
|
421
|
-
// mouseenter handler can't itself round-trip to a Node-side locator per hover.
|
|
422
|
-
const raw = btn.dataset.hlBox;
|
|
423
|
-
if (!raw || typeof window.__wgPositionRing !== "function")
|
|
424
|
-
return;
|
|
425
|
-
window.__wgPositionRing(JSON.parse(raw), btn.textContent?.trim()?.slice(0, 80) || "target", "auto");
|
|
426
|
-
});
|
|
427
|
-
btn.addEventListener("mouseleave", () => window.__wgHideRing?.());
|
|
428
|
-
});
|
|
429
|
-
root.querySelector("[data-quit]")?.addEventListener("click", () => window.__wgAutoQuit?.());
|
|
430
|
-
}, { css: PANEL_CSS, note: lastRunNote, hereLabel, memFields, sections });
|
|
431
|
-
return waitPick();
|
|
432
|
-
}
|
|
433
|
-
function printCliMenu(menu, library) {
|
|
434
|
-
console.log("");
|
|
435
|
-
console.log(`You are here: ${menu.here ?? "Unknown (pick Start here)"}`);
|
|
436
|
-
console.log("");
|
|
437
|
-
if (menu.flat.length === 0) {
|
|
438
|
-
console.log(" (no moves available on this page)");
|
|
439
|
-
}
|
|
440
|
-
let n = 0;
|
|
441
|
-
for (const section of menu.sections) {
|
|
442
|
-
console.log(` -- ${section.title} --`);
|
|
443
|
-
for (const c of section.edges) {
|
|
444
|
-
const entry = library.get(c.block);
|
|
445
|
-
n++;
|
|
446
|
-
if (c.label) {
|
|
447
|
-
console.log(` [${n}] ${c.label}`);
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
const desc = entry?.description ? ` - ${entry.description}` : "";
|
|
451
|
-
console.log(` [${n}] ${c.block} (${c.kind}) -> ${c.to}${desc}`);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
console.log(" [q] quit");
|
|
456
|
-
console.log("");
|
|
457
|
-
}
|
|
458
|
-
async function cliPick(menu, rl) {
|
|
459
|
-
while (true) {
|
|
460
|
-
let ans;
|
|
461
|
-
try {
|
|
462
|
-
ans = (await rl.question("Choose: ")).trim().toLowerCase();
|
|
463
|
-
}
|
|
464
|
-
catch {
|
|
465
|
-
return { type: "quit" }; // stdin ended (piped / Ctrl-D) - same as q
|
|
466
|
-
}
|
|
467
|
-
if (ans === "q" || ans === "quit")
|
|
468
|
-
return { type: "quit" };
|
|
469
|
-
const n = Number(ans);
|
|
470
|
-
if (Number.isInteger(n) && n >= 1 && n <= menu.flat.length) {
|
|
471
|
-
return { type: "pick", index: n - 1 };
|
|
472
|
-
}
|
|
473
|
-
console.log("Enter a number from the list, or q to quit.");
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
66
|
/**
|
|
477
67
|
* Interactive waygraph auto: locate current Checkpoint, list runnable Blocks, pick one, repeat.
|
|
478
68
|
*/
|
|
@@ -573,7 +163,7 @@ async function runAutoExploreCore(projectDir, options = {}) {
|
|
|
573
163
|
pick = await cliPick(menu, cliRl);
|
|
574
164
|
}
|
|
575
165
|
else {
|
|
576
|
-
pick = await headfulPick(page, menu, library.byName, mem, () => pickWait.promise);
|
|
166
|
+
pick = await headfulPick(page, menu, library.byName, mem, () => pickWait.promise, lastRunNote);
|
|
577
167
|
}
|
|
578
168
|
if (pick.type === "quit")
|
|
579
169
|
break;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Page } from "@playwright/test";
|
|
2
|
-
import type
|
|
3
|
-
import
|
|
2
|
+
import { type WaygraphEdge, type WaygraphGraph } from "../../analysis/graph.js";
|
|
3
|
+
import type { WaygraphInstanceOption } from "../../engine/block/action-page.js";
|
|
4
4
|
export interface ExploreContextOpts {
|
|
5
5
|
blocksSelect?: import("../../manifest/select.js").BlocksSelect;
|
|
6
6
|
/** Absolute paths of extra Block trees to merge (host projectDir is always first). */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MemPage, type MemKey } from "../../engine/mem/mem.js";
|
|
2
|
+
import type { BlockEntry } from "../../manifest/project.js";
|
|
3
|
+
export declare function missingMemKeys(entry: BlockEntry, mem: MemPage): MemKey<unknown>[];
|
|
4
|
+
/** The project's own default for a key: its `registerMemStub` fake (live sessions load every `*.mem-stubs.ts`). */
|
|
5
|
+
export declare function memStubValue(key: MemKey<unknown>): unknown | undefined;
|
|
6
|
+
export declare function defaultMemJsonPrompt(key: MemKey<unknown>): string;
|
|
7
|
+
export declare function seedMemFromEnv(mem: MemPage, entry: BlockEntry): boolean;
|
|
8
|
+
/** Pre-seeds required keys from WAYGRAPH_DATA and the project's mem stubs, so a session does not stop for mem forms. */
|
|
9
|
+
export declare function seedDefaultMem(library: Map<string, BlockEntry>, mem: MemPage): void;
|
|
10
|
+
export declare function promptMemCli(entry: BlockEntry, mem: MemPage): Promise<void>;
|
|
11
|
+
export declare function ensureMem(entry: BlockEntry, mem: MemPage, cli: boolean): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Mem for a live session: --data first, then the project's mem stubs; the terminal prompt for what is missing.
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { envString } from "../../engine/env.js";
|
|
5
|
+
import { getMemStub } from "../../engine/mem/mem-stub.js";
|
|
6
|
+
export function missingMemKeys(entry, mem) {
|
|
7
|
+
const requires = entry.block.requires ?? [];
|
|
8
|
+
return requires.filter((k) => !mem.has(k));
|
|
9
|
+
}
|
|
10
|
+
/** The project's own default for a key: its `registerMemStub` fake (live sessions load every `*.mem-stubs.ts`). */
|
|
11
|
+
export function memStubValue(key) {
|
|
12
|
+
return getMemStub(key)?.();
|
|
13
|
+
}
|
|
14
|
+
export function defaultMemJsonPrompt(key) {
|
|
15
|
+
const def = memStubValue(key);
|
|
16
|
+
return def === undefined ? "{}" : JSON.stringify(def);
|
|
17
|
+
}
|
|
18
|
+
export function seedMemFromEnv(mem, entry) {
|
|
19
|
+
const raw = envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
|
|
20
|
+
if (!raw)
|
|
21
|
+
return false;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
for (const k of entry.block.requires ?? []) {
|
|
25
|
+
if (k.name in parsed)
|
|
26
|
+
mem.set(k, parsed[k.name]);
|
|
27
|
+
}
|
|
28
|
+
return missingMemKeys(entry, mem).length === 0;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Pre-seeds required keys from WAYGRAPH_DATA and the project's mem stubs, so a session does not stop for mem forms. */
|
|
35
|
+
export function seedDefaultMem(library, mem) {
|
|
36
|
+
const raw = envString("WAYGRAPH_DATA") ?? envString("WAYGRAPH_AUTO_MEM");
|
|
37
|
+
let envParsed = {};
|
|
38
|
+
if (raw) {
|
|
39
|
+
try {
|
|
40
|
+
envParsed = JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
throw new Error(`waygraph: --data / WAYGRAPH_DATA is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const entry of library.values()) {
|
|
47
|
+
for (const k of entry.block.requires ?? []) {
|
|
48
|
+
if (mem.has(k))
|
|
49
|
+
continue;
|
|
50
|
+
if (k.name in envParsed) {
|
|
51
|
+
mem.set(k, envParsed[k.name]);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const def = memStubValue(k);
|
|
55
|
+
if (def !== undefined)
|
|
56
|
+
mem.set(k, def);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function promptMemCli(entry, mem) {
|
|
61
|
+
const rl = createInterface({ input, output });
|
|
62
|
+
try {
|
|
63
|
+
for (const k of missingMemKeys(entry, mem)) {
|
|
64
|
+
const def = defaultMemJsonPrompt(k);
|
|
65
|
+
const line = await rl.question(` ${k.name} JSON [${def}]: `);
|
|
66
|
+
const json = line.trim() || def;
|
|
67
|
+
mem.set(k, JSON.parse(json));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
rl.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function ensureMem(entry, mem, cli) {
|
|
75
|
+
if (missingMemKeys(entry, mem).length === 0)
|
|
76
|
+
return;
|
|
77
|
+
if (seedMemFromEnv(mem, entry))
|
|
78
|
+
return;
|
|
79
|
+
if (cli) {
|
|
80
|
+
console.log(`\nBlock "${entry.block.name}" needs MemPage keys:`);
|
|
81
|
+
await promptMemCli(entry, mem);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Block "${entry.block.name}" needs MemPage keys - pass --data '{...}' / WAYGRAPH_DATA (or WAYGRAPH_AUTO_MEM), or enter values in --cli`);
|
|
85
|
+
}
|