gsd-pi 2.7.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/dist/loader.js +0 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme.js +949 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/node_modules/cliui/CHANGELOG.md +121 -0
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/node_modules/esprima/ChangeLog +235 -0
- package/node_modules/mz/HISTORY.md +66 -0
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/source-map/CHANGELOG.md +301 -0
- package/node_modules/thenify/History.md +11 -0
- package/node_modules/thenify-all/History.md +11 -0
- package/node_modules/y18n/CHANGELOG.md +100 -0
- package/node_modules/yargs/CHANGELOG.md +88 -0
- package/node_modules/yargs-parser/CHANGELOG.md +263 -0
- package/package.json +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/src/resources/extensions/browser-tools/capture.ts +165 -0
- package/src/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
- package/src/resources/extensions/browser-tools/index.ts +47 -4985
- package/src/resources/extensions/browser-tools/lifecycle.ts +265 -0
- package/src/resources/extensions/browser-tools/package.json +5 -1
- package/src/resources/extensions/browser-tools/refs.ts +264 -0
- package/src/resources/extensions/browser-tools/settle.ts +197 -0
- package/src/resources/extensions/browser-tools/state.ts +408 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
- package/src/resources/extensions/browser-tools/tools/assertions.ts +342 -0
- package/src/resources/extensions/browser-tools/tools/forms.ts +801 -0
- package/src/resources/extensions/browser-tools/tools/inspection.ts +492 -0
- package/src/resources/extensions/browser-tools/tools/intent.ts +614 -0
- package/src/resources/extensions/browser-tools/tools/interaction.ts +865 -0
- package/src/resources/extensions/browser-tools/tools/navigation.ts +232 -0
- package/src/resources/extensions/browser-tools/tools/pages.ts +303 -0
- package/src/resources/extensions/browser-tools/tools/refs.ts +541 -0
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +400 -0
- package/src/resources/extensions/browser-tools/tools/wait.ts +247 -0
- package/src/resources/extensions/browser-tools/utils.ts +660 -0
- package/src/resources/extensions/gsd/git-service.ts +3 -0
- package/src/resources/extensions/shared/interview-ui.ts +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-tools — DOM settle logic
|
|
3
|
+
*
|
|
4
|
+
* Adaptive settling after browser actions. Polls for DOM quiet (mutation
|
|
5
|
+
* counter stable, no pending critical requests, optional focus stability)
|
|
6
|
+
* before returning control.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Frame, Page } from "playwright";
|
|
10
|
+
import type { AdaptiveSettleDetails, AdaptiveSettleOptions } from "./state.js";
|
|
11
|
+
import { getPendingCriticalRequests } from "./utils.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Mutation counter (installed in-page via evaluate)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export async function ensureMutationCounter(p: Page): Promise<void> {
|
|
18
|
+
await p.evaluate(() => {
|
|
19
|
+
const key = "__piMutationCounter" as const;
|
|
20
|
+
const installedKey = "__piMutationCounterInstalled" as const;
|
|
21
|
+
const w = window as unknown as Record<string, unknown>;
|
|
22
|
+
if (typeof w[key] !== "number") w[key] = 0;
|
|
23
|
+
if (w[installedKey]) return;
|
|
24
|
+
const observer = new MutationObserver(() => {
|
|
25
|
+
const current = typeof w[key] === "number" ? (w[key] as number) : 0;
|
|
26
|
+
w[key] = current + 1;
|
|
27
|
+
});
|
|
28
|
+
observer.observe(document.documentElement || document.body, {
|
|
29
|
+
subtree: true,
|
|
30
|
+
childList: true,
|
|
31
|
+
attributes: true,
|
|
32
|
+
characterData: true,
|
|
33
|
+
});
|
|
34
|
+
w[installedKey] = true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function readMutationCounter(p: Page): Promise<number> {
|
|
39
|
+
try {
|
|
40
|
+
return await p.evaluate(() => {
|
|
41
|
+
const w = window as unknown as Record<string, unknown>;
|
|
42
|
+
const value = w.__piMutationCounter;
|
|
43
|
+
return typeof value === "number" ? value : 0;
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Focus descriptor (for focus-stability checks)
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export async function readFocusedDescriptor(target: Page | Frame): Promise<string> {
|
|
55
|
+
try {
|
|
56
|
+
return await target.evaluate(() => {
|
|
57
|
+
const el = document.activeElement as HTMLElement | null;
|
|
58
|
+
if (!el || el === document.body || el === document.documentElement) return "";
|
|
59
|
+
const id = el.id ? `#${el.id}` : "";
|
|
60
|
+
const role = el.getAttribute("role") || "";
|
|
61
|
+
const name = (el.getAttribute("aria-label") || el.getAttribute("name") || "").trim();
|
|
62
|
+
return `${el.tagName.toLowerCase()}${id}|${role}|${name}`;
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Combined settle-state reader (mutation counter + focus in one evaluate)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reads the mutation counter and optionally the focused element descriptor
|
|
75
|
+
* in a single `evaluate()` call, saving one round-trip per poll iteration.
|
|
76
|
+
*/
|
|
77
|
+
async function readSettleState(
|
|
78
|
+
target: Page | Frame,
|
|
79
|
+
checkFocus: boolean,
|
|
80
|
+
): Promise<{ mutationCount: number; focusDescriptor: string }> {
|
|
81
|
+
try {
|
|
82
|
+
return await target.evaluate((wantFocus: boolean) => {
|
|
83
|
+
const w = window as unknown as Record<string, unknown>;
|
|
84
|
+
const mutationCount = typeof w.__piMutationCounter === "number" ? (w.__piMutationCounter as number) : 0;
|
|
85
|
+
if (!wantFocus) return { mutationCount, focusDescriptor: "" };
|
|
86
|
+
const el = document.activeElement as HTMLElement | null;
|
|
87
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
88
|
+
return { mutationCount, focusDescriptor: "" };
|
|
89
|
+
}
|
|
90
|
+
const id = el.id ? `#${el.id}` : "";
|
|
91
|
+
const role = el.getAttribute("role") || "";
|
|
92
|
+
const name = (el.getAttribute("aria-label") || el.getAttribute("name") || "").trim();
|
|
93
|
+
return { mutationCount, focusDescriptor: `${el.tagName.toLowerCase()}${id}|${role}|${name}` };
|
|
94
|
+
}, checkFocus);
|
|
95
|
+
} catch {
|
|
96
|
+
return { mutationCount: 0, focusDescriptor: "" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Adaptive settle
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/** Threshold (ms) after which zero mutations triggers a shortened quiet window. */
|
|
105
|
+
const ZERO_MUTATION_THRESHOLD_MS = 60;
|
|
106
|
+
/** Shortened quiet window when no mutations have been observed. */
|
|
107
|
+
const ZERO_MUTATION_QUIET_MS = 30;
|
|
108
|
+
|
|
109
|
+
export async function settleAfterActionAdaptive(
|
|
110
|
+
p: Page,
|
|
111
|
+
opts: AdaptiveSettleOptions = {},
|
|
112
|
+
): Promise<AdaptiveSettleDetails> {
|
|
113
|
+
const timeoutMs = Math.max(150, opts.timeoutMs ?? 500);
|
|
114
|
+
const pollMs = Math.min(100, Math.max(20, opts.pollMs ?? 40));
|
|
115
|
+
const baseQuietWindowMs = Math.max(60, opts.quietWindowMs ?? 100);
|
|
116
|
+
const checkFocus = opts.checkFocusStability ?? false;
|
|
117
|
+
|
|
118
|
+
const startedAt = Date.now();
|
|
119
|
+
let polls = 0;
|
|
120
|
+
let sawUrlChange = false;
|
|
121
|
+
let lastActivityAt = startedAt;
|
|
122
|
+
let previousUrl = p.url();
|
|
123
|
+
let totalMutationsSeen = 0;
|
|
124
|
+
let activeQuietWindowMs = baseQuietWindowMs;
|
|
125
|
+
|
|
126
|
+
// Install mutation counter + read initial state in one evaluate sequence.
|
|
127
|
+
// ensureMutationCounter must run first (installs the observer), then we
|
|
128
|
+
// read the baseline via the combined reader.
|
|
129
|
+
await ensureMutationCounter(p).catch(() => {});
|
|
130
|
+
const initial = await readSettleState(p, checkFocus);
|
|
131
|
+
let previousMutationCount = initial.mutationCount;
|
|
132
|
+
let previousFocus = initial.focusDescriptor;
|
|
133
|
+
|
|
134
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
136
|
+
polls += 1;
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
|
|
139
|
+
const currentUrl = p.url();
|
|
140
|
+
if (currentUrl !== previousUrl) {
|
|
141
|
+
sawUrlChange = true;
|
|
142
|
+
previousUrl = currentUrl;
|
|
143
|
+
lastActivityAt = now;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Single combined evaluate for mutation count + focus descriptor.
|
|
147
|
+
const state = await readSettleState(p, checkFocus);
|
|
148
|
+
|
|
149
|
+
if (state.mutationCount > previousMutationCount) {
|
|
150
|
+
totalMutationsSeen += state.mutationCount - previousMutationCount;
|
|
151
|
+
previousMutationCount = state.mutationCount;
|
|
152
|
+
lastActivityAt = now;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (checkFocus && state.focusDescriptor !== previousFocus) {
|
|
156
|
+
previousFocus = state.focusDescriptor;
|
|
157
|
+
lastActivityAt = now;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const pendingCritical = getPendingCriticalRequests(p);
|
|
161
|
+
if (pendingCritical > 0) {
|
|
162
|
+
lastActivityAt = now;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Zero-mutation short-circuit: after ZERO_MUTATION_THRESHOLD_MS with
|
|
167
|
+
// no mutations observed at all, reduce the quiet window to settle faster.
|
|
168
|
+
if (
|
|
169
|
+
totalMutationsSeen === 0 &&
|
|
170
|
+
now - startedAt >= ZERO_MUTATION_THRESHOLD_MS &&
|
|
171
|
+
activeQuietWindowMs !== ZERO_MUTATION_QUIET_MS
|
|
172
|
+
) {
|
|
173
|
+
activeQuietWindowMs = ZERO_MUTATION_QUIET_MS;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (now - lastActivityAt >= activeQuietWindowMs) {
|
|
177
|
+
const usedShortcut = activeQuietWindowMs === ZERO_MUTATION_QUIET_MS && totalMutationsSeen === 0;
|
|
178
|
+
return {
|
|
179
|
+
settleMode: "adaptive",
|
|
180
|
+
settleMs: now - startedAt,
|
|
181
|
+
settleReason: usedShortcut
|
|
182
|
+
? "zero_mutation_shortcut"
|
|
183
|
+
: sawUrlChange
|
|
184
|
+
? "url_changed_then_quiet"
|
|
185
|
+
: "dom_quiet",
|
|
186
|
+
settlePolls: polls,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
settleMode: "adaptive",
|
|
193
|
+
settleMs: Date.now() - startedAt,
|
|
194
|
+
settleReason: "timeout_fallback",
|
|
195
|
+
settlePolls: polls,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-tools — shared mutable state
|
|
3
|
+
*
|
|
4
|
+
* All mutable state lives behind accessor functions (get/set) so that
|
|
5
|
+
* jiti-transpiled modules see updates reliably. ES module live bindings
|
|
6
|
+
* (`export let`) are not guaranteed to work under jiti's CJS shim layer.
|
|
7
|
+
*
|
|
8
|
+
* State is initialized to sensible defaults and can be bulk-reset via
|
|
9
|
+
* `resetAllState()` (called by closeBrowser).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Browser, BrowserContext, Frame, Page } from "playwright";
|
|
13
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
createActionTimeline,
|
|
17
|
+
createBoundedLogPusher,
|
|
18
|
+
createPageRegistry,
|
|
19
|
+
} from "./core.js";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export const ARTIFACT_ROOT = path.resolve(process.cwd(), ".artifacts", "browser");
|
|
26
|
+
export const HAR_FILENAME = "session.har";
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Type / interface definitions
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export interface ConsoleEntry {
|
|
33
|
+
type: string;
|
|
34
|
+
text: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
url: string;
|
|
37
|
+
pageId: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NetworkEntry {
|
|
41
|
+
method: string;
|
|
42
|
+
url: string;
|
|
43
|
+
status: number | null;
|
|
44
|
+
resourceType: string;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
failed: boolean;
|
|
47
|
+
failureText?: string;
|
|
48
|
+
responseBody?: string;
|
|
49
|
+
pageId: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DialogEntry {
|
|
53
|
+
type: string;
|
|
54
|
+
message: string;
|
|
55
|
+
timestamp: number;
|
|
56
|
+
url: string;
|
|
57
|
+
defaultValue?: string;
|
|
58
|
+
accepted: boolean;
|
|
59
|
+
pageId: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RefNode {
|
|
63
|
+
ref: string;
|
|
64
|
+
tag: string;
|
|
65
|
+
role: string;
|
|
66
|
+
name: string;
|
|
67
|
+
selectorHints: string[];
|
|
68
|
+
isVisible: boolean;
|
|
69
|
+
isEnabled: boolean;
|
|
70
|
+
xpathOrPath: string;
|
|
71
|
+
href?: string;
|
|
72
|
+
type?: string;
|
|
73
|
+
path: number[];
|
|
74
|
+
contentHash?: string;
|
|
75
|
+
structuralSignature?: string;
|
|
76
|
+
nearestHeading?: string;
|
|
77
|
+
formOwnership?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RefMetadata {
|
|
81
|
+
url: string;
|
|
82
|
+
timestamp: number;
|
|
83
|
+
selectorScope?: string;
|
|
84
|
+
interactiveOnly: boolean;
|
|
85
|
+
limit: number;
|
|
86
|
+
version: number;
|
|
87
|
+
frameContext?: string;
|
|
88
|
+
mode?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface CompactSelectorState {
|
|
92
|
+
exists: boolean;
|
|
93
|
+
visible: boolean;
|
|
94
|
+
value: string;
|
|
95
|
+
checked: boolean | null;
|
|
96
|
+
text: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CompactPageState {
|
|
100
|
+
url: string;
|
|
101
|
+
title: string;
|
|
102
|
+
focus: string;
|
|
103
|
+
headings: string[];
|
|
104
|
+
bodyText: string;
|
|
105
|
+
counts: {
|
|
106
|
+
landmarks: number;
|
|
107
|
+
buttons: number;
|
|
108
|
+
links: number;
|
|
109
|
+
inputs: number;
|
|
110
|
+
};
|
|
111
|
+
dialog: {
|
|
112
|
+
count: number;
|
|
113
|
+
title: string;
|
|
114
|
+
};
|
|
115
|
+
selectorStates: Record<string, CompactSelectorState>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface TraceSessionState {
|
|
119
|
+
startedAt: number;
|
|
120
|
+
name: string;
|
|
121
|
+
title?: string;
|
|
122
|
+
path?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface HarState {
|
|
126
|
+
enabled: boolean;
|
|
127
|
+
configuredAtContextCreation: boolean;
|
|
128
|
+
path: string | null;
|
|
129
|
+
exportCount: number;
|
|
130
|
+
lastExportedPath: string | null;
|
|
131
|
+
lastExportedAt: number | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ClickTargetStateSnapshot {
|
|
135
|
+
exists: boolean;
|
|
136
|
+
ariaExpanded: string | null;
|
|
137
|
+
ariaPressed: string | null;
|
|
138
|
+
ariaSelected: string | null;
|
|
139
|
+
open: boolean | null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface VerificationCheck {
|
|
143
|
+
name: string;
|
|
144
|
+
passed: boolean;
|
|
145
|
+
value?: unknown;
|
|
146
|
+
expected?: unknown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface VerificationResult {
|
|
150
|
+
verified: boolean;
|
|
151
|
+
checks: VerificationCheck[];
|
|
152
|
+
verificationSummary: string;
|
|
153
|
+
retryHint?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface AdaptiveSettleOptions {
|
|
157
|
+
timeoutMs?: number;
|
|
158
|
+
pollMs?: number;
|
|
159
|
+
quietWindowMs?: number;
|
|
160
|
+
checkFocusStability?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface AdaptiveSettleDetails {
|
|
164
|
+
settleMode: "adaptive";
|
|
165
|
+
settleMs: number;
|
|
166
|
+
settleReason: "dom_quiet" | "url_changed_then_quiet" | "timeout_fallback" | "zero_mutation_shortcut";
|
|
167
|
+
settlePolls: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ParsedRefSpec {
|
|
171
|
+
key: string;
|
|
172
|
+
version: number | null;
|
|
173
|
+
display: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface BrowserAssertionCheckInput {
|
|
177
|
+
kind: string;
|
|
178
|
+
selector?: string;
|
|
179
|
+
text?: string;
|
|
180
|
+
value?: string;
|
|
181
|
+
checked?: boolean;
|
|
182
|
+
sinceActionId?: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Mutable state variables — accessed only via get/set functions
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
// 1. browser
|
|
190
|
+
let _browser: Browser | null = null;
|
|
191
|
+
export function getBrowser(): Browser | null { return _browser; }
|
|
192
|
+
export function setBrowser(b: Browser | null): void { _browser = b; }
|
|
193
|
+
|
|
194
|
+
// 2. context
|
|
195
|
+
let _context: BrowserContext | null = null;
|
|
196
|
+
export function getContext(): BrowserContext | null { return _context; }
|
|
197
|
+
export function setContext(c: BrowserContext | null): void { _context = c; }
|
|
198
|
+
|
|
199
|
+
// 3. pageRegistry (object with internal state — export the instance directly + getter)
|
|
200
|
+
export const pageRegistry = createPageRegistry();
|
|
201
|
+
export function getPageRegistry() { return pageRegistry; }
|
|
202
|
+
|
|
203
|
+
// 4. activeFrame
|
|
204
|
+
let _activeFrame: Frame | null = null;
|
|
205
|
+
export function getActiveFrame(): Frame | null { return _activeFrame; }
|
|
206
|
+
export function setActiveFrame(f: Frame | null): void { _activeFrame = f; }
|
|
207
|
+
|
|
208
|
+
// 5. logPusher (bounded log push function — stateless utility, export directly)
|
|
209
|
+
export const logPusher = createBoundedLogPusher(1000);
|
|
210
|
+
|
|
211
|
+
// 6. consoleLogs
|
|
212
|
+
let _consoleLogs: ConsoleEntry[] = [];
|
|
213
|
+
export function getConsoleLogs(): ConsoleEntry[] { return _consoleLogs; }
|
|
214
|
+
export function setConsoleLogs(logs: ConsoleEntry[]): void { _consoleLogs = logs; }
|
|
215
|
+
|
|
216
|
+
// 7. networkLogs
|
|
217
|
+
let _networkLogs: NetworkEntry[] = [];
|
|
218
|
+
export function getNetworkLogs(): NetworkEntry[] { return _networkLogs; }
|
|
219
|
+
export function setNetworkLogs(logs: NetworkEntry[]): void { _networkLogs = logs; }
|
|
220
|
+
|
|
221
|
+
// 8. dialogLogs
|
|
222
|
+
let _dialogLogs: DialogEntry[] = [];
|
|
223
|
+
export function getDialogLogs(): DialogEntry[] { return _dialogLogs; }
|
|
224
|
+
export function setDialogLogs(logs: DialogEntry[]): void { _dialogLogs = logs; }
|
|
225
|
+
|
|
226
|
+
// 9. pendingCriticalRequestsByPage (WeakMap — can't be reassigned, just cleared by replacing)
|
|
227
|
+
let _pendingCriticalRequestsByPage = new WeakMap<Page, number>();
|
|
228
|
+
export function getPendingCriticalRequestsByPage(): WeakMap<Page, number> { return _pendingCriticalRequestsByPage; }
|
|
229
|
+
export function resetPendingCriticalRequestsByPage(): void { _pendingCriticalRequestsByPage = new WeakMap(); }
|
|
230
|
+
|
|
231
|
+
// 10. currentRefMap
|
|
232
|
+
let _currentRefMap: Record<string, RefNode> = {};
|
|
233
|
+
export function getCurrentRefMap(): Record<string, RefNode> { return _currentRefMap; }
|
|
234
|
+
export function setCurrentRefMap(m: Record<string, RefNode>): void { _currentRefMap = m; }
|
|
235
|
+
|
|
236
|
+
// 11. refVersion
|
|
237
|
+
let _refVersion = 0;
|
|
238
|
+
export function getRefVersion(): number { return _refVersion; }
|
|
239
|
+
export function setRefVersion(v: number): void { _refVersion = v; }
|
|
240
|
+
|
|
241
|
+
// 12. refMetadata
|
|
242
|
+
let _refMetadata: RefMetadata | null = null;
|
|
243
|
+
export function getRefMetadata(): RefMetadata | null { return _refMetadata; }
|
|
244
|
+
export function setRefMetadata(m: RefMetadata | null): void { _refMetadata = m; }
|
|
245
|
+
|
|
246
|
+
// 13. actionTimeline (object with internal state)
|
|
247
|
+
export const actionTimeline = createActionTimeline(60);
|
|
248
|
+
export function getActionTimeline() { return actionTimeline; }
|
|
249
|
+
|
|
250
|
+
// 14. lastActionBeforeState
|
|
251
|
+
let _lastActionBeforeState: CompactPageState | null = null;
|
|
252
|
+
export function getLastActionBeforeState(): CompactPageState | null { return _lastActionBeforeState; }
|
|
253
|
+
export function setLastActionBeforeState(s: CompactPageState | null): void { _lastActionBeforeState = s; }
|
|
254
|
+
|
|
255
|
+
// 15. lastActionAfterState
|
|
256
|
+
let _lastActionAfterState: CompactPageState | null = null;
|
|
257
|
+
export function getLastActionAfterState(): CompactPageState | null { return _lastActionAfterState; }
|
|
258
|
+
export function setLastActionAfterState(s: CompactPageState | null): void { _lastActionAfterState = s; }
|
|
259
|
+
|
|
260
|
+
// 16. sessionStartedAt
|
|
261
|
+
let _sessionStartedAt: number | null = null;
|
|
262
|
+
export function getSessionStartedAt(): number | null { return _sessionStartedAt; }
|
|
263
|
+
export function setSessionStartedAt(t: number | null): void { _sessionStartedAt = t; }
|
|
264
|
+
|
|
265
|
+
// 17. sessionArtifactDir
|
|
266
|
+
let _sessionArtifactDir: string | null = null;
|
|
267
|
+
export function getSessionArtifactDir(): string | null { return _sessionArtifactDir; }
|
|
268
|
+
export function setSessionArtifactDir(d: string | null): void { _sessionArtifactDir = d; }
|
|
269
|
+
|
|
270
|
+
// 18a. activeTraceSession
|
|
271
|
+
let _activeTraceSession: TraceSessionState | null = null;
|
|
272
|
+
export function getActiveTraceSession(): TraceSessionState | null { return _activeTraceSession; }
|
|
273
|
+
export function setActiveTraceSession(t: TraceSessionState | null): void { _activeTraceSession = t; }
|
|
274
|
+
|
|
275
|
+
// 18b. harState
|
|
276
|
+
const DEFAULT_HAR_STATE: HarState = {
|
|
277
|
+
enabled: false,
|
|
278
|
+
configuredAtContextCreation: false,
|
|
279
|
+
path: null,
|
|
280
|
+
exportCount: 0,
|
|
281
|
+
lastExportedPath: null,
|
|
282
|
+
lastExportedAt: null,
|
|
283
|
+
};
|
|
284
|
+
let _harState: HarState = { ...DEFAULT_HAR_STATE };
|
|
285
|
+
export function getHarState(): HarState { return _harState; }
|
|
286
|
+
export function setHarState(h: HarState): void { _harState = h; }
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// resetAllState — mirrors closeBrowser()'s reset logic
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
export function resetAllState(): void {
|
|
293
|
+
_browser = null;
|
|
294
|
+
_context = null;
|
|
295
|
+
pageRegistry.pages = [];
|
|
296
|
+
pageRegistry.activePageId = null;
|
|
297
|
+
pageRegistry.nextId = 1;
|
|
298
|
+
_activeFrame = null;
|
|
299
|
+
_consoleLogs = [];
|
|
300
|
+
_networkLogs = [];
|
|
301
|
+
_dialogLogs = [];
|
|
302
|
+
_pendingCriticalRequestsByPage = new WeakMap();
|
|
303
|
+
_currentRefMap = {};
|
|
304
|
+
_refVersion = 0;
|
|
305
|
+
_refMetadata = null;
|
|
306
|
+
_lastActionBeforeState = null;
|
|
307
|
+
_lastActionAfterState = null;
|
|
308
|
+
actionTimeline.entries = [];
|
|
309
|
+
actionTimeline.nextId = 1;
|
|
310
|
+
_sessionStartedAt = null;
|
|
311
|
+
_sessionArtifactDir = null;
|
|
312
|
+
_activeTraceSession = null;
|
|
313
|
+
_harState = { ...DEFAULT_HAR_STATE };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// ToolDeps — interface that tool registration functions consume
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Bundles the infrastructure functions that tool registration files need.
|
|
322
|
+
* Built once in the index.ts orchestrator and passed to each register* function.
|
|
323
|
+
*/
|
|
324
|
+
export interface ToolDeps {
|
|
325
|
+
// Lifecycle
|
|
326
|
+
ensureBrowser: () => Promise<{ browser: Browser; context: BrowserContext; page: Page }>;
|
|
327
|
+
closeBrowser: () => Promise<void>;
|
|
328
|
+
getActivePage: () => Page;
|
|
329
|
+
getActiveTarget: () => Page | Frame;
|
|
330
|
+
getActivePageOrNull: () => Page | null;
|
|
331
|
+
|
|
332
|
+
// Page event wiring
|
|
333
|
+
attachPageListeners: (p: Page, pageId: number) => void;
|
|
334
|
+
|
|
335
|
+
// Capture & summary
|
|
336
|
+
captureCompactPageState: (
|
|
337
|
+
p: Page,
|
|
338
|
+
options?: { selectors?: string[]; includeBodyText?: boolean; target?: Page | Frame }
|
|
339
|
+
) => Promise<CompactPageState>;
|
|
340
|
+
postActionSummary: (p: Page, target?: Page | Frame) => Promise<string>;
|
|
341
|
+
formatCompactStateSummary: (state: CompactPageState) => string;
|
|
342
|
+
constrainScreenshot: (page: Page, buffer: Buffer, mimeType: string, quality: number) => Promise<Buffer>;
|
|
343
|
+
captureErrorScreenshot: (p: Page | null) => Promise<{ data: string; mimeType: string } | null>;
|
|
344
|
+
getRecentErrors: (pageUrl: string) => string;
|
|
345
|
+
|
|
346
|
+
// Settle
|
|
347
|
+
settleAfterActionAdaptive: (p: Page, opts?: AdaptiveSettleOptions) => Promise<AdaptiveSettleDetails>;
|
|
348
|
+
ensureMutationCounter: (p: Page) => Promise<void>;
|
|
349
|
+
|
|
350
|
+
// Refs
|
|
351
|
+
buildRefSnapshot: (
|
|
352
|
+
target: Page | Frame,
|
|
353
|
+
options: { selector?: string; interactiveOnly: boolean; limit: number; mode?: string }
|
|
354
|
+
) => Promise<Array<Omit<RefNode, "ref">>>;
|
|
355
|
+
resolveRefTarget: (
|
|
356
|
+
target: Page | Frame,
|
|
357
|
+
node: RefNode
|
|
358
|
+
) => Promise<{ ok: true; selector: string } | { ok: false; reason: string }>;
|
|
359
|
+
parseRef: (input: string) => ParsedRefSpec;
|
|
360
|
+
formatVersionedRef: (version: number, key: string) => string;
|
|
361
|
+
staleRefGuidance: (refDisplay: string, reason: string) => string;
|
|
362
|
+
|
|
363
|
+
// Action tracking
|
|
364
|
+
beginTrackedAction: (tool: string, params: unknown, beforeUrl: string) => ReturnType<typeof import("./core.js").beginAction>;
|
|
365
|
+
finishTrackedAction: (
|
|
366
|
+
actionId: number,
|
|
367
|
+
updates: {
|
|
368
|
+
status: "success" | "error";
|
|
369
|
+
afterUrl?: string;
|
|
370
|
+
verificationSummary?: string;
|
|
371
|
+
warningSummary?: string;
|
|
372
|
+
diffSummary?: string;
|
|
373
|
+
changed?: boolean;
|
|
374
|
+
error?: string;
|
|
375
|
+
beforeState?: CompactPageState;
|
|
376
|
+
afterState?: CompactPageState;
|
|
377
|
+
}
|
|
378
|
+
) => ReturnType<typeof import("./core.js").finishAction>;
|
|
379
|
+
|
|
380
|
+
// Utilities (forwarded from utils.ts)
|
|
381
|
+
truncateText: (text: string) => string;
|
|
382
|
+
verificationFromChecks: (checks: VerificationCheck[], retryHint?: string) => VerificationResult;
|
|
383
|
+
verificationLine: (verification: VerificationResult) => string;
|
|
384
|
+
collectAssertionState: (
|
|
385
|
+
p: Page,
|
|
386
|
+
checks: BrowserAssertionCheckInput[],
|
|
387
|
+
target?: Page | Frame
|
|
388
|
+
) => Promise<Record<string, unknown>>;
|
|
389
|
+
formatAssertionText: (result: ReturnType<typeof import("./core.js").evaluateAssertionChecks>) => string;
|
|
390
|
+
formatDiffText: (diff: ReturnType<typeof import("./core.js").diffCompactStates>) => string;
|
|
391
|
+
getUrlHash: (url: string) => string;
|
|
392
|
+
captureClickTargetState: (target: Page | Frame, selector: string) => Promise<ClickTargetStateSnapshot>;
|
|
393
|
+
readInputLikeValue: (target: Page | Frame, selector?: string) => Promise<string | null>;
|
|
394
|
+
firstErrorLine: (err: unknown) => string;
|
|
395
|
+
captureAccessibilityMarkdown: (selector?: string) => Promise<{ snapshot: string; scope: string; source: string }>;
|
|
396
|
+
resolveAccessibilityScope: (selector?: string) => Promise<{ selector?: string; scope: string; source: string }>;
|
|
397
|
+
getLivePagesSnapshot: () => Promise<ReturnType<typeof import("./core.js").registryListPages>>;
|
|
398
|
+
getSinceTimestamp: (sinceActionId?: number) => number;
|
|
399
|
+
getConsoleEntriesSince: (sinceActionId?: number) => ConsoleEntry[];
|
|
400
|
+
getNetworkEntriesSince: (sinceActionId?: number) => NetworkEntry[];
|
|
401
|
+
writeArtifactFile: (filePath: string, content: string | Uint8Array) => Promise<{ path: string; bytes: number }>;
|
|
402
|
+
copyArtifactFile: (sourcePath: string, destinationPath: string) => Promise<{ path: string; bytes: number }>;
|
|
403
|
+
ensureSessionArtifactDir: () => Promise<string>;
|
|
404
|
+
buildSessionArtifactPath: (filename: string) => string;
|
|
405
|
+
getSessionArtifactMetadata: () => Record<string, unknown>;
|
|
406
|
+
sanitizeArtifactName: (value: string, fallback: string) => string;
|
|
407
|
+
formatArtifactTimestamp: (timestamp: number) => string;
|
|
408
|
+
}
|