@_deep4wee/agent-lens 1.0.1 ā 1.2.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 +141 -31
- package/dist/cli.d.mts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +1169 -957
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1268 -974
- package/dist/cli.mjs.map +1 -1
- package/dist/dsl-BIjVN1M0.d.mts +204 -0
- package/dist/dsl-BIjVN1M0.d.ts +204 -0
- package/dist/index.d.mts +131 -155
- package/dist/index.d.ts +131 -155
- package/dist/index.js +1506 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1479 -3
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/a11y-tree/index.d.mts +11 -0
- package/dist/plugins/a11y-tree/index.d.ts +11 -0
- package/dist/plugins/a11y-tree/index.js +190 -0
- package/dist/plugins/a11y-tree/index.js.map +1 -0
- package/dist/plugins/a11y-tree/index.mjs +155 -0
- package/dist/plugins/a11y-tree/index.mjs.map +1 -0
- package/dist/plugins/desktop-webview2/index.d.mts +14 -0
- package/dist/plugins/desktop-webview2/index.d.ts +14 -0
- package/dist/plugins/desktop-webview2/index.js +258 -0
- package/dist/plugins/desktop-webview2/index.js.map +1 -0
- package/dist/plugins/desktop-webview2/index.mjs +221 -0
- package/dist/plugins/desktop-webview2/index.mjs.map +1 -0
- package/dist/plugins/live-controller/index.d.mts +35 -0
- package/dist/plugins/live-controller/index.d.ts +35 -0
- package/dist/plugins/live-controller/index.js +303 -0
- package/dist/plugins/live-controller/index.js.map +1 -0
- package/dist/plugins/live-controller/index.mjs +261 -0
- package/dist/plugins/live-controller/index.mjs.map +1 -0
- package/dist/plugins/mock-ipc/index.d.mts +30 -0
- package/dist/plugins/mock-ipc/index.d.ts +30 -0
- package/dist/plugins/mock-ipc/index.js +210 -0
- package/dist/plugins/mock-ipc/index.js.map +1 -0
- package/dist/plugins/mock-ipc/index.mjs +181 -0
- package/dist/plugins/mock-ipc/index.mjs.map +1 -0
- package/dist/plugins/visual-diff/index.d.mts +30 -0
- package/dist/plugins/visual-diff/index.d.ts +30 -0
- package/dist/plugins/visual-diff/index.js +163 -0
- package/dist/plugins/visual-diff/index.js.map +1 -0
- package/dist/plugins/visual-diff/index.mjs +127 -0
- package/dist/plugins/visual-diff/index.mjs.map +1 -0
- package/docs/plugins.md +415 -0
- package/package.json +40 -2
- package/skills/agent-lens/SKILL.md +142 -37
- package/skills/agent-lens/examples/06-state-testing-with-mock-ipc.md +13 -12
- package/skills/agent-lens/examples/07-live-controller-interactive-loop.md +108 -0
- package/skills/agent-lens/examples/08-accessibility-semantic-inspection.md +80 -0
- package/skills/agent-lens/examples/09-visual-regression-and-pixel-diffing.md +66 -0
- package/skills/agent-lens/examples/10-authoring-custom-agent-plugins.md +85 -0
- package/skills/agent-lens/references/plugin-development.md +165 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/plugins/a11y-tree/index.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// src/shared/api/plugin.ts
|
|
6
|
+
function definePlugin(plugin) {
|
|
7
|
+
return plugin;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/plugins/a11y-tree/formatter.ts
|
|
11
|
+
function formatAccessibilityTree(node, depth = 0, options) {
|
|
12
|
+
if (!node) return "";
|
|
13
|
+
const indent = " ".repeat(depth);
|
|
14
|
+
const parts = [`${indent}- role: **${node.role}**`];
|
|
15
|
+
if (node.name) {
|
|
16
|
+
parts.push(`"${node.name.replace(/"/g, '\\"')}"`);
|
|
17
|
+
}
|
|
18
|
+
const flags = [];
|
|
19
|
+
if (node.level !== void 0) flags.push(`level ${node.level}`);
|
|
20
|
+
if (node.disabled) flags.push("disabled");
|
|
21
|
+
if (node.required) flags.push("required");
|
|
22
|
+
if (node.focused) flags.push("focused");
|
|
23
|
+
if (node.checked !== void 0 && node.checked !== false) flags.push(`checked: ${String(node.checked)}`);
|
|
24
|
+
if (node.pressed !== void 0) flags.push(`pressed: ${String(node.pressed)}`);
|
|
25
|
+
if (node.selected) flags.push("selected");
|
|
26
|
+
if (node.value !== void 0 && node.value !== "") flags.push(`value: "${String(node.value)}"`);
|
|
27
|
+
if (flags.length > 0) {
|
|
28
|
+
parts.push(`[${flags.join(", ")}]`);
|
|
29
|
+
}
|
|
30
|
+
let result = parts.join(" ") + "\n";
|
|
31
|
+
if (node.children && node.children.length > 0) {
|
|
32
|
+
for (const child of node.children) {
|
|
33
|
+
if (options?.compact && !child.name && (!child.children || child.children.length === 0)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
result += formatAccessibilityTree(child, depth + 1, options);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/plugins/a11y-tree/index.ts
|
|
43
|
+
var STATE_A11Y_KEY = "latestA11yTree";
|
|
44
|
+
async function extractDomAccessibilityTree(page, rootSelector) {
|
|
45
|
+
return await page.evaluate((selector) => {
|
|
46
|
+
function getRole(el) {
|
|
47
|
+
const explicit = el.getAttribute("role");
|
|
48
|
+
if (explicit) return explicit;
|
|
49
|
+
const tag = el.tagName.toLowerCase();
|
|
50
|
+
if (tag === "button") return "button";
|
|
51
|
+
if (tag === "a" && el.hasAttribute("href")) return "link";
|
|
52
|
+
if (tag === "input") {
|
|
53
|
+
const type = el.type || "text";
|
|
54
|
+
if (["checkbox", "radio"].includes(type)) return type;
|
|
55
|
+
if (["button", "submit", "reset"].includes(type)) return "button";
|
|
56
|
+
return "textbox";
|
|
57
|
+
}
|
|
58
|
+
if (tag === "textarea") return "textbox";
|
|
59
|
+
if (tag === "select") return "combobox";
|
|
60
|
+
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) return "heading";
|
|
61
|
+
if (tag === "nav") return "navigation";
|
|
62
|
+
if (tag === "main") return "main";
|
|
63
|
+
if (tag === "header") return "banner";
|
|
64
|
+
if (tag === "footer") return "contentinfo";
|
|
65
|
+
if (tag === "ul" || tag === "ol") return "list";
|
|
66
|
+
if (tag === "li") return "listitem";
|
|
67
|
+
if (tag === "table") return "table";
|
|
68
|
+
if (tag === "img") return "img";
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
function getName(el) {
|
|
72
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
73
|
+
if (ariaLabel) return ariaLabel.trim();
|
|
74
|
+
const placeholder = el.placeholder;
|
|
75
|
+
if (placeholder) return placeholder.trim();
|
|
76
|
+
const alt = el.getAttribute("alt");
|
|
77
|
+
if (alt) return alt.trim();
|
|
78
|
+
const tag = el.tagName.toLowerCase();
|
|
79
|
+
if (["button", "a", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
|
|
80
|
+
return (el.innerText || el.textContent || "").trim().slice(0, 80);
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
function walk(el) {
|
|
85
|
+
const htmlEl = el;
|
|
86
|
+
if (!htmlEl || htmlEl.nodeType !== 1) return null;
|
|
87
|
+
const style = window.getComputedStyle(htmlEl);
|
|
88
|
+
if (style.display === "none" || style.visibility === "hidden") return null;
|
|
89
|
+
const role = getRole(htmlEl);
|
|
90
|
+
const name = getName(htmlEl);
|
|
91
|
+
const children = [];
|
|
92
|
+
for (const child of Array.from(htmlEl.children)) {
|
|
93
|
+
const childNode = walk(child);
|
|
94
|
+
if (childNode) children.push(childNode);
|
|
95
|
+
}
|
|
96
|
+
if (!role && !name && children.length === 0) return null;
|
|
97
|
+
if (!role && !name) {
|
|
98
|
+
if (children.length === 1) return children[0];
|
|
99
|
+
return { role: "group", children };
|
|
100
|
+
}
|
|
101
|
+
const node = { role: role || "generic" };
|
|
102
|
+
if (name) node.name = name;
|
|
103
|
+
if (children.length > 0) node.children = children;
|
|
104
|
+
if (htmlEl.tagName.startsWith("H") && htmlEl.tagName.length === 2) {
|
|
105
|
+
node.level = parseInt(htmlEl.tagName[1], 10);
|
|
106
|
+
}
|
|
107
|
+
if (htmlEl.disabled) node.disabled = true;
|
|
108
|
+
if (htmlEl.hasAttribute("required")) node.required = true;
|
|
109
|
+
if (document.activeElement === htmlEl) node.focused = true;
|
|
110
|
+
return node;
|
|
111
|
+
}
|
|
112
|
+
const rootEl = selector ? document.querySelector(selector) : document.body;
|
|
113
|
+
return rootEl ? walk(rootEl) : null;
|
|
114
|
+
}, rootSelector);
|
|
115
|
+
}
|
|
116
|
+
var a11yTreePlugin = definePlugin({
|
|
117
|
+
name: "a11y-tree",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
extendContext: (_ctx, page, hookContext) => {
|
|
120
|
+
return {
|
|
121
|
+
dumpAccessibilityTree: async (options) => {
|
|
122
|
+
console.log(`\u267F [a11y-tree] Capturing semantic accessibility tree...`);
|
|
123
|
+
const rootNode = await extractDomAccessibilityTree(page, options?.selector);
|
|
124
|
+
const markdown = formatAccessibilityTree(rootNode, 0, { compact: options?.compact ?? true });
|
|
125
|
+
if (hookContext.artifactsDir && options?.saveToFile !== false) {
|
|
126
|
+
const filePath = path.join(hookContext.artifactsDir, "a11y-tree.md");
|
|
127
|
+
fs.writeFileSync(filePath, markdown, "utf-8");
|
|
128
|
+
console.log(`\u267F [a11y-tree] Dump saved to: ${filePath}`);
|
|
129
|
+
}
|
|
130
|
+
hookContext.state.set(STATE_A11Y_KEY, markdown);
|
|
131
|
+
return markdown;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
onAfterRun: (reportData, hookContext) => {
|
|
136
|
+
const tree = hookContext.state.get(STATE_A11Y_KEY);
|
|
137
|
+
if (tree && reportData.customSections) {
|
|
138
|
+
const truncated = tree.length > 3e3 ? tree.slice(0, 3e3) + "\n\n*...and more (see a11y-tree.md)*" : tree;
|
|
139
|
+
reportData.customSections.push({
|
|
140
|
+
title: "\u267F Accessibility Semantic Tree (for Text AI Agents)",
|
|
141
|
+
content: `> Clean semantic hierarchy for text models (GPT-4o-mini, Haiku, Ollama).
|
|
142
|
+
|
|
143
|
+
\`\`\`markdown
|
|
144
|
+
${truncated}
|
|
145
|
+
\`\`\``
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
var a11y_tree_default = a11yTreePlugin;
|
|
151
|
+
export {
|
|
152
|
+
a11yTreePlugin,
|
|
153
|
+
a11y_tree_default as default
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/a11y-tree/index.ts","../../../src/shared/api/plugin.ts","../../../src/plugins/a11y-tree/formatter.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page } from 'playwright';\nimport { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport { formatAccessibilityTree, type AccessibilityNode } from './formatter';\n\nconst STATE_A11Y_KEY = 'latestA11yTree';\n\nexport interface DumpA11yOptions {\n compact?: boolean;\n selector?: string;\n saveToFile?: boolean;\n}\n\nasync function extractDomAccessibilityTree(page: Page, rootSelector?: string): Promise<AccessibilityNode | null> {\n return await page.evaluate((selector) => {\n function getRole(el: HTMLElement): string {\n const explicit = el.getAttribute('role');\n if (explicit) return explicit;\n const tag = el.tagName.toLowerCase();\n if (tag === 'button') return 'button';\n if (tag === 'a' && el.hasAttribute('href')) return 'link';\n if (tag === 'input') {\n const type = (el as HTMLInputElement).type || 'text';\n if (['checkbox', 'radio'].includes(type)) return type;\n if (['button', 'submit', 'reset'].includes(type)) return 'button';\n return 'textbox';\n }\n if (tag === 'textarea') return 'textbox';\n if (tag === 'select') return 'combobox';\n if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) return 'heading';\n if (tag === 'nav') return 'navigation';\n if (tag === 'main') return 'main';\n if (tag === 'header') return 'banner';\n if (tag === 'footer') return 'contentinfo';\n if (tag === 'ul' || tag === 'ol') return 'list';\n if (tag === 'li') return 'listitem';\n if (tag === 'table') return 'table';\n if (tag === 'img') return 'img';\n return '';\n }\n\n function getName(el: HTMLElement): string {\n const ariaLabel = el.getAttribute('aria-label');\n if (ariaLabel) return ariaLabel.trim();\n const placeholder = (el as HTMLInputElement).placeholder;\n if (placeholder) return placeholder.trim();\n const alt = el.getAttribute('alt');\n if (alt) return alt.trim();\n const tag = el.tagName.toLowerCase();\n if (['button', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) {\n return (el.innerText || el.textContent || '').trim().slice(0, 80);\n }\n return '';\n }\n\n function walk(el: Element): any {\n const htmlEl = el as HTMLElement;\n if (!htmlEl || htmlEl.nodeType !== 1) return null;\n\n const style = window.getComputedStyle(htmlEl);\n if (style.display === 'none' || style.visibility === 'hidden') return null;\n\n const role = getRole(htmlEl);\n const name = getName(htmlEl);\n\n const children: any[] = [];\n for (const child of Array.from(htmlEl.children)) {\n const childNode = walk(child);\n if (childNode) children.push(childNode);\n }\n\n if (!role && !name && children.length === 0) return null;\n\n if (!role && !name) {\n if (children.length === 1) return children[0];\n return { role: 'group', children };\n }\n\n const node: any = { role: role || 'generic' };\n if (name) node.name = name;\n if (children.length > 0) node.children = children;\n\n if (htmlEl.tagName.startsWith('H') && htmlEl.tagName.length === 2) {\n node.level = parseInt(htmlEl.tagName[1], 10);\n }\n if ((htmlEl as any).disabled) node.disabled = true;\n if (htmlEl.hasAttribute('required')) node.required = true;\n if (document.activeElement === htmlEl) node.focused = true;\n\n return node;\n }\n\n const rootEl = selector ? document.querySelector(selector) : document.body;\n return rootEl ? walk(rootEl) : null;\n }, rootSelector);\n}\n\nexport const a11yTreePlugin: AgentLensPlugin = definePlugin({\n name: 'a11y-tree',\n version: '1.0.0',\n\n extendContext: (_ctx, page, hookContext) => {\n return {\n dumpAccessibilityTree: async (options?: DumpA11yOptions): Promise<string> => {\n console.log(`āæ [a11y-tree] Capturing semantic accessibility tree...`);\n\n const rootNode = await extractDomAccessibilityTree(page, options?.selector);\n const markdown = formatAccessibilityTree(rootNode, 0, { compact: options?.compact ?? true });\n\n if (hookContext.artifactsDir && options?.saveToFile !== false) {\n const filePath = path.join(hookContext.artifactsDir, 'a11y-tree.md');\n fs.writeFileSync(filePath, markdown, 'utf-8');\n console.log(`āæ [a11y-tree] Dump saved to: ${filePath}`);\n }\n\n hookContext.state.set(STATE_A11Y_KEY, markdown);\n return markdown;\n }\n };\n },\n\n onAfterRun: (reportData, hookContext) => {\n const tree = hookContext.state.get(STATE_A11Y_KEY) as string | undefined;\n if (tree && reportData.customSections) {\n const truncated = tree.length > 3000 ? tree.slice(0, 3000) + '\\n\\n*...and more (see a11y-tree.md)*' : tree;\n reportData.customSections.push({\n title: 'āæ Accessibility Semantic Tree (for Text AI Agents)',\n content: `> Clean semantic hierarchy for text models (GPT-4o-mini, Haiku, Ollama).\\n\\n\\`\\`\\`markdown\\n${truncated}\\n\\`\\`\\``\n });\n }\n }\n});\n\nexport default a11yTreePlugin;\n","import type { Page, BrowserContext, Browser } from 'playwright';\nimport type { TestContext, VisualScenario } from './dsl';\nimport type { ReportData } from '../types/report';\n\nexport interface DriverLaunchResult {\n page: Page;\n context: BrowserContext;\n browser?: Browser;\n stop?: () => Promise<void>;\n}\n\nexport interface PluginHookContext {\n scenario?: VisualScenario;\n targetMode: 'desktop' | 'preview' | string;\n artifactsDir: string;\n cliOptions?: Record<string, unknown>;\n state: Map<string, unknown>;\n}\n\nexport interface AgentLensPlugin {\n /** Unique plugin identifier (e.g. 'desktop-webview2', 'live-controller', 'mock-ipc') */\n name: string;\n version?: string;\n\n /** Lifecycle hook: initial setup before drivers and scenarios start */\n setup?: (context: PluginHookContext) => Promise<void> | void;\n\n /**\n * Microkernel driver hook: allows a plugin to provide a custom browser/page session\n * (e.g., desktop WebView2 CDP connection, custom remote browser).\n */\n launchSession?: (\n options: { currentViewport: { width: number; height: number }; headed?: boolean },\n hookContext: PluginHookContext\n ) => Promise<DriverLaunchResult | undefined>;\n\n /** Lifecycle hook: called when browser context is ready */\n onContextCreated?: (context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: called when the test page is created/navigated */\n onPageCreated?: (page: Page, context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /**\n * Context extension: inject custom methods or properties directly into TestContext (ctx)\n */\n extendContext?: (\n ctx: TestContext,\n page: Page,\n hookContext: PluginHookContext\n ) => Record<string, any> | Promise<Record<string, any>>;\n\n /** Lifecycle hook: called after scenario runs and report data is calculated */\n onAfterRun?: (reportData: ReportData, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: teardown and cleanup guaranteed to run */\n teardown?: (hookContext: PluginHookContext) => Promise<void> | void;\n}\n\nexport function definePlugin(plugin: AgentLensPlugin): AgentLensPlugin {\n return plugin;\n}","export interface AccessibilityNode {\n role: string;\n name?: string;\n value?: string | number;\n description?: string;\n keyshortcuts?: string;\n roledescription?: string;\n valuetext?: string;\n disabled?: boolean;\n expanded?: boolean;\n focused?: boolean;\n modal?: boolean;\n multiline?: boolean;\n multiselectable?: boolean;\n readonly?: boolean;\n required?: boolean;\n selected?: boolean;\n checked?: boolean | 'mixed';\n pressed?: boolean | 'mixed';\n level?: number;\n valuemin?: number;\n valuemax?: number;\n autocomplete?: string;\n haspopup?: string;\n invalid?: string;\n orientation?: string;\n children?: AccessibilityNode[];\n}\n\n/**\n * Format an accessibility tree node into clean, indented Markdown for text-based LLMs.\n */\nexport function formatAccessibilityTree(\n node: AccessibilityNode | null,\n depth = 0,\n options?: { compact?: boolean }\n): string {\n if (!node) return '';\n\n const indent = ' '.repeat(depth);\n const parts: string[] = [`${indent}- role: **${node.role}**`];\n\n if (node.name) {\n parts.push(`\"${node.name.replace(/\"/g, '\\\\\"')}\"`);\n }\n\n const flags: string[] = [];\n if (node.level !== undefined) flags.push(`level ${node.level}`);\n if (node.disabled) flags.push('disabled');\n if (node.required) flags.push('required');\n if (node.focused) flags.push('focused');\n if (node.checked !== undefined && node.checked !== false) flags.push(`checked: ${String(node.checked)}`);\n if (node.pressed !== undefined) flags.push(`pressed: ${String(node.pressed)}`);\n if (node.selected) flags.push('selected');\n if (node.value !== undefined && node.value !== '') flags.push(`value: \"${String(node.value)}\"`);\n\n if (flags.length > 0) {\n parts.push(`[${flags.join(', ')}]`);\n }\n\n let result = parts.join(' ') + '\\n';\n\n if (node.children && node.children.length > 0) {\n for (const child of node.children) {\n if (options?.compact && !child.name && (!child.children || child.children.length === 0)) {\n continue;\n }\n result += formatAccessibilityTree(child, depth + 1, options);\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { A as AgentLensPlugin } from '../../dsl-BIjVN1M0.mjs';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
interface DesktopPluginOptions {
|
|
5
|
+
executablePath?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
autoLaunch?: boolean;
|
|
8
|
+
args?: string[];
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const desktopWebview2Plugin: AgentLensPlugin;
|
|
13
|
+
|
|
14
|
+
export { type DesktopPluginOptions, desktopWebview2Plugin as default, desktopWebview2Plugin };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { A as AgentLensPlugin } from '../../dsl-BIjVN1M0.js';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
interface DesktopPluginOptions {
|
|
5
|
+
executablePath?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
autoLaunch?: boolean;
|
|
8
|
+
args?: string[];
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const desktopWebview2Plugin: AgentLensPlugin;
|
|
13
|
+
|
|
14
|
+
export { type DesktopPluginOptions, desktopWebview2Plugin as default, desktopWebview2Plugin };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/plugins/desktop-webview2/index.ts
|
|
31
|
+
var desktop_webview2_exports = {};
|
|
32
|
+
__export(desktop_webview2_exports, {
|
|
33
|
+
default: () => desktop_webview2_default,
|
|
34
|
+
desktopWebview2Plugin: () => desktopWebview2Plugin
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(desktop_webview2_exports);
|
|
37
|
+
|
|
38
|
+
// src/shared/api/plugin.ts
|
|
39
|
+
function definePlugin(plugin) {
|
|
40
|
+
return plugin;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/plugins/desktop-webview2/driver.ts
|
|
44
|
+
var import_child_process = require("child_process");
|
|
45
|
+
var import_http = __toESM(require("http"));
|
|
46
|
+
var import_path = __toESM(require("path"));
|
|
47
|
+
var import_fs = __toESM(require("fs"));
|
|
48
|
+
|
|
49
|
+
// src/shared/lib/playwrightLoader.ts
|
|
50
|
+
var import_playwright = require("playwright");
|
|
51
|
+
var chromium = new Proxy(import_playwright.chromium, {
|
|
52
|
+
get(target, prop, receiver) {
|
|
53
|
+
if (prop === "launch") {
|
|
54
|
+
return async (...args) => {
|
|
55
|
+
try {
|
|
56
|
+
return await target.launch(...args);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err.message?.includes("Executable doesn't exist") || err.message?.includes("playwright install") || err.message?.includes("browser has not been downloaded")) {
|
|
59
|
+
console.error("\n\u274C [AgentLens] Playwright Chromium browser binary is missing!");
|
|
60
|
+
console.error("\u{1F449} Please install it by running: npx playwright install chromium\n");
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const val = Reflect.get(target, prop, receiver);
|
|
67
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// src/plugins/desktop-webview2/driver.ts
|
|
72
|
+
var import_tree_kill = __toESM(require("tree-kill"));
|
|
73
|
+
var DesktopDriver = class {
|
|
74
|
+
port;
|
|
75
|
+
executablePath;
|
|
76
|
+
autoLaunch;
|
|
77
|
+
args;
|
|
78
|
+
env;
|
|
79
|
+
cwd;
|
|
80
|
+
childProcess = null;
|
|
81
|
+
browser = null;
|
|
82
|
+
context = null;
|
|
83
|
+
page = null;
|
|
84
|
+
processStderr = "";
|
|
85
|
+
processExited = false;
|
|
86
|
+
exitCode = null;
|
|
87
|
+
constructor(options) {
|
|
88
|
+
this.port = options?.port || 9222;
|
|
89
|
+
this.autoLaunch = options?.autoLaunch ?? true;
|
|
90
|
+
this.executablePath = options?.executablePath;
|
|
91
|
+
this.args = options?.args || [];
|
|
92
|
+
this.env = options?.env || {};
|
|
93
|
+
this.cwd = options?.cwd;
|
|
94
|
+
}
|
|
95
|
+
async isPortAvailable() {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
const req = import_http.default.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {
|
|
98
|
+
resolve(res.statusCode === 200);
|
|
99
|
+
});
|
|
100
|
+
req.on("error", () => resolve(false));
|
|
101
|
+
req.setTimeout(800, () => {
|
|
102
|
+
req.destroy();
|
|
103
|
+
resolve(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async waitForPort(timeoutMs = 25e3) {
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
110
|
+
if (this.processExited) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`[DesktopDriver] Process terminated prematurely with exit code ${this.exitCode}.
|
|
113
|
+
Stderr: ${this.processStderr.trim() || "(none)"}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (await this.isPortAvailable()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
120
|
+
}
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Timeout waiting for WebView2/Chromium CDP port ${this.port}. Last stderr:
|
|
123
|
+
${this.processStderr.trim() || "(no stderr output)"}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
async start(initialViewport = { width: 1200, height: 800 }) {
|
|
127
|
+
const alreadyRunning = await this.isPortAvailable();
|
|
128
|
+
if (!alreadyRunning) {
|
|
129
|
+
if (!this.autoLaunch) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`App is not running on port ${this.port} and autoLaunch is false. Start app with WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=${this.port}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (!this.executablePath) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`[DesktopDriver] No executablePath provided and nothing is running on port ${this.port}. Specify --exe=<path> in CLI or executablePath in config.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const resolvedExe = import_path.default.resolve(process.cwd(), this.executablePath);
|
|
140
|
+
if (!import_fs.default.existsSync(resolvedExe)) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`[DesktopDriver] Desktop executable not found at: ${resolvedExe}. Please build your native project first.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
console.log(`\u{1F680} [DesktopDriver] Launching: ${resolvedExe}`);
|
|
146
|
+
const finalArgs = [...this.args];
|
|
147
|
+
const hasDebugPort = finalArgs.some((a) => a.startsWith("--remote-debugging-port="));
|
|
148
|
+
if (!hasDebugPort) {
|
|
149
|
+
finalArgs.push(`--remote-debugging-port=${this.port}`);
|
|
150
|
+
}
|
|
151
|
+
const mergedEnv = {
|
|
152
|
+
...process.env,
|
|
153
|
+
...this.env,
|
|
154
|
+
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${this.port}`
|
|
155
|
+
};
|
|
156
|
+
this.processStderr = "";
|
|
157
|
+
this.processExited = false;
|
|
158
|
+
this.exitCode = null;
|
|
159
|
+
this.childProcess = (0, import_child_process.spawn)(resolvedExe, finalArgs, {
|
|
160
|
+
env: mergedEnv,
|
|
161
|
+
cwd: this.cwd || import_path.default.dirname(resolvedExe),
|
|
162
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
163
|
+
detached: false
|
|
164
|
+
});
|
|
165
|
+
this.childProcess.stderr?.on("data", (chunk) => {
|
|
166
|
+
this.processStderr += chunk.toString();
|
|
167
|
+
});
|
|
168
|
+
this.childProcess.on("exit", (code) => {
|
|
169
|
+
this.processExited = true;
|
|
170
|
+
this.exitCode = code;
|
|
171
|
+
});
|
|
172
|
+
this.childProcess.on("error", (err) => {
|
|
173
|
+
console.error("[DesktopDriver] Failed to spawn process:", err);
|
|
174
|
+
});
|
|
175
|
+
console.log(`\u23F3 [DesktopDriver] Waiting for CDP debugging port on ${this.port}...`);
|
|
176
|
+
await this.waitForPort();
|
|
177
|
+
} else {
|
|
178
|
+
console.log(`\u{1F50C} [DesktopDriver] Attached to already running process on port ${this.port}`);
|
|
179
|
+
}
|
|
180
|
+
console.log(`\u{1F310} [DesktopDriver] Connecting Playwright CDP to http://127.0.0.1:${this.port}...`);
|
|
181
|
+
this.browser = await chromium.connectOverCDP(`http://127.0.0.1:${this.port}`);
|
|
182
|
+
const contexts = this.browser.contexts();
|
|
183
|
+
this.context = contexts[0] || await this.browser.newContext();
|
|
184
|
+
const pages = this.context.pages();
|
|
185
|
+
if (pages.length > 0) {
|
|
186
|
+
this.page = pages[0];
|
|
187
|
+
} else {
|
|
188
|
+
this.page = await this.context.waitForEvent("page", { timeout: 1e4 });
|
|
189
|
+
}
|
|
190
|
+
if (initialViewport) {
|
|
191
|
+
await this.page.setViewportSize(initialViewport).catch(() => {
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return { page: this.page, context: this.context, browser: this.browser };
|
|
195
|
+
}
|
|
196
|
+
async stop() {
|
|
197
|
+
if (this.browser) {
|
|
198
|
+
await this.browser.close().catch(() => {
|
|
199
|
+
});
|
|
200
|
+
this.browser = null;
|
|
201
|
+
}
|
|
202
|
+
if (this.childProcess && !this.childProcess.killed && this.childProcess.pid) {
|
|
203
|
+
console.log("\u{1F6D1} [DesktopDriver] Terminating spawned desktop process tree...");
|
|
204
|
+
const pid = this.childProcess.pid;
|
|
205
|
+
await new Promise((resolve) => {
|
|
206
|
+
(0, import_tree_kill.default)(pid, "SIGTERM", () => resolve());
|
|
207
|
+
});
|
|
208
|
+
this.childProcess = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/plugins/desktop-webview2/index.ts
|
|
214
|
+
var activeDriver = null;
|
|
215
|
+
var desktopWebview2Plugin = definePlugin({
|
|
216
|
+
name: "desktop-webview2",
|
|
217
|
+
version: "1.0.0",
|
|
218
|
+
launchSession: async (options, hookContext) => {
|
|
219
|
+
const cliOpts = hookContext.cliOptions || {};
|
|
220
|
+
const isDesktopMode = hookContext.targetMode === "desktop";
|
|
221
|
+
const hasExe = Boolean(cliOpts.executablePath || cliOpts.exe);
|
|
222
|
+
if (!isDesktopMode && !hasExe) {
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
console.log(`\u{1F50C} [Plugin:desktop-webview2] Initializing native desktop bridge...`);
|
|
226
|
+
const driver = new DesktopDriver({
|
|
227
|
+
executablePath: typeof cliOpts.executablePath === "string" ? cliOpts.executablePath : typeof cliOpts.exe === "string" ? cliOpts.exe : void 0,
|
|
228
|
+
port: typeof cliOpts.port === "number" ? cliOpts.port : 9222,
|
|
229
|
+
autoLaunch: typeof cliOpts.autoLaunchDesktop === "boolean" ? cliOpts.autoLaunchDesktop : true,
|
|
230
|
+
args: Array.isArray(cliOpts.desktopArgs) ? cliOpts.desktopArgs : void 0,
|
|
231
|
+
env: cliOpts.desktopEnv || void 0,
|
|
232
|
+
cwd: typeof cliOpts.startCwd === "string" ? cliOpts.startCwd : void 0
|
|
233
|
+
});
|
|
234
|
+
activeDriver = driver;
|
|
235
|
+
const session = await driver.start(options.currentViewport);
|
|
236
|
+
return {
|
|
237
|
+
page: session.page,
|
|
238
|
+
context: session.context,
|
|
239
|
+
browser: session.browser,
|
|
240
|
+
stop: async () => {
|
|
241
|
+
await driver.stop();
|
|
242
|
+
activeDriver = null;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
teardown: async () => {
|
|
247
|
+
if (activeDriver) {
|
|
248
|
+
await activeDriver.stop();
|
|
249
|
+
activeDriver = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
var desktop_webview2_default = desktopWebview2Plugin;
|
|
254
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
255
|
+
0 && (module.exports = {
|
|
256
|
+
desktopWebview2Plugin
|
|
257
|
+
});
|
|
258
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/desktop-webview2/index.ts","../../../src/shared/api/plugin.ts","../../../src/plugins/desktop-webview2/driver.ts","../../../src/shared/lib/playwrightLoader.ts"],"sourcesContent":["import { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport { DesktopDriver } from './driver';\n\nexport interface DesktopPluginOptions {\n executablePath?: string;\n port?: number;\n autoLaunch?: boolean;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\n\nlet activeDriver: DesktopDriver | null = null;\n\nexport const desktopWebview2Plugin: AgentLensPlugin = definePlugin({\n name: 'desktop-webview2',\n version: '1.0.0',\n\n launchSession: async (options, hookContext) => {\n // Only intercept if targetMode is desktop or an executable is explicitly provided\n const cliOpts = hookContext.cliOptions || {};\n const isDesktopMode = hookContext.targetMode === 'desktop';\n const hasExe = Boolean(cliOpts.executablePath || cliOpts.exe);\n\n if (!isDesktopMode && !hasExe) {\n return undefined;\n }\n\n console.log(`š [Plugin:desktop-webview2] Initializing native desktop bridge...`);\n\n const driver = new DesktopDriver({\n executablePath: typeof cliOpts.executablePath === 'string'\n ? cliOpts.executablePath\n : typeof cliOpts.exe === 'string'\n ? cliOpts.exe\n : undefined,\n port: typeof cliOpts.port === 'number' ? cliOpts.port : 9222,\n autoLaunch: typeof cliOpts.autoLaunchDesktop === 'boolean' ? cliOpts.autoLaunchDesktop : true,\n args: Array.isArray(cliOpts.desktopArgs) ? (cliOpts.desktopArgs as string[]) : undefined,\n env: (cliOpts.desktopEnv as Record<string, string>) || undefined,\n cwd: typeof cliOpts.startCwd === 'string' ? cliOpts.startCwd : undefined\n });\n\n activeDriver = driver;\n const session = await driver.start(options.currentViewport);\n\n return {\n page: session.page,\n context: session.context,\n browser: session.browser,\n stop: async () => {\n await driver.stop();\n activeDriver = null;\n }\n };\n },\n\n teardown: async () => {\n if (activeDriver) {\n await activeDriver.stop();\n activeDriver = null;\n }\n }\n});\n\nexport default desktopWebview2Plugin;\n","import type { Page, BrowserContext, Browser } from 'playwright';\nimport type { TestContext, VisualScenario } from './dsl';\nimport type { ReportData } from '../types/report';\n\nexport interface DriverLaunchResult {\n page: Page;\n context: BrowserContext;\n browser?: Browser;\n stop?: () => Promise<void>;\n}\n\nexport interface PluginHookContext {\n scenario?: VisualScenario;\n targetMode: 'desktop' | 'preview' | string;\n artifactsDir: string;\n cliOptions?: Record<string, unknown>;\n state: Map<string, unknown>;\n}\n\nexport interface AgentLensPlugin {\n /** Unique plugin identifier (e.g. 'desktop-webview2', 'live-controller', 'mock-ipc') */\n name: string;\n version?: string;\n\n /** Lifecycle hook: initial setup before drivers and scenarios start */\n setup?: (context: PluginHookContext) => Promise<void> | void;\n\n /**\n * Microkernel driver hook: allows a plugin to provide a custom browser/page session\n * (e.g., desktop WebView2 CDP connection, custom remote browser).\n */\n launchSession?: (\n options: { currentViewport: { width: number; height: number }; headed?: boolean },\n hookContext: PluginHookContext\n ) => Promise<DriverLaunchResult | undefined>;\n\n /** Lifecycle hook: called when browser context is ready */\n onContextCreated?: (context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: called when the test page is created/navigated */\n onPageCreated?: (page: Page, context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /**\n * Context extension: inject custom methods or properties directly into TestContext (ctx)\n */\n extendContext?: (\n ctx: TestContext,\n page: Page,\n hookContext: PluginHookContext\n ) => Record<string, any> | Promise<Record<string, any>>;\n\n /** Lifecycle hook: called after scenario runs and report data is calculated */\n onAfterRun?: (reportData: ReportData, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: teardown and cleanup guaranteed to run */\n teardown?: (hookContext: PluginHookContext) => Promise<void> | void;\n}\n\nexport function definePlugin(plugin: AgentLensPlugin): AgentLensPlugin {\n return plugin;\n}","import { spawn, type ChildProcess } from 'child_process';\nimport http from 'http';\nimport path from 'path';\nimport fs from 'fs';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport { chromium } from '../../shared/lib/playwrightLoader';\nimport treeKill from 'tree-kill';\n\nexport interface DesktopDriverOptions {\n port?: number;\n executablePath?: string;\n autoLaunch?: boolean;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\n\nexport class DesktopDriver {\n private port: number;\n private executablePath?: string;\n private autoLaunch: boolean;\n private args: string[];\n private env: Record<string, string>;\n private cwd?: string;\n\n private childProcess: ChildProcess | null = null;\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n private processStderr: string = '';\n private processExited: boolean = false;\n private exitCode: number | null = null;\n\n constructor(options?: DesktopDriverOptions) {\n this.port = options?.port || 9222;\n this.autoLaunch = options?.autoLaunch ?? true;\n this.executablePath = options?.executablePath;\n this.args = options?.args || [];\n this.env = options?.env || {};\n this.cwd = options?.cwd;\n }\n\n private async isPortAvailable(): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {\n resolve(res.statusCode === 200);\n });\n req.on('error', () => resolve(false));\n req.setTimeout(800, () => {\n req.destroy();\n resolve(false);\n });\n });\n }\n\n private async waitForPort(timeoutMs: number = 25000): Promise<void> {\n const startTime = Date.now();\n while (Date.now() - startTime < timeoutMs) {\n if (this.processExited) {\n throw new Error(\n `[DesktopDriver] Process terminated prematurely with exit code ${this.exitCode}.\\nStderr: ${this.processStderr.trim() || '(none)'}`\n );\n }\n\n if (await this.isPortAvailable()) {\n return;\n }\n await new Promise((r) => setTimeout(r, 400));\n }\n throw new Error(\n `Timeout waiting for WebView2/Chromium CDP port ${this.port}. Last stderr:\\n${this.processStderr.trim() || '(no stderr output)'}`\n );\n }\n\n public async start(initialViewport = { width: 1200, height: 800 }): Promise<{ page: Page; context: BrowserContext; browser: Browser }> {\n const alreadyRunning = await this.isPortAvailable();\n\n if (!alreadyRunning) {\n if (!this.autoLaunch) {\n throw new Error(\n `App is not running on port ${this.port} and autoLaunch is false. Start app with WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=${this.port}`\n );\n }\n\n if (!this.executablePath) {\n throw new Error(\n `[DesktopDriver] No executablePath provided and nothing is running on port ${this.port}. Specify --exe=<path> in CLI or executablePath in config.`\n );\n }\n\n const resolvedExe = path.resolve(process.cwd(), this.executablePath);\n if (!fs.existsSync(resolvedExe)) {\n throw new Error(\n `[DesktopDriver] Desktop executable not found at: ${resolvedExe}. Please build your native project first.`\n );\n }\n\n console.log(`š [DesktopDriver] Launching: ${resolvedExe}`);\n\n const finalArgs = [...this.args];\n const hasDebugPort = finalArgs.some((a) => a.startsWith('--remote-debugging-port='));\n if (!hasDebugPort) {\n finalArgs.push(`--remote-debugging-port=${this.port}`);\n }\n\n const mergedEnv = {\n ...process.env,\n ...this.env,\n WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${this.port}`\n };\n\n this.processStderr = '';\n this.processExited = false;\n this.exitCode = null;\n\n this.childProcess = spawn(resolvedExe, finalArgs, {\n env: mergedEnv,\n cwd: this.cwd || path.dirname(resolvedExe),\n stdio: ['ignore', 'ignore', 'pipe'],\n detached: false\n });\n\n this.childProcess.stderr?.on('data', (chunk) => {\n this.processStderr += chunk.toString();\n });\n\n this.childProcess.on('exit', (code) => {\n this.processExited = true;\n this.exitCode = code;\n });\n\n this.childProcess.on('error', (err) => {\n console.error('[DesktopDriver] Failed to spawn process:', err);\n });\n\n console.log(`ā³ [DesktopDriver] Waiting for CDP debugging port on ${this.port}...`);\n await this.waitForPort();\n } else {\n console.log(`š [DesktopDriver] Attached to already running process on port ${this.port}`);\n }\n\n console.log(`š [DesktopDriver] Connecting Playwright CDP to http://127.0.0.1:${this.port}...`);\n this.browser = await chromium.connectOverCDP(`http://127.0.0.1:${this.port}`);\n\n const contexts = this.browser.contexts();\n this.context = contexts[0] || (await this.browser.newContext());\n\n const pages = this.context.pages();\n if (pages.length > 0) {\n this.page = pages[0];\n } else {\n this.page = await this.context.waitForEvent('page', { timeout: 10000 });\n }\n\n if (initialViewport) {\n await this.page.setViewportSize(initialViewport).catch(() => {});\n }\n\n return { page: this.page, context: this.context, browser: this.browser };\n }\n\n public async stop(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n this.browser = null;\n }\n\n if (this.childProcess && !this.childProcess.killed && this.childProcess.pid) {\n console.log('š [DesktopDriver] Terminating spawned desktop process tree...');\n const pid = this.childProcess.pid;\n await new Promise<void>((resolve) => {\n treeKill(pid, 'SIGTERM', () => resolve());\n });\n this.childProcess = null;\n }\n }\n}\n","import { chromium as baseChromium, type ChromiumBrowser, type BrowserType } from 'playwright';\r\n\r\n/**\r\n * Safe Playwright Chromium loader that wraps browser initialization with helpful diagnostic messages.\r\n */\r\nexport const chromium: BrowserType<ChromiumBrowser> = new Proxy(baseChromium, {\r\n get(target, prop, receiver) {\r\n if (prop === 'launch') {\r\n return async (...args: Parameters<typeof baseChromium.launch>) => {\r\n try {\r\n return await target.launch(...args);\r\n } catch (err: any) {\r\n if (\r\n err.message?.includes(\"Executable doesn't exist\") ||\r\n err.message?.includes('playwright install') ||\r\n err.message?.includes('browser has not been downloaded')\r\n ) {\r\n console.error(\"\\nā [AgentLens] Playwright Chromium browser binary is missing!\");\r\n console.error(\"š Please install it by running: npx playwright install chromium\\n\");\r\n }\r\n throw err;\r\n }\r\n };\r\n }\r\n const val = Reflect.get(target, prop, receiver);\r\n return typeof val === 'function' ? val.bind(target) : val;\r\n }\r\n});"],"mappings":"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","names":["baseChromium","http","path","fs","treeKill"]}
|