Add controls inventory and truncate long data: URIs in dumps.
CI / automation-js (vitest + real Chromium) (pull_request) Successful in 1m13s
CI / Lint + tests (pull_request) Successful in 10m24s

Agents hitting Outline-style SPAs need visible vs hidden control lists,
and inlined base64 images were blowing up AI prompts and network copy.
This commit is contained in:
2026-08-09 19:13:54 -04:00
parent f45833ff07
commit ee520fc391
12 changed files with 297 additions and 12 deletions
+1
View File
@@ -34,6 +34,7 @@ const session = new ExtractorSession(page); // attach BEFORE navigating
await page.goto('https://example.com', { waitUntil: 'networkidle' });
console.log(await session.buildAiPrompt()); // or session.extractMarkdown('#main')
console.log(await session.inventoryControls('#main')); // visible/hidden buttons & aria-labels
console.log(session.getStore()); // { console, errors, network }
```
+1
View File
@@ -5,5 +5,6 @@ export {
type ErrorEntry,
type NetworkEntry,
type ExtractedMarkdown,
type InterestingControl,
} from './session.js';
export { readSharedJs } from './loadJs.js';
+41 -2
View File
@@ -118,18 +118,44 @@ return extractMarkdown(el);
`;
}
function buildInventoryScript(selector: string | null | undefined): string {
const domJs = readSharedJs('dom.js');
const selJson = JSON.stringify(selector || '');
return `
(() => {
${domJs}
let el = null;
const sel = ${selJson};
if (sel) { try { el = document.querySelector(sel); } catch (_) { el = null; } }
if (!el) el = document.body;
return inventoryInterestingControls(el);
})()
`;
}
export interface InterestingControl {
tag: string;
role: string;
ariaLabel: string;
text: string;
visible: boolean;
selector: string;
}
function buildPromptScript(
meta: Pick<ExtractedMarkdown, 'url' | 'title' | 'ts' | 'selector'>,
markdown: string,
store: CaptureStore,
maxChars: number | undefined,
controls?: InterestingControl[],
): string {
const promptJs = readSharedJs('prompt.js');
const maxCharsJs = maxChars === undefined ? 'undefined' : JSON.stringify(maxChars);
const controlsJs = JSON.stringify(controls ?? []);
return `
(() => {
${promptJs}
return buildAIPrompt(${JSON.stringify(meta)}, ${JSON.stringify(markdown)}, ${JSON.stringify(store)}, ${maxCharsJs});
return buildAIPrompt(${JSON.stringify(meta)}, ${JSON.stringify(markdown)}, ${JSON.stringify(store)}, ${maxCharsJs}, ${controlsJs});
})()
`;
}
@@ -191,15 +217,28 @@ export class ExtractorSession {
};
}
/** Inventory buttons / menuitems / aria-labels under selector (default body). */
async inventoryControls(selector?: string | null): Promise<InterestingControl[]> {
const list = (await this.page.evaluate(buildInventoryScript(selector))) as InterestingControl[];
return Array.isArray(list) ? list : [];
}
async buildAiPrompt(selector?: string | null, maxChars?: number): Promise<string> {
const extracted = await this.extractMarkdown(selector);
const controls = await this.inventoryControls(selector);
const meta = {
url: extracted.url,
title: extracted.title,
ts: extracted.ts,
selector: extracted.selector,
};
const script = buildPromptScript(meta, extracted.markdown, this.getStore(), maxChars);
const script = buildPromptScript(
meta,
extracted.markdown,
this.getStore(),
maxChars,
controls,
);
return (await this.page.evaluate(script)) as string;
}
+20
View File
@@ -63,6 +63,9 @@ describe('ExtractorSession', () => {
expect(full.markdown).toContain('**world**');
expect(full.markdown).toContain('[docs](/docs)');
expect(full.markdown).toContain('- Alpha');
expect(full.markdown).toContain('[tiny-jpeg](data:image/jpeg;base64,…[');
expect(full.markdown).toContain('bytes truncated])');
expect(full.markdown).not.toMatch(/4AAQSkZJRgABAQAAAQABAAD/);
const scoped = await session.extractMarkdown('#main-content');
expect(scoped.selector).toBe('#main-content');
@@ -95,11 +98,28 @@ describe('ExtractorSession', () => {
expect(prompt.startsWith('# Page Context')).toBe(true);
expect(prompt).toContain('## Page Content');
expect(prompt).toContain('Hello **world**');
expect(prompt).toContain('## Interesting Controls');
expect(prompt).toContain('[visible]');
expect(prompt).toContain('Document options');
expect(prompt).toContain('### Hidden');
expect(/JavaScript Errors|Console Errors/.test(prompt)).toBe(true);
expect(prompt).toContain('_Extracted by Context Extractor_');
});
});
it('inventories visible and hidden controls', async () => {
await withPage(async (page) => {
const session = new ExtractorSession(page);
await page.goto(FIXTURE_URL, { waitUntil: 'domcontentloaded' });
const controls = await session.inventoryControls('#main-content');
const docs = controls.filter((c) => c.ariaLabel === 'Document options');
expect(docs.length).toBe(2);
expect(docs.some((c) => c.visible)).toBe(true);
expect(docs.some((c) => !c.visible)).toBe(true);
expect(controls.some((c) => c.ariaLabel === 'Upload file' && c.visible)).toBe(true);
});
});
it('clears the capture store, selectively and fully', async () => {
await withPage(async (page) => {
const session = new ExtractorSession(page);