pi-browser-harness 0.8.3 → 0.10.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/CHANGELOG.md +50 -0
- package/README.md +242 -181
- package/package.json +1 -1
- package/skills/deep-research/SKILL.md +66 -0
- package/skills/pi-browser-harness/SKILL.md +23 -0
- package/src/cdp/discovery.ts +183 -37
- package/src/cdp/ownership.ts +19 -1
- package/src/cdp/session.ts +74 -0
- package/src/cdp/target-factory.ts +223 -0
- package/src/cdp/window-binding.ts +15 -0
- package/src/client.ts +114 -27
- package/src/daemon/protocol.ts +12 -2
- package/src/daemon/spawn.ts +46 -13
- package/src/deep-research.ts +41 -0
- package/src/domains/forms.ts +355 -0
- package/src/domains/isolated-tab.ts +120 -0
- package/src/domains/navigate.ts +21 -13
- package/src/domains/readpage/capture.ts +46 -0
- package/src/domains/readpage/read-page.ts +119 -0
- package/src/domains/readpage/readability.ts +89 -0
- package/src/domains/render.ts +64 -0
- package/src/domains/search/extract.ts +44 -0
- package/src/domains/search/google-serp.ts +114 -0
- package/src/domains/search/web-search.ts +116 -0
- package/src/domains/setup.ts +4 -2
- package/src/index.ts +33 -7
- package/src/profile/bind.ts +138 -0
- package/src/profile/browser-process.ts +371 -0
- package/src/profile/command.ts +168 -0
- package/src/profile/launch.ts +90 -0
- package/src/profile/list.ts +171 -0
- package/src/profile/paths.ts +166 -0
- package/src/profile/picker.ts +73 -0
- package/src/profile/store.ts +105 -0
- package/src/prompt.ts +92 -116
- package/src/registry.ts +6 -0
- package/src/setup.ts +28 -44
- package/src/state.ts +1 -0
package/src/prompt.ts
CHANGED
|
@@ -2,150 +2,126 @@
|
|
|
2
2
|
* System prompt guidance for browser usage.
|
|
3
3
|
*
|
|
4
4
|
* Injects a compact browser control reference into the pi system prompt.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* This prompt carries only cross-cutting knowledge — the mental model, tool
|
|
6
|
+
* routing, workflows, and gotchas. Per-tool specifics (schemas, detailed usage)
|
|
7
|
+
* are injected separately via promptGuidelines on each tool registration, so this
|
|
8
|
+
* file intentionally does NOT restate full tool descriptions.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
export function getBrowserSystemPrompt(): string {
|
|
10
12
|
return `
|
|
11
13
|
## Browser Control
|
|
12
14
|
|
|
13
|
-
You
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
**These tools coexist with all other standard pi tools** — read, bash, edit,
|
|
17
|
-
write, and any other built-in tools are still available. Use browser_* tools
|
|
18
|
-
to interact with pages visually, and other tools alongside them.
|
|
19
|
-
|
|
20
|
-
### Browser-Specific Tools
|
|
21
|
-
|
|
22
|
-
| Tool | Purpose |
|
|
23
|
-
|------|---------|
|
|
24
|
-
| browser_snapshot | Accessibility-tree snapshot. Assigns every interactive element a stable **ref** (\`[eN]\`) plus \`@(x,y)\`. Refs are the primary handle for interaction tools — they survive re-renders, coordinates don't. |
|
|
25
|
-
| browser_screenshot | Capture a PNG or JPEG screenshot of the current page (rendered inline in the TUI; the LLM reads the image via the saved file path) |
|
|
26
|
-
| browser_click | Click an element by **ref** (preferred — re-resolves position, survives re-renders) or viewport x/y. Appends a compact diff of page changes. |
|
|
27
|
-
| browser_type | Type text into the focused element (real keystrokes; requires a focused field) |
|
|
28
|
-
| browser_fill | Fill an input/textarea by **ref** (preferred) or selector — updates React/Vue controlled components, verifies the value, appends a page-changes diff |
|
|
29
|
-
| browser_select_option | Select an option in a native <select> by **ref** (preferred) or selector, choosing by value/label/index (fires change) |
|
|
30
|
-
| browser_focus | Focus an element by **ref** (preferred) or selector (deterministic; use before browser_type) |
|
|
31
|
-
| browser_press_key | Press a keyboard key (Enter, Tab, Escape, arrows, etc.) |
|
|
32
|
-
| browser_scroll | Scroll the page at coordinates |
|
|
33
|
-
| browser_navigate | Navigate to a URL (result details.outcome.kind is "in_place" or "new_tab_created") |
|
|
34
|
-
| browser_new_tab | Open a new tab, optionally navigate |
|
|
35
|
-
| browser_open_urls | Open multiple URLs in new tabs (parallel) |
|
|
36
|
-
| browser_go_back / browser_go_forward / browser_reload | History navigation |
|
|
37
|
-
| browser_page_info | Get page URL, title, viewport, scroll position, or dialog info |
|
|
38
|
-
| browser_list_tabs | List all open browser tabs |
|
|
39
|
-
| browser_current_tab | Get current tab info |
|
|
40
|
-
| browser_switch_tab | Switch to a different tab by targetId |
|
|
41
|
-
| browser_execute_js | Execute JavaScript and return the result |
|
|
42
|
-
| browser_http_get | Direct HTTP GET (outside browser, for APIs) |
|
|
43
|
-
| browser_console | Read JS errors / console output on the current tab (diagnostic — use when an action looks broken; pass sinceSeq from the previous nextCursor to see only new messages) |
|
|
44
|
-
| browser_wait | Wait N seconds |
|
|
45
|
-
| browser_wait_for | Wait until a selector/text appears (or a selector disappears) — use for SPA content before interacting |
|
|
46
|
-
| browser_wait_for_load | Wait for document.readyState === 'complete' (returns a typed timeout error if the page doesn't reach readyState=complete in N seconds, default 15) |
|
|
47
|
-
| browser_handle_dialog | Accept or dismiss a JS dialog |
|
|
48
|
-
| browser_run_script | Execute a temporary script file with daemon access (write script to disk, then run) |
|
|
49
|
-
|
|
50
|
-
### Temporary Scripts
|
|
51
|
-
|
|
52
|
-
When the built-in tools aren't enough for a multi-step workflow, write a temporary
|
|
53
|
-
script to disk and execute it with browser_run_script. The script runs in the
|
|
54
|
-
harness process with direct access to the browser daemon and Node.js APIs.
|
|
15
|
+
You drive a **real Chrome** (the user's running instance, not headless) through
|
|
16
|
+
\`browser_*\` tools. These coexist with every standard pi tool — read, bash, edit,
|
|
17
|
+
write — so mix them freely: \`browser_*\` for the page, everything else alongside.
|
|
55
18
|
|
|
56
|
-
|
|
57
|
-
write("/tmp/scrape-pages.js", \`
|
|
58
|
-
const results = [];
|
|
59
|
-
for (const url of params.urls) {
|
|
60
|
-
await daemon.cdp("Page.navigate", { url });
|
|
61
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
62
|
-
const data = await daemon.evaluateJS("document.title");
|
|
63
|
-
results.push({ url, title: data });
|
|
64
|
-
}
|
|
65
|
-
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
66
|
-
\`)
|
|
67
|
-
browser_run_script("/tmp/scrape-pages.js", { urls: [...] })
|
|
68
|
-
\`\`\`
|
|
19
|
+
### The core handle: refs
|
|
69
20
|
|
|
70
|
-
|
|
21
|
+
Interaction is ref-first. Call \`browser_snapshot\` to get an accessibility tree
|
|
22
|
+
where every interactive element has a stable ref (\`[eN]\`) and \`@(x,y)\`. **Target
|
|
23
|
+
elements by ref, not by coordinates or guessed selectors** — refs are keyed to
|
|
24
|
+
element identity, so they survive the re-renders that React/Vue forms trigger on
|
|
25
|
+
every edit and save. Coordinates go stale; guessed selectors miss.
|
|
71
26
|
|
|
72
|
-
|
|
27
|
+
- Prefer \`browser_fill\` for text — it fires input/change so controlled components
|
|
28
|
+
update, and returns the value for verification. Use \`browser_click\` + \`browser_type\`
|
|
29
|
+
only for keystroke-sensitive widgets (autocomplete, masked inputs).
|
|
30
|
+
- Mutating calls append a compact **"Page changes"** diff — read it to confirm the
|
|
31
|
+
change landed (form closed, new \`*[eN]\` appeared).
|
|
32
|
+
- \`"ref is stale"\` means the page changed: re-run \`browser_snapshot\` for fresh refs.
|
|
73
33
|
|
|
74
|
-
|
|
34
|
+
### Choosing a tool
|
|
75
35
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
36
|
+
| To… | Reach for |
|
|
37
|
+
|-----|-----------|
|
|
38
|
+
| See the page structure (for interaction) | \`browser_snapshot\` (refs) |
|
|
39
|
+
| See the page visually | \`browser_screenshot\` |
|
|
40
|
+
| Search the web | \`browser_web_search\` → ranked {title, url, snippet} |
|
|
41
|
+
| Read an article's clean text | \`browser_read_page\` (reader mode; url or owned tab) |
|
|
42
|
+
| Extract specific DOM values | \`browser_execute_js\` |
|
|
43
|
+
| Hit a JSON/API endpoint | \`browser_http_get\` (raw GET, outside the browser) |
|
|
44
|
+
| Click / fill / type / select / focus / press a key | \`browser_click\` · \`browser_fill\` · \`browser_type\` · \`browser_select_option\` · \`browser_focus\` · \`browser_press_key\` |
|
|
45
|
+
| Upload a file · drag · resize viewport | \`browser_upload_file\` · \`browser_drag_and_drop\` · \`browser_viewport_resize\` |
|
|
46
|
+
| Go to a URL / open many / manage tabs | \`browser_navigate\` · \`browser_new_tab\` · \`browser_open_urls\` · \`browser_list_tabs\` · \`browser_switch_tab\` · \`browser_go_back\`/\`go_forward\`/\`reload\` |
|
|
47
|
+
| Wait for content / load / a fixed delay | \`browser_wait_for\` (selector/text) · \`browser_wait_for_load\` (readyState) · \`browser_wait\` |
|
|
48
|
+
| Inspect page / handle a dialog | \`browser_page_info\` · \`browser_current_tab\` · \`browser_handle_dialog\` |
|
|
49
|
+
| Diagnose a broken action | \`browser_console\` (JS errors) · \`browser_get_network_log\` |
|
|
50
|
+
| Save output / config downloads | \`browser_print_to_pdf\` · \`browser_download\` |
|
|
51
|
+
| Anything the tools can't express | \`browser_run_script\` (see Extending) |
|
|
81
52
|
|
|
82
|
-
|
|
53
|
+
Each tool carries its own detailed guidelines — this table is only for routing.
|
|
83
54
|
|
|
84
|
-
###
|
|
55
|
+
### Workflows
|
|
85
56
|
|
|
86
|
-
**
|
|
57
|
+
**Research (search → read):**
|
|
87
58
|
\`\`\`
|
|
88
|
-
|
|
59
|
+
browser_web_search("query") // ranked links, no page content
|
|
60
|
+
→ browser_read_page(url) // clean article text for the top hits
|
|
61
|
+
// fall back to browser_open_urls + browser_execute_js only when reader mode misses
|
|
89
62
|
\`\`\`
|
|
90
63
|
|
|
91
|
-
**Form filling (ref-first —
|
|
64
|
+
**Form filling (ref-first — the reliable path on SPA forms):**
|
|
92
65
|
\`\`\`
|
|
93
|
-
browser_wait_for({ selector: "#email" })
|
|
94
|
-
browser_snapshot()
|
|
95
|
-
→ browser_fill({ ref: "e7", value: "a@b.com" })
|
|
96
|
-
→ browser_select_option({ ref: "e9", label: "India" })
|
|
97
|
-
→ browser_click({ ref: "e12" })
|
|
98
|
-
// each
|
|
99
|
-
// the change landed (e.g. a save closed the form, a new field appeared as *[eN]).
|
|
66
|
+
browser_wait_for({ selector: "#email" }) // ensure the field is rendered
|
|
67
|
+
browser_snapshot() // interactive elements get [eN] refs
|
|
68
|
+
→ browser_fill({ ref: "e7", value: "a@b.com" })
|
|
69
|
+
→ browser_select_option({ ref: "e9", label: "India" })
|
|
70
|
+
→ browser_click({ ref: "e12" }) // re-resolves position even after reflow
|
|
71
|
+
// read each "Page changes" diff to confirm the step landed
|
|
100
72
|
\`\`\`
|
|
101
|
-
**Why ref over selector/coordinates:** refs are keyed to the element's identity, so
|
|
102
|
-
they survive the re-renders that React/Vue forms trigger on every edit and save —
|
|
103
|
-
coordinates go stale and selectors you guess often don't match. If a call returns
|
|
104
|
-
"ref is stale", the page changed: re-run browser_snapshot for fresh refs.
|
|
105
|
-
|
|
106
|
-
Prefer browser_fill for text inputs/textareas — it fires input/change so controlled
|
|
107
|
-
components update, and returns the field's value for verification. Use browser_click +
|
|
108
|
-
browser_type only for keystroke-sensitive widgets (autocomplete, masked inputs);
|
|
109
|
-
browser_type requires an already-focused field and errors if none is focused.
|
|
110
73
|
|
|
111
|
-
**
|
|
74
|
+
**Navigate + verify:**
|
|
112
75
|
\`\`\`
|
|
113
|
-
|
|
114
|
-
// or for APIs:
|
|
115
|
-
browser_http_get("https://api.example.com/data")
|
|
76
|
+
browser_new_tab("https://example.com") → browser_wait_for_load() → browser_screenshot()
|
|
116
77
|
\`\`\`
|
|
117
78
|
|
|
118
|
-
**
|
|
119
|
-
|
|
120
|
-
browser_screenshot() → browser_scroll({ deltaY: 500 }) → browser_screenshot()
|
|
121
|
-
\`\`\`
|
|
122
|
-
Note: deltaY follows W3C wheel-event convention: positive=down, negative=up. Default deltaY=300 scrolls down.
|
|
79
|
+
**Scroll:** \`browser_scroll({ deltaY: 500 })\` — W3C wheel convention: positive = down,
|
|
80
|
+
negative = up (default 300, down).
|
|
123
81
|
|
|
124
|
-
|
|
82
|
+
### Parallelism
|
|
83
|
+
|
|
84
|
+
Observation tools (\`browser_screenshot\`, \`browser_page_info\`, \`browser_execute_js\`,
|
|
85
|
+
\`browser_list_tabs\`, \`browser_http_get\`, \`browser_web_search\`, \`browser_read_page\`, …)
|
|
86
|
+
run in parallel with each other and with mutations. The harness serializes mutations
|
|
87
|
+
(click, type, scroll, navigate, switch_tab) so they never race. Emit independent calls
|
|
88
|
+
in one turn:
|
|
125
89
|
\`\`\`
|
|
126
|
-
|
|
127
|
-
browser_open_urls(urls: ["url1", "url2", ...]) → open result pages in parallel tabs
|
|
128
|
-
browser_list_tabs() → see all open tabs with targetIds
|
|
129
|
-
browser_switch_tab(targetId: "...") → switch to a tab
|
|
130
|
-
browser_screenshot() → visually inspect the page
|
|
131
|
-
browser_execute_js("document.querySelector('.main').innerText") → extract content
|
|
90
|
+
browser_screenshot() + browser_page_info() + browser_execute_js("document.title")
|
|
132
91
|
\`\`\`
|
|
133
92
|
|
|
134
|
-
**
|
|
93
|
+
**Multi-agent:** tab switching by one agent changes the active tab for all agents,
|
|
94
|
+
but per-tab data (console, network, dialogs) stays isolated. Call \`browser_current_tab\`
|
|
95
|
+
before a mutation to confirm you're on the expected tab. Handle any dialog reported by
|
|
96
|
+
\`browser_page_info\` promptly — dialogs block interaction and aren't queued across agents.
|
|
97
|
+
|
|
98
|
+
**Session boundary & tab hygiene:** you operate only inside this session's own Chrome
|
|
99
|
+
window. \`browser_switch_tab\`/\`browser_close_tab\` refuse tabs this session didn't open,
|
|
100
|
+
and \`browser_list_tabs\` defaults to owned tabs — never reach into the user's other tabs
|
|
101
|
+
or windows. Close tabs when you're done with them: call \`browser_close_tab\` on any tab
|
|
102
|
+
you opened (via \`browser_new_tab\`/\`browser_open_urls\`) once you've extracted what you
|
|
103
|
+
need. Don't leave a pile of stale tabs behind — keep only what a later step still needs.
|
|
104
|
+
(Leftover tabs are closed automatically when the session ends, but close them yourself
|
|
105
|
+
as you go.)
|
|
106
|
+
|
|
107
|
+
### Extending: temporary scripts
|
|
108
|
+
|
|
109
|
+
When built-in tools can't express a multi-step workflow, write a script to a temp file
|
|
110
|
+
and run it with \`browser_run_script\`. It executes in the harness process with direct
|
|
111
|
+
daemon + Node access.
|
|
135
112
|
\`\`\`
|
|
136
|
-
write(
|
|
113
|
+
write(<tempfile>.js, \`
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const url of params.urls) {
|
|
116
|
+
await daemon.cdp("Page.navigate", { url });
|
|
117
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
118
|
+
results.push({ url, title: await daemon.evaluateJS("document.title") });
|
|
119
|
+
}
|
|
120
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
121
|
+
\`)
|
|
122
|
+
browser_run_script(<tempfile>.js, { urls: [...] })
|
|
137
123
|
\`\`\`
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| Tool | Purpose |
|
|
142
|
-
|------|---------|
|
|
143
|
-
| browser_upload_file | Upload a file to a file input by **ref** (preferred) or selector (bypasses file picker) |
|
|
144
|
-
| browser_dispatch_key | Dispatch a DOM KeyboardEvent on an element by **ref** (preferred) or selector (for React/Vue inputs); returns details.matched |
|
|
145
|
-
| browser_download | Configure download directory and disable save-as prompts |
|
|
146
|
-
| browser_viewport_resize | Resize the viewport for responsive testing |
|
|
147
|
-
| browser_drag_and_drop | Perform drag-and-drop from one coordinate to another |
|
|
148
|
-
| browser_print_to_pdf | Print the current page to a PDF file |
|
|
149
|
-
| browser_get_network_log | Get buffered network request/response events |
|
|
124
|
+
Script bindings: params, daemon, require, signal, onUpdate, ctx, console, fetch, JSON,
|
|
125
|
+
Buffer, setTimeout, clearTimeout. Use a real temp path from the OS — don't hardcode \`/tmp\`.
|
|
150
126
|
`;
|
|
151
127
|
}
|
package/src/registry.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { consoleTool } from "./domains/console";
|
|
|
19
19
|
import { snapshotTool } from "./domains/snapshot";
|
|
20
20
|
import { executeJsTool, runScriptTool } from "./domains/js";
|
|
21
21
|
import { setupTool } from "./domains/setup";
|
|
22
|
+
import { webSearchTool } from "./domains/search/web-search";
|
|
23
|
+
import { readPageTool } from "./domains/readpage/read-page";
|
|
22
24
|
|
|
23
25
|
// Mutation tools that modify page / browser state are marked `serialized: true`
|
|
24
26
|
// so they run through a shared async mutex. Observation / read-only tools are
|
|
@@ -61,6 +63,10 @@ const TOOLS: ReadonlyArray<AnyBrowserToolDefinition> = [
|
|
|
61
63
|
snapshotTool,
|
|
62
64
|
executeJsTool,
|
|
63
65
|
runScriptTool,
|
|
66
|
+
// Opens and owns an isolated tab to scrape a SERP — same class as openUrlsTool.
|
|
67
|
+
{ ...webSearchTool, serialized: true },
|
|
68
|
+
// Pure isolated read (own sessionId, own tab) — safe concurrent, like executeJsTool.
|
|
69
|
+
readPageTool,
|
|
64
70
|
];
|
|
65
71
|
|
|
66
72
|
export const registerAllTools = (pi: ExtensionAPI, client: BrowserClient): void => {
|
package/src/setup.ts
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* users and a tool (browser_setup) for the agent to self-recover.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
9
8
|
import type { BrowserClient } from "./client";
|
|
10
9
|
import { ensureDaemon } from "./daemon/spawn";
|
|
10
|
+
import { DAEMON_SOCKET_PATH } from "./daemon/protocol";
|
|
11
|
+
import { isBrowserRunning } from "./profile/browser-process";
|
|
12
|
+
import { promptForProfileIfUnset } from "./profile/command";
|
|
11
13
|
|
|
12
14
|
// ── Public: register the /browser-setup slash command ──────────────────────
|
|
13
15
|
|
|
@@ -15,7 +17,7 @@ export function registerSetupCommand(pi: ExtensionAPI, client: BrowserClient): v
|
|
|
15
17
|
pi.registerCommand("browser-setup", {
|
|
16
18
|
description: "Connect pi to your Chrome browser",
|
|
17
19
|
handler: async (_args, ctx) => {
|
|
18
|
-
const result = await performSetup(client);
|
|
20
|
+
const result = await performSetup(client, ctx);
|
|
19
21
|
if (result.success) {
|
|
20
22
|
ctx.ui.notify(result.data, "info");
|
|
21
23
|
} else {
|
|
@@ -31,9 +33,9 @@ export type SetupResult = { success: true; data: string } | { success: false; er
|
|
|
31
33
|
|
|
32
34
|
// ── Shared setup logic (used by both the command and the tool) ─────────────
|
|
33
35
|
|
|
34
|
-
export async function performSetup(client: BrowserClient): Promise<SetupResult> {
|
|
36
|
+
export async function performSetup(client: BrowserClient, ctx?: ExtensionContext): Promise<SetupResult> {
|
|
35
37
|
// Step 1: Check Chrome is running
|
|
36
|
-
const chromeRunning =
|
|
38
|
+
const chromeRunning = await isBrowserRunning();
|
|
37
39
|
if (!chromeRunning) {
|
|
38
40
|
return { success: false, error: "No browser instance running. Please open your browser and then run /browser-setup." };
|
|
39
41
|
}
|
|
@@ -41,10 +43,16 @@ export async function performSetup(client: BrowserClient): Promise<SetupResult>
|
|
|
41
43
|
// Step 2: Start the browser daemon (spawns if not running, silently reuses if alive)
|
|
42
44
|
const daemonReady = await ensureDaemon();
|
|
43
45
|
if (!daemonReady) {
|
|
44
|
-
return { success: false, error:
|
|
46
|
+
return { success: false, error: `Could not start the browser daemon. Check ${DAEMON_SOCKET_PATH}.` };
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
// Step 3:
|
|
49
|
+
// Step 3: Ask which profile to work in, the first time only. Enumeration
|
|
50
|
+
// reads DevToolsActivePort + Local State from disk, so it works before the
|
|
51
|
+
// connection exists. Declining leaves the pre-profile behavior untouched —
|
|
52
|
+
// this must never block setup.
|
|
53
|
+
const profileNote = await promptForProfileIfUnset(client, ctx);
|
|
54
|
+
|
|
55
|
+
// Step 4: Connect to Chrome DevTools
|
|
48
56
|
const startResult = await client.start();
|
|
49
57
|
if (!startResult.success) {
|
|
50
58
|
const msg = startResult.error.message;
|
|
@@ -59,9 +67,12 @@ export async function performSetup(client: BrowserClient): Promise<SetupResult>
|
|
|
59
67
|
return {
|
|
60
68
|
success: false,
|
|
61
69
|
error:
|
|
62
|
-
"
|
|
63
|
-
|
|
70
|
+
"Browser remote debugging needs to be enabled.\n\n" +
|
|
71
|
+
"Open chrome://inspect/#remote-debugging (or brave://inspect,\n" +
|
|
72
|
+
"edge://inspect) in your browser, tick the\n" +
|
|
64
73
|
'"Discover network targets" / Allow checkbox, then retry.\n\n' +
|
|
74
|
+
"If that doesn't expose DevTools, relaunch the browser with\n" +
|
|
75
|
+
"--remote-debugging-port=9222.\n\n" +
|
|
65
76
|
"Or set BU_CDP_WS to a remote browser WebSocket URL.",
|
|
66
77
|
};
|
|
67
78
|
}
|
|
@@ -69,7 +80,7 @@ export async function performSetup(client: BrowserClient): Promise<SetupResult>
|
|
|
69
80
|
return { success: false, error: `Connection failed: ${msg}` };
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
// Step
|
|
83
|
+
// Step 5: Verify with test navigation
|
|
73
84
|
const tabResult = await client.newTab("https://github.com");
|
|
74
85
|
if (!tabResult.success) {
|
|
75
86
|
return { success: false, error: `Browser connected but test navigation failed: ${tabResult.error.message}` };
|
|
@@ -81,40 +92,13 @@ export async function performSetup(client: BrowserClient): Promise<SetupResult>
|
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
const pageUrl = info.success && !("dialog" in info.data) ? info.data.url : "github.com";
|
|
84
|
-
|
|
95
|
+
const lines = ["Browser connected ✓", `Navigated to: ${pageUrl}`];
|
|
96
|
+
if (profileNote) lines.push(profileNote);
|
|
97
|
+
return { success: true, data: lines.join("\n") };
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
// ── Chrome process detection ───────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// ps -o comm= returns the full executable path on modern macOS.
|
|
93
|
-
// We check for "Google Chrome" anywhere in the path (case-insensitive),
|
|
94
|
-
// excluding helper/renderer subprocesses.
|
|
95
|
-
const out = execSync("ps -A -o comm=", { timeout: 5000 }).toString().toLowerCase();
|
|
96
|
-
const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
97
|
-
return lines.some((l) => {
|
|
98
|
-
if (l.includes("helper") || l.includes("renderer") || l.includes("crashpad")) return false;
|
|
99
|
-
return l.includes("google chrome") || l.includes("chromium") || l.includes("microsoft edge");
|
|
100
|
-
});
|
|
101
|
-
} else if (process.platform === "linux") {
|
|
102
|
-
const out = execSync("ps -A -o comm=,args=", { timeout: 5000 }).toString().toLowerCase();
|
|
103
|
-
const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
104
|
-
const browserComms = ["chrome", "chromium", "chromium-browser", "msedge", "microsoft-edge", "google-chrome"];
|
|
105
|
-
return lines.some((line) => {
|
|
106
|
-
const parts = line.split(/\s+/);
|
|
107
|
-
const comm = parts[0] ?? "";
|
|
108
|
-
if (!comm || !browserComms.includes(comm)) return false;
|
|
109
|
-
const isSubprocess = line.includes("--type=") && !line.includes("--type=browser");
|
|
110
|
-
return !isSubprocess;
|
|
111
|
-
});
|
|
112
|
-
} else if (process.platform === "win32") {
|
|
113
|
-
const out = execSync("tasklist", { timeout: 5000 }).toString().toLowerCase();
|
|
114
|
-
return ["chrome.exe", "msedge.exe"].some((n) => out.includes(n));
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
// best-effort
|
|
118
|
-
}
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
101
|
+
//
|
|
102
|
+
// Detection moved to src/profile/browser-process.ts, which answers the same
|
|
103
|
+
// "is a browser running?" question per platform and additionally reports the
|
|
104
|
+
// executable path and any explicit --user-data-dir that profile selection needs.
|
package/src/state.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type BrowserState = {
|
|
|
12
12
|
readonly remoteBrowserId?: string;
|
|
13
13
|
readonly ownedTargetIds?: ReadonlyArray<string>;
|
|
14
14
|
readonly harnessWindowTargetId?: string;
|
|
15
|
+
readonly harnessWindowId?: number;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export const defaultState = (namespace = "default"): BrowserState => ({
|