fix: resolve TypeScript type errors in browser-tools extension (#204) (#212)

- Create core.d.ts with type declarations for the untyped core.js module
- Create tsconfig.extensions.json for type-checking browser-tools in isolation
- Fix collectAssertionState adapter to match ToolDeps signature by wrapping with captureCompactPageState binding
- Fix captureAccessibilityMarkdown adapter to match ToolDeps signature by binding getActiveTarget()
- Add explicit Element | null type annotation to refs.ts parent variable (TS7022 circular inference)
- Fix session.ts timeline.count to timeline.retained (nonexistent property on formatTimelineEntries result)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-13 11:35:47 -06:00 committed by GitHub
parent 940b6a38dc
commit a5eebd8d98
5 changed files with 212 additions and 5 deletions

View file

@ -0,0 +1,205 @@
/**
* Type declarations for core.js runtime-neutral helper logic for browser-tools.
*/
export interface ActionTimeline {
limit: number;
nextId: number;
entries: ActionEntry[];
}
export interface ActionEntry {
id: number;
tool: string;
paramsSummary: string;
startedAt: number;
finishedAt: number | null;
status: string;
beforeUrl: string;
afterUrl: string;
verificationSummary?: string;
warningSummary?: string;
diffSummary?: string;
changed?: boolean;
error?: string;
}
export interface ActionPartial {
tool: string;
paramsSummary?: string;
startedAt?: number;
beforeUrl?: string;
afterUrl?: string;
verificationSummary?: string;
warningSummary?: string;
diffSummary?: string;
changed?: boolean;
error?: string;
}
export interface ActionUpdates {
finishedAt?: number;
status?: string;
afterUrl?: string;
verificationSummary?: string;
warningSummary?: string;
diffSummary?: string;
changed?: boolean;
error?: string;
}
export interface DiffResult {
changed: boolean;
changes: Array<{ type: string; before: unknown; after: unknown }>;
summary: string;
}
export interface Threshold {
op: string;
n: number;
}
export interface PageRegistry {
pages: PageEntry[];
activePageId: number | null;
nextId: number;
}
export interface PageEntry {
id: number;
page: any;
title: string;
url: string;
opener: number | null;
}
export interface PageListEntry {
id: number;
title: string;
url: string;
opener: number | null;
isActive: boolean;
}
export interface SnapshotModeConfig {
tags: string[];
roles: string[];
selectors: string[];
ariaAttributes: string[];
useInteractiveFilter: boolean;
visibleOnly?: boolean;
containerExpand?: boolean;
}
export interface AssertionCheckResult {
name: string;
passed: boolean;
actual: unknown;
expected: unknown;
selector?: string;
text?: string;
}
export interface AssertionEvaluation {
verified: boolean;
checks: AssertionCheckResult[];
summary: string;
agentHint: string;
}
export interface WaitValidationError {
error: string;
}
export interface BatchStepResult {
ok: boolean;
stopReason: string | null;
failedStepIndex: number | null;
stepResults: unknown[];
summary: string;
}
export interface FormattedTimeline {
entries: Array<{
id: number | null;
tool: string;
status: string;
durationMs: number | null;
beforeUrl: string;
afterUrl: string;
line: string;
}>;
retained: number;
totalRecorded: number;
bounded: boolean;
summary: string;
}
export interface FailureHypothesis {
hasFailures: boolean;
categories: string[];
summary: string;
signals: Array<{ category: string; source: string; detail: string }>;
}
export interface SessionSummary {
counts: {
pages: number;
actions: { total: number; retained: number; success: number; error: number; running: number };
waits: { total: number; success: number; error: number; running: number };
assertions: { total: number; passed: number; failed: number; running: number };
consoleErrors: number;
failedRequests: number;
dialogs: number;
};
activePage: { id: number | null; title: string; url: string } | null;
caveats: string[];
failureHypothesis: FailureHypothesis;
summary: string;
}
export function createActionTimeline(limit?: number): ActionTimeline;
export function beginAction(timeline: ActionTimeline, partial: ActionPartial): ActionEntry;
export function finishAction(timeline: ActionTimeline, actionId: number, updates?: ActionUpdates): ActionEntry | null;
export function findAction(timeline: ActionTimeline, actionId: number): ActionEntry | null;
export function toActionParamsSummary(params: unknown): string;
export function diffCompactStates(before: unknown, after: unknown): DiffResult;
export function includesNeedle(haystack: string, needle: string): boolean;
export function parseThreshold(value: string | null | undefined): Threshold | null;
export function meetsThreshold(count: number, threshold: Threshold): boolean;
export function getEntriesSince(
entries: Array<{ timestamp?: number }>,
sinceActionId: number | undefined,
timeline: ActionTimeline,
): unknown[];
export function evaluateAssertionChecks(args: { checks: unknown[]; state: unknown }): AssertionEvaluation;
export function validateWaitParams(params: { condition: string; value?: string; threshold?: string }): WaitValidationError | null;
export function createRegionStableScript(selector: string): string;
export function createPageRegistry(): PageRegistry;
export function registryAddPage(
registry: PageRegistry,
info: { page: unknown; title?: string; url?: string; opener?: number | null },
): PageEntry;
export function registryRemovePage(registry: PageRegistry, pageId: number): { removed: PageEntry; newActiveId: number | null };
export function registrySetActive(registry: PageRegistry, pageId: number): void;
export function registryGetActive(registry: PageRegistry): PageEntry;
export function registryGetPage(registry: PageRegistry, pageId: number): PageEntry | null;
export function registryListPages(registry: PageRegistry): PageListEntry[];
export function createBoundedLogPusher(maxSize: number): (array: unknown[], entry: unknown) => void;
export function runBatchSteps(args: {
steps: unknown[];
executeStep: (step: unknown, index: number) => Promise<{ ok: boolean; [key: string]: unknown }>;
stopOnFailure?: boolean;
}): Promise<BatchStepResult>;
export declare const SNAPSHOT_MODES: Record<string, SnapshotModeConfig>;
export function getSnapshotModeConfig(mode: string): SnapshotModeConfig | null;
export function computeContentHash(text: string): string;
export function computeStructuralSignature(tag: string, role: string, childTags: string[]): string;
export function matchFingerprint(
stored: { contentHash?: string; structuralSignature?: string },
candidate: { contentHash?: string; structuralSignature?: string },
): boolean;
export function formatTimelineEntries(entries?: unknown[], options?: Record<string, unknown>): FormattedTimeline;
export function buildFailureHypothesis(session?: Record<string, unknown>): FailureHypothesis;
export function summarizeBrowserSession(session?: Record<string, unknown>): SessionSummary;

View file

@ -28,11 +28,11 @@ export default function (pi: ExtensionAPI) {
parseRef: u.parseRef, formatVersionedRef: u.formatVersionedRef, staleRefGuidance: u.staleRefGuidance,
beginTrackedAction: u.beginTrackedAction, finishTrackedAction: u.finishTrackedAction,
truncateText: u.truncateText, verificationFromChecks: u.verificationFromChecks,
verificationLine: u.verificationLine, collectAssertionState: u.collectAssertionState,
verificationLine: u.verificationLine, collectAssertionState: (p, checks, target?) => u.collectAssertionState(p, checks, captureCompactPageState, target),
formatAssertionText: u.formatAssertionText, formatDiffText: u.formatDiffText,
getUrlHash: u.getUrlHash,
captureClickTargetState: u.captureClickTargetState, readInputLikeValue: u.readInputLikeValue,
firstErrorLine: u.firstErrorLine, captureAccessibilityMarkdown: u.captureAccessibilityMarkdown,
firstErrorLine: u.firstErrorLine, captureAccessibilityMarkdown: (selector?) => u.captureAccessibilityMarkdown(getActiveTarget(), selector),
resolveAccessibilityScope: u.resolveAccessibilityScope,
getLivePagesSnapshot: u.createGetLivePagesSnapshot(ensureBrowser),
getSinceTimestamp: u.getSinceTimestamp, getConsoleEntriesSince: u.getConsoleEntriesSince,

View file

@ -124,7 +124,7 @@ export async function buildRefSnapshot(
sib = sib.previousElementSibling;
}
// Check if the parent itself is a heading (unlikely but possible)
const parent = current.parentElement;
const parent: Element | null = current.parentElement;
if (parent && (headingTags.has(parent.tagName) || parent.getAttribute("role") === "heading")) {
return (parent.textContent || "").trim().replace(/\s+/g, " ").slice(0, 80);
}

View file

@ -379,7 +379,7 @@ export function registerSessionTools(pi: ExtensionAPI, deps: ToolDeps): void {
console: consoleLogs.length,
network: networkLogs.length,
dialog: dialogLogs.length,
actions: timeline.count,
actions: timeline.retained,
pages: pages.length,
},
elapsedMs: Date.now() - startedAt,

View file

@ -3,8 +3,10 @@
"compilerOptions": {
"noEmit": true,
"allowImportingTsExtensions": true,
"allowJs": true,
"checkJs": false,
"rootDir": "."
},
"include": ["src/resources/extensions/gsd"],
"include": ["src/resources/extensions"],
"exclude": []
}