aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-28 20:31:40 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commit91a2fdeff796bae8e87d9146e2d540550f54dd74 (patch)
tree9b243c2ad6643827159ba04840bd2b3a0eaf095b
parentb67faca60b21b0cf74d5c967f0f8d215ad601e93 (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>
-rw-r--r--src/client.ts54
-rw-r--r--src/deep-research.ts13
-rw-r--r--src/index.ts55
-rw-r--r--src/setup.ts21
-rw-r--r--src/state.ts13
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"]);