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.
Files changed (53) hide show
  1. package/README.md +12 -5
  2. package/dist/loader.js +0 -0
  3. package/dist/modes/interactive/theme/dark.json +85 -0
  4. package/dist/modes/interactive/theme/light.json +84 -0
  5. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  6. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  7. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  8. package/dist/modes/interactive/theme/theme.js +949 -0
  9. package/dist/modes/interactive/theme/theme.js.map +1 -0
  10. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  11. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
  12. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  13. package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
  14. package/node_modules/cliui/CHANGELOG.md +121 -0
  15. package/node_modules/color-convert/CHANGELOG.md +54 -0
  16. package/node_modules/esprima/ChangeLog +235 -0
  17. package/node_modules/mz/HISTORY.md +66 -0
  18. package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
  19. package/node_modules/source-map/CHANGELOG.md +301 -0
  20. package/node_modules/thenify/History.md +11 -0
  21. package/node_modules/thenify-all/History.md +11 -0
  22. package/node_modules/y18n/CHANGELOG.md +100 -0
  23. package/node_modules/yargs/CHANGELOG.md +88 -0
  24. package/node_modules/yargs-parser/CHANGELOG.md +263 -0
  25. package/package.json +5 -2
  26. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  27. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
  28. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  29. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
  30. package/src/resources/extensions/browser-tools/capture.ts +165 -0
  31. package/src/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
  32. package/src/resources/extensions/browser-tools/index.ts +47 -4985
  33. package/src/resources/extensions/browser-tools/lifecycle.ts +265 -0
  34. package/src/resources/extensions/browser-tools/package.json +5 -1
  35. package/src/resources/extensions/browser-tools/refs.ts +264 -0
  36. package/src/resources/extensions/browser-tools/settle.ts +197 -0
  37. package/src/resources/extensions/browser-tools/state.ts +408 -0
  38. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
  39. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
  40. package/src/resources/extensions/browser-tools/tools/assertions.ts +342 -0
  41. package/src/resources/extensions/browser-tools/tools/forms.ts +801 -0
  42. package/src/resources/extensions/browser-tools/tools/inspection.ts +492 -0
  43. package/src/resources/extensions/browser-tools/tools/intent.ts +614 -0
  44. package/src/resources/extensions/browser-tools/tools/interaction.ts +865 -0
  45. package/src/resources/extensions/browser-tools/tools/navigation.ts +232 -0
  46. package/src/resources/extensions/browser-tools/tools/pages.ts +303 -0
  47. package/src/resources/extensions/browser-tools/tools/refs.ts +541 -0
  48. package/src/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
  49. package/src/resources/extensions/browser-tools/tools/session.ts +400 -0
  50. package/src/resources/extensions/browser-tools/tools/wait.ts +247 -0
  51. package/src/resources/extensions/browser-tools/utils.ts +660 -0
  52. package/src/resources/extensions/gsd/git-service.ts +3 -0
  53. 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
+ }