diff options
| author | amankumarsingh77 <amankumarsingh7702@gmail.com> | 2026-07-28 20:31:40 +0530 |
|---|---|---|
| committer | Aman Kumar <145339995+amankumarsingh77@users.noreply.github.com> | 2026-08-02 19:12:46 +0530 |
| commit | 91a2fdeff796bae8e87d9146e2d540550f54dd74 (patch) | |
| tree | 9b243c2ad6643827159ba04840bd2b3a0eaf095b /src | |
| parent | b67faca60b21b0cf74d5c967f0f8d215ad601e93 (diff) | |
refactor: strip comment noise from the root modules
Removes install/command banners and comments restating the code. Keeps
one-line notes for the per-session profile-pin re-read, the deliberately
surviving transport on shutdown, seed-before-attach under a pin, and the
starts-with-return wrap rule. src/prompt.ts is untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.ts | 54 | ||||
| -rw-r--r-- | src/deep-research.ts | 13 | ||||
| -rw-r--r-- | src/index.ts | 55 | ||||
| -rw-r--r-- | src/setup.ts | 21 | ||||
| -rw-r--r-- | src/state.ts | 13 |
5 files changed, 5 insertions, 151 deletions
diff --git a/src/client.ts b/src/client.ts index 4937447..84a98ee 100644 --- a/src/client.ts +++ b/src/client.ts @@ -24,7 +24,6 @@ export type BrowserClientOptions = { readonly harnessWindowTargetId?: string; readonly harnessWindowId?: number; }; - /** Profile to pin this session to, loaded from the durable pin store. */ readonly profilePin?: ProfilePin | null; readonly onOwnershipChange?: (snapshot: { readonly ownedTargetIds: ReadonlyArray<string>; @@ -39,11 +38,7 @@ export type BrowserClient = { status(): DaemonStatus; start(): Promise<Result<void, CdpError>>; stop(): Promise<void>; - /** Detach from the current page target (removes the "Chrome is being controlled" banner) - * while keeping the transport connection alive. Call on session shutdown. */ detach(): Promise<void>; - /** Close every tab this session owns and clear the window binding. Best-effort — - * used on session shutdown so no stale harness tabs survive. */ closeOwnedTabs(): Promise<void>; evaluateJs(expression: string, sessionId?: string): Promise<Result<unknown, CdpError>>; pageInfo(): Promise<Result<PageInfo | { readonly dialog: DialogInfo }, CdpError>>; @@ -57,21 +52,12 @@ export type BrowserClient = { current(): { readonly sessionId: string; readonly targetId: string } | null; session(): CdpSession; transport(): CdpTransport; - /** User-data-dir of the connected browser, when discovery could determine it. - * Undefined for BU_CDP_WS / bare-port connections, where profile selection - * is unavailable. */ userDataDir(): string | undefined; - /** The profile this session is pinned to, or null when unpinned. */ profilePin(): ProfilePin | null; setProfilePin(pin: ProfilePin | null): void; - /** browserContextId of the pinned profile, once a window has identified it. - * Session-scoped: Chrome re-mints context ids on every browser run. */ profileContextId(): string | undefined; setProfileContextId(contextId: string | undefined): void; - /** Open and adopt a window inside the pinned profile. Returns its seed tab. */ seedPinnedProfileWindow(): Promise<Result<string, CdpError>>; - /** Returns the shared async mutex that serialized browser tools must acquire - * before performing mutations. Observation tools should not use this. */ mutationMutex(): Mutex; }; @@ -132,9 +118,6 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = }); }); } - // The seed provider routes attachFirstPage's fallback through the target - // factory, so a pinned profile is honoured on every path that can create the - // harness window. const session = createCdpSession(transport, ownership, async () => { const window = await ensureHarnessWindow(api); return window.success ? ok(window.data.targetId) : window; @@ -147,12 +130,7 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = let profilePin: ProfilePin | null = opts.profilePin ?? null; let profileContextId: string | undefined; - /** - * With a profile pinned, the harness window must exist BEFORE anything - * attaches: session.attachFirstPage() would otherwise fall back to a bare - * Target.createTarget, which lands in the focus-derived default profile. - * Seeding first means attachFirstPage always finds an owned tab to attach to. - */ + // With a pin set the window must exist BEFORE anything attaches, or attachFirstPage's bare Target.createTarget lands in the focus-derived default profile. const seedIfPinned = async (): Promise<Result<void, CdpError>> => { if (!profilePin) return ok(undefined); const window = await ensureHarnessWindow(api); @@ -215,14 +193,10 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = return start(); } lastHealth = Date.now(); - // Verify the page session is still responsive (handles the case where the - // browser transport is alive but the page target crashed, e.g. localhost died). const jsProbe = await session.call("Runtime.evaluate", { expression: "1", returnByValue: true, }, { timeoutMs: 2_000 }); if (!jsProbe.success && jsProbe.error.kind === "session_not_found") { - // Re-seed first when pinned: if the user closed every harness tab, - // attachFirstPage would mint a window in the focus-derived profile. const seeded = await seedIfPinned(); if (!seeded.success) return seeded; const reattached = await session.attachFirstPage(); @@ -237,12 +211,7 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = }; const evaluateJs = async (expression: string, sessionId?: string): Promise<Result<unknown, CdpError>> => { - // Heuristic IIFE wrap: legacy convenience that lets agents write - // `return foo` instead of `(() => foo)()`. Only wrap when the trimmed - // source *starts with* a return statement — a bare expression, or one that - // merely mentions `return` inside a string literal or comment, is passed - // through untouched (previously any substring `"return "` triggered a wrap, - // silently turning such expressions into `undefined`). + // Wrap only when the trimmed source *starts with* `return` — matching the substring anywhere silently turned expressions mentioning it into `undefined`. const trimmed = expression.trim(); const isReturnStatement = /^return[\s(]/.test(trimmed); const wrapped = isReturnStatement ? `(function(){${expression}})()` : expression; @@ -289,14 +258,12 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = url: t.url, owned: ownership.has(t.targetId), })); - // Reconcile any persisted owned ids that no longer exist as live page targets. const live = new Set(tabs.map((t) => t.targetId)); const owned = ownership.list(); const survivors = owned.filter((id) => live.has(id)); if (survivors.length !== owned.length) ownership.replaceAll(survivors); const hw = ownership.harnessWindow(); if (hw && !live.has(hw)) ownership.setHarnessWindow(undefined); - // Prune per-tab page caches for tabs that no longer exist for (const tid of pageCaches.keys()) { if (!live.has(tid)) pageCaches.delete(tid); } @@ -306,10 +273,6 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = const switchTab = async (targetId: string): Promise<Result<void, CdpError>> => { const r = await session.switchTo(targetId); if (!r.success) return r; - // pageCache per-tab: no longer cleared — each tab retains its cache - // Best-effort: mark the tab title with a green circle so the user can - // see which tab the agent attached to. CSP or detached frames may - // block the eval; we don't surface that as a switchTab failure. await session.call("Runtime.evaluate", { expression: safeJs`if(!document.title.startsWith('🟢'))document.title='🟢 '+document.title`, }); @@ -317,17 +280,11 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = }; const newTab = async (url?: string): Promise<Result<string, CdpError>> => { - // Reconcile ownership against live targets before deciding where to open. const tabsResult = await listTabs(true); if (!tabsResult.success) return tabsResult; - // ensureHarnessWindow reuses the seed tab, falls back to any surviving - // owned tab, and only then creates a window — which, when a profile is - // pinned, means launching one inside that profile. const window = await ensureHarnessWindow(api); if (!window.success) return window; - // A freshly created window IS the new tab; opening another would leave a - // stray blank tab behind. let tabId: string; if (window.data.freshlyCreated) { tabId = window.data.targetId; @@ -354,9 +311,6 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = }; const closeOwnedTabs = async (): Promise<void> => { - // Snapshot first — remove() mutates the list, and targetDestroyed events - // may also prune concurrently. Failures are swallowed: a tab the user - // already closed, or a dead transport, must not block shutdown. for (const id of ownership.list()) { await session.callBrowser("Target.closeTarget", { targetId: id }).catch(() => {}); ownership.remove(id); @@ -375,10 +329,6 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = const detach = async (): Promise<void> => { const cur = session.current(); if (!cur) return; - // Target.detachFromTarget removes the "Chrome is being controlled" banner - // and releases the page session. The transport (WebSocket/Unix socket) - // stays alive for reuse by the next session. - // Reference: https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-detachFromTarget await transport.request("Target.detachFromTarget", { sessionId: cur.sessionId }, { sessionId: null }); }; diff --git a/src/deep-research.ts b/src/deep-research.ts index 5034fe4..5fa596d 100644 --- a/src/deep-research.ts +++ b/src/deep-research.ts @@ -1,18 +1,6 @@ -/** - * Deep research — the /deep-research slash command. An explicit entry point - * (alongside the auto-triggering deep-research skill) that kicks off a research - * run for a given question. - * - * The command is thin: it validates a non-empty question and injects a user - * message that starts the deep-research flow. The actual orchestration - * (decompose → fan out web-search-researcher subagents → synthesize a cited - * report) lives in skills/deep-research/SKILL.md, which the injected prompt - * triggers. pi.sendUserMessage always starts a turn. - */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -// ── Shared core: build the prompt that starts a deep-research run ─────────── export function buildDeepResearchPrompt(question: string): string { return ( @@ -23,7 +11,6 @@ export function buildDeepResearchPrompt(question: string): string { ); } -// ── Public: register the /deep-research slash command ────────────────────── export function registerDeepResearchCommand(pi: ExtensionAPI): void { pi.registerCommand("deep-research", { diff --git a/src/index.ts b/src/index.ts index 9029940..7b35f35 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,3 @@ -/** - * pi-browser-harness — browser control extension for pi. - * - * Gives pi agents full control of a real Chrome browser via CDP. - * Connects to the user's running Chrome (chrome://inspect/#remote-debugging), - * registers browser_* tools, and injects browser control guidance into the - * system prompt. - * - * Install: - * pi install npm:pi-browser-harness - * # or copy to .pi/extensions/pi-browser-harness/ - * - * Commands: - * /browser-setup — guided setup wizard - * /browser-profile — choose which browser profile the agent uses - * /browser-status — show client status and current page - * /browser-reload-daemon — restart the browser client - * /deep-research <q> — research a question on the web, write a cited report - * - * Flags: - * --browser-namespace <name> — override namespace (default: auto) - * --browser-debug-clicks — enable debug click overlay - */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { type BrowserClient, createBrowserClient } from "./client"; @@ -108,15 +85,9 @@ export default function browserHarnessExtension(pi: ExtensionAPI): void { pi.on("session_start", async (_event, ctx) => { state = restoreState(ctx, state.namespace); - // The pinned profile lives on disk, not in the session, so it survives - // session termination. Re-read it every session start: another pi session - // may have changed it since this client was created. + // Re-read the on-disk profile pin every session start: another pi session may have changed it since this client was created. const profilePin = await readPin(); - // Create the client lazily on first session. The daemon transport - // is created but NOT connected — that only happens when the user - // runs /browser-setup or a browser tool call finds an existing socket. - // This means zero Chrome prompts for non-browser pi sessions. if (!client) { const transport = createDaemonTransport(state.namespace); @@ -141,22 +112,13 @@ export default function browserHarnessExtension(pi: ExtensionAPI): void { ? { harnessWindowId: snap.harnessWindowId } : {}), }; - // Ownership changes can fire from CDP events at any time, - // including after session replacement when pi's ctx is stale. - // Persistence is best-effort — failure must not crash the event consumer. - // ponytail: try/catch is the simplest guard against stale-ctx. - try { persistState(pi, state); } catch { /* stale ctx — safe to ignore */ } + try { persistState(pi, state); } catch {} }, }); } else { - // Client carried over from a previous session — adopt whatever pin is on - // disk now, which another pi session may have changed. client.setProfilePin(profilePin); } - // Do NOT call client.start() here — browser control is on-demand. - // The first browser tool call or /browser-setup triggers the actual - // daemon socket connection and Chrome prompt. if (!toolsRegistered) { registerAllTools(pi, client); @@ -170,24 +132,13 @@ export default function browserHarnessExtension(pi: ExtensionAPI): void { pi.on("session_shutdown", async () => { if (client) { try { - // Detach from the page target (removes the "Chrome is being controlled" - // banner) but keep the transport alive for the next session. await client.detach(); - // Close every tab this session opened so no stale harness tabs linger - // in the user's browser between sessions. This also clears the persisted - // ownership + window binding via onOwnershipChange, so the next session - // starts with a fresh dedicated window rather than reattaching to leftovers. await client.closeOwnedTabs(); } catch (e) { console.warn("[pi-browser-harness] browser teardown failed during shutdown:", e); } - // ponytail: keep the transport alive across sessions. - // Do NOT call client.stop() or null out client — the daemon connection - // persists and eliminates the per-session "Allow Remote Debugging" prompt. - // Only Chrome restart or daemon death triggers a new prompt. + // Do NOT stop the client or null it out: the surviving daemon connection is what eliminates the per-session "Allow Remote Debugging" prompt. } - // Persist last — closeOwnedTabs mutates `state` through onOwnershipChange, - // so this captures the cleared ownership set. persistState(pi, state); await cleanupTempDirs(); }); diff --git a/src/setup.ts b/src/setup.ts index 8c04030..d2ab7ac 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,8 +1,3 @@ -/** - * Setup — verifies Chrome is running with remote debugging and connects - * the pi agent to it. Exposed as both a slash command (/browser-setup) for - * users and a tool (browser_setup) for the agent to self-recover. - */ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import type { BrowserClient } from "./client"; @@ -11,7 +6,6 @@ import { DAEMON_SOCKET_PATH } from "./daemon/protocol"; import { isBrowserRunning } from "./profile/browser-process"; import { promptForProfileIfUnset } from "./profile/command"; -// ── Public: register the /browser-setup slash command ────────────────────── export function registerSetupCommand(pi: ExtensionAPI, client: BrowserClient): void { pi.registerCommand("browser-setup", { @@ -27,32 +21,23 @@ export function registerSetupCommand(pi: ExtensionAPI, client: BrowserClient): v }); } -// ── Shared setup result ──────────────────────────────────────────────────── export type SetupResult = { success: true; data: string } | { success: false; error: string }; -// ── Shared setup logic (used by both the command and the tool) ───────────── export async function performSetup(client: BrowserClient, ctx?: ExtensionContext): Promise<SetupResult> { - // Step 1: Check Chrome is running const chromeRunning = await isBrowserRunning(); if (!chromeRunning) { return { success: false, error: "No browser instance running. Please open your browser and then run /browser-setup." }; } - // Step 2: Start the browser daemon (spawns if not running, silently reuses if alive) const daemonReady = await ensureDaemon(); if (!daemonReady) { return { success: false, error: `Could not start the browser daemon. Check ${DAEMON_SOCKET_PATH}.` }; } - // Step 3: Ask which profile to work in, the first time only. Enumeration - // reads DevToolsActivePort + Local State from disk, so it works before the - // connection exists. Declining leaves the pre-profile behavior untouched — - // this must never block setup. const profileNote = await promptForProfileIfUnset(client, ctx); - // Step 4: Connect to Chrome DevTools const startResult = await client.start(); if (!startResult.success) { const msg = startResult.error.message; @@ -80,7 +65,6 @@ export async function performSetup(client: BrowserClient, ctx?: ExtensionContext return { success: false, error: `Connection failed: ${msg}` }; } - // Step 5: Verify with test navigation const tabResult = await client.newTab("https://github.com"); if (!tabResult.success) { return { success: false, error: `Browser connected but test navigation failed: ${tabResult.error.message}` }; @@ -97,8 +81,3 @@ export async function performSetup(client: BrowserClient, ctx?: ExtensionContext return { success: true, data: lines.join("\n") }; } -// ── Chrome process detection ─────────────────────────────────────────────── -// -// Detection moved to src/profile/browser-process.ts, which answers the same -// "is a browser running?" question per platform and additionally reports the -// executable path and any explicit --user-data-dir that profile selection needs. diff --git a/src/state.ts b/src/state.ts index 601f7e0..05de5d3 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,9 +1,3 @@ -/** - * Session persistence for pi-browser-harness. - * - * Persists the daemon namespace and (when applicable) the remote browser ID - * across session reloads and branch navigation. - */ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { asArrayOf, asNumber, asString, isRecord } from "./util/guards"; @@ -25,13 +19,6 @@ export const persistState = (pi: ExtensionAPI, state: BrowserState): void => { pi.appendEntry<BrowserState>("browser-harness-state", state); }; -/** - * Find the last browser-harness-state entry in the current branch - * and return the restored state, merged with defaults. - * - * If `currentNamespace` is supplied (e.g. from the --browser-namespace flag), - * it overrides whatever is in the persisted entry. - */ const asPersistedState = (v: unknown): Partial<BrowserState> | undefined => { if (!isRecord(v)) return undefined; const namespace = asString(v["namespace"]); |
