pi-browser-use 0.11.0 → 0.11.1

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.
Files changed (55) hide show
  1. package/dist/annotate.d.ts +0 -14
  2. package/dist/annotate.js +0 -14
  3. package/dist/artifacts.d.ts +0 -2
  4. package/dist/artifacts.js +0 -2
  5. package/dist/auth-verifiers.d.ts +0 -29
  6. package/dist/auth-verifiers.js +0 -31
  7. package/dist/chrome-launcher.d.ts +0 -59
  8. package/dist/chrome-launcher.js +0 -54
  9. package/dist/client.d.ts +0 -4
  10. package/dist/client.js +0 -17
  11. package/dist/config.d.ts +0 -25
  12. package/dist/config.js +0 -23
  13. package/dist/doctor.d.ts +0 -8
  14. package/dist/doctor.js +0 -5
  15. package/dist/existing-flow.d.ts +0 -31
  16. package/dist/existing-flow.js +0 -29
  17. package/dist/focus-policy.d.ts +0 -13
  18. package/dist/focus-policy.js +0 -13
  19. package/dist/index.d.ts +0 -1
  20. package/dist/index.js +0 -1
  21. package/dist/mcp-server.d.ts +0 -2
  22. package/dist/mcp-server.js +0 -10
  23. package/dist/named-profile.d.ts +0 -36
  24. package/dist/named-profile.js +0 -37
  25. package/dist/persistent-backend.d.ts +2 -56
  26. package/dist/persistent-backend.js +30 -65
  27. package/dist/persistent-store.d.ts +0 -24
  28. package/dist/persistent-store.js +0 -24
  29. package/dist/profile-lock.d.ts +0 -21
  30. package/dist/profile-lock.js +0 -27
  31. package/dist/profile.d.ts +0 -10
  32. package/dist/profile.js +0 -14
  33. package/dist/runtime.d.ts +0 -3
  34. package/dist/runtime.js +3 -92
  35. package/dist/session-manager.d.ts +0 -48
  36. package/dist/session-manager.js +0 -46
  37. package/dist/session.d.ts +0 -42
  38. package/dist/session.js +0 -18
  39. package/dist/settings.d.ts +0 -5
  40. package/dist/settings.js +0 -5
  41. package/dist/setup-flow.d.ts +0 -41
  42. package/dist/setup-flow.js +0 -37
  43. package/dist/shared-backend.d.ts +0 -30
  44. package/dist/shared-backend.js +0 -31
  45. package/dist/tab-bridge.d.ts +0 -27
  46. package/dist/tab-bridge.js +0 -27
  47. package/dist/tool-augment.d.ts +0 -9
  48. package/dist/tool-augment.js +0 -14
  49. package/dist/vision.d.ts +0 -17
  50. package/dist/vision.js +0 -17
  51. package/docs/performance.md +2 -0
  52. package/extension/README.md +2 -0
  53. package/extension/background.js +4 -9
  54. package/package.json +1 -1
  55. package/plugin.json +1 -1
package/dist/session.d.ts CHANGED
@@ -1,31 +1,13 @@
1
- /**
2
- * Stable internal browser API shared by all Pi browser modes.
3
- *
4
- * Skills talk to {@link BrowserSession} only. They must never deal with
5
- * Chrome process flags, profile directories, CDP endpoints, tab-group IDs,
6
- * or focus behavior directly — that translation lives in
7
- * `BrowserSessionManager` (see `session-manager.ts`).
8
- *
9
- * Spec sections: 1 (core architecture), 2 (persistent metadata),
10
- * 6 (skill-owned auth detection), 8 (execution modes), 21 (capabilities),
11
- * 23 (status UX).
12
- */
13
1
  export type BrowserSessionMode = 'fresh' | 'persistent' | 'existing';
14
- /** Opaque handle to an open page/tab. Backends resolve it to MCP/CDP state. */
15
2
  export interface PageHandle {
16
- /** Backend page identifier (MCP pageId, CDP target id, ...). */
17
3
  id: string;
18
4
  url?: string;
19
5
  title?: string;
20
6
  }
21
- /** User-facing browser status. Never expose CDP/MCP terminology here. */
22
7
  export interface BrowserStatus {
23
8
  mode: BrowserSessionMode;
24
- /** Plain-language state, e.g. "Running privately in background". */
25
9
  state: string;
26
- /** e.g. "Ready", "Setup required", "Authentication required". */
27
10
  profile: string;
28
- /** e.g. "Headless", "Visible fallback", "Using your existing Chrome". */
29
11
  execution: string;
30
12
  }
31
13
  export interface BrowserSession {
@@ -34,21 +16,10 @@ export interface BrowserSession {
34
16
  openPage(url: string): Promise<PageHandle>;
35
17
  listPages(): Promise<PageHandle[]>;
36
18
  closePage(page: PageHandle): Promise<void>;
37
- /**
38
- * Ask to make the browser visible (auth handoff, explicit "show me").
39
- * Implementations may no-op when already visible or when the mode is
40
- * headless-only.
41
- */
42
19
  requestVisibleBrowser(reason: string): Promise<void>;
43
20
  getStatus(): Promise<BrowserStatus>;
44
21
  shutdown(): Promise<void>;
45
22
  }
46
- /**
47
- * Thrown by skill-level auth verifiers when a page needs a human
48
- * (sign-in, 2FA, passkey, CAPTCHA, SSO). The browser subsystem catches this
49
- * and moves the Persistent session into reauthentication; skills define the
50
- * destination URL and the authenticated/challenge checks.
51
- */
52
23
  export declare class BrowserAuthRequired extends Error {
53
24
  readonly provider: string;
54
25
  readonly url?: string;
@@ -58,35 +29,22 @@ export declare class BrowserAuthRequired extends Error {
58
29
  message?: string;
59
30
  });
60
31
  }
61
- /** Two automation execution modes inside Persistent (spec section 8). */
62
32
  export type PersistentExecutionMode = 'headless' | 'headed-background';
63
- /** Persistent lifecycle state machine (spec section 2). */
64
33
  export type PersistentState = 'UNINITIALIZED' | 'SETUP_REQUIRED' | 'SETUP_HEADFUL' | 'READY' | 'AUTOMATING_HEADLESS' | 'AUTOMATING_HEADFUL' | 'REAUTH_REQUIRED' | 'REAUTH_HEADFUL';
65
- /**
66
- * Durable Persistent state. Passwords, cookies, tokens, and copied browser
67
- * credentials must never be stored here — Chrome owns those inside its
68
- * profile directory.
69
- */
70
34
  export interface PersistentBrowserMetadata {
71
35
  initialized: boolean;
72
36
  profilePath: string;
73
37
  lastSuccessfulMode?: 'headless' | 'headed';
74
38
  lastBootstrapAt?: string;
75
39
  }
76
- /** Optional per-origin headed-background preference (spec section 8). */
77
40
  export interface SiteBrowserPreference {
78
41
  origin: string;
79
42
  executionMode: PersistentExecutionMode;
80
43
  }
81
- /** Capability intent from a skill (spec section 21). */
82
44
  export interface BrowserCapabilityRequest {
83
- /** True when cookies/sessions must survive restarts. */
84
45
  persistence?: boolean;
85
- /** e.g. "gmail", "github" — informational, used for status/reauth UX. */
86
46
  authentication?: string;
87
- /** Default: prefer headless, fall back to headed-background per origin. */
88
47
  visibility?: 'prefer-headless' | 'require-headless' | 'allow-visible';
89
- /** Whether falling back to the user's own Chrome is acceptable. */
90
48
  supportsExisting?: boolean;
91
49
  }
92
50
  //# sourceMappingURL=session.d.ts.map
package/dist/session.js CHANGED
@@ -1,21 +1,3 @@
1
- /**
2
- * Stable internal browser API shared by all Pi browser modes.
3
- *
4
- * Skills talk to {@link BrowserSession} only. They must never deal with
5
- * Chrome process flags, profile directories, CDP endpoints, tab-group IDs,
6
- * or focus behavior directly — that translation lives in
7
- * `BrowserSessionManager` (see `session-manager.ts`).
8
- *
9
- * Spec sections: 1 (core architecture), 2 (persistent metadata),
10
- * 6 (skill-owned auth detection), 8 (execution modes), 21 (capabilities),
11
- * 23 (status UX).
12
- */
13
- /**
14
- * Thrown by skill-level auth verifiers when a page needs a human
15
- * (sign-in, 2FA, passkey, CAPTCHA, SSO). The browser subsystem catches this
16
- * and moves the Persistent session into reauthentication; skills define the
17
- * destination URL and the authenticated/challenge checks.
18
- */
19
1
  export class BrowserAuthRequired extends Error {
20
2
  provider;
21
3
  url;
@@ -1,9 +1,4 @@
1
1
  import type { BrowserUseConfig } from './config.js';
2
- /**
3
- * Load the "pi-browser-use" section. Priority: user settings, then trusted
4
- * project settings (<cwd>/.pi/settings.json, no env expansion). Project
5
- * settings apply only when the session trusts the project.
6
- */
7
2
  export declare function loadConfig(options?: {
8
3
  cwd?: string;
9
4
  projectTrusted?: boolean;
package/dist/settings.js CHANGED
@@ -29,11 +29,6 @@ function readSection(filePath, expand) {
29
29
  throw error;
30
30
  }
31
31
  }
32
- /**
33
- * Load the "pi-browser-use" section. Priority: user settings, then trusted
34
- * project settings (<cwd>/.pi/settings.json, no env expansion). Project
35
- * settings apply only when the session trusts the project.
36
- */
37
32
  export function loadConfig(options) {
38
33
  const user = readSection(join(homedir(), '.pi', 'agent', 'settings.json'), true);
39
34
  const cwd = options?.cwd ?? process.cwd();
@@ -1,40 +1,13 @@
1
- /**
2
- * Persistent bootstrap and reauthentication flows (spec sections 3 and 7).
3
- *
4
- * Bootstrap (first run): launch a real headed Chrome on the Pi profile with
5
- * no MCP/Puppeteer/CDP — an ordinary manually launched browser. The user
6
- * signs into Chrome/Google and any sites, completes 2FA/passkeys/SSO, then
7
- * closes the window. Close means SETUP_HEADFUL → READY; it initializes the
8
- * *profile*, it does not prove every site is authenticated (auth stays
9
- * site-specific, verified by skill-level verifiers).
10
- *
11
- * Reauthentication: when a skill raises BrowserAuthRequired, shut the
12
- * headless backend down cleanly, then reauth in one of two variants:
13
- * - Variant B (default): Pi-owned headed Chrome *with* CDP, so Pi can
14
- * navigate to the login page before handing control to the user.
15
- * - Variant A (fallback): plain headed Chrome with no MCP/CDP, for login
16
- * providers that reject instrumented browsers.
17
- * Afterwards the backend restarts headless and automation resumes.
18
- *
19
- * Launchers are injectable so the orchestration is unit-testable.
20
- */
21
1
  import type { PersistentBackend } from './persistent-backend.js';
22
2
  export type ReauthVariant = 'instrumented' | 'plain';
23
3
  export interface SetupFlowEvents {
24
- /** Human-facing instruction shown before the setup window opens. */
25
4
  onSetupNeeded?: (message: string) => void;
26
- /** Fired when the setup browser exits and the profile is marked ready. */
27
5
  onSetupComplete?: (profileDir: string) => void;
28
6
  onReauthNeeded?: (message: string) => void;
29
7
  onReauthComplete?: (profileDir: string) => void;
30
8
  }
31
9
  export declare const SETUP_INSTRUCTIONS: string;
32
10
  export declare function reauthInstructions(url: string, variant: ReauthVariant): string;
33
- /**
34
- * First-run bootstrap. Holds no locks itself — the caller (browser_setup
35
- * tool) must ensure no backend is running on the profile. Resolves with the
36
- * window exit code after marking the profile initialized.
37
- */
38
11
  export declare function runBootstrap(options: {
39
12
  profileDir?: string;
40
13
  executablePath?: string;
@@ -50,13 +23,11 @@ export declare function runBootstrap(options: {
50
23
  }, events?: SetupFlowEvents): Promise<number | null>;
51
24
  export interface ReauthOptions {
52
25
  backend: Pick<PersistentBackend, 'profileDir' | 'restart'>;
53
- /** Page that needs auth (used for messaging + Variant B navigation hint). */
54
26
  url: string;
55
27
  variant?: ReauthVariant;
56
28
  signal?: AbortSignal;
57
29
  executablePath?: string;
58
30
  chromeArgs?: string[];
59
- /** Plain-variant launcher (no CDP). Defaults to launchSetupBrowser. */
60
31
  launchPlain?: (options: {
61
32
  userDataDir: string;
62
33
  profileDirectory?: string;
@@ -64,21 +35,9 @@ export interface ReauthOptions {
64
35
  chromeArgs?: string[];
65
36
  signal?: AbortSignal;
66
37
  }) => Promise<number | null>;
67
- /** Restart the backend headed/headless (Variant B + resume). */
68
38
  restartBackend?: (headed: boolean) => Promise<unknown>;
69
39
  events?: SetupFlowEvents;
70
40
  }
71
- /**
72
- * Run reauthentication against a stopped backend: the caller must have
73
- * already shut the headless backend down (or pass a backend whose
74
- * restartBackend handles it). Variant B restarts headed with CDP and leaves
75
- * the headed backend running for the handoff; Variant A opens a plain window
76
- * and waits for close. Returns the user-facing instruction to relay.
77
- */
78
41
  export declare function runReauth(options: ReauthOptions): Promise<string>;
79
- /**
80
- * Resume headless automation after reauth: restart the backend headless.
81
- * Returns true when the backend reports running afterwards.
82
- */
83
42
  export declare function resumeHeadless(backend: PersistentBackend): Promise<boolean>;
84
43
  //# sourceMappingURL=setup-flow.d.ts.map
@@ -1,23 +1,3 @@
1
- /**
2
- * Persistent bootstrap and reauthentication flows (spec sections 3 and 7).
3
- *
4
- * Bootstrap (first run): launch a real headed Chrome on the Pi profile with
5
- * no MCP/Puppeteer/CDP — an ordinary manually launched browser. The user
6
- * signs into Chrome/Google and any sites, completes 2FA/passkeys/SSO, then
7
- * closes the window. Close means SETUP_HEADFUL → READY; it initializes the
8
- * *profile*, it does not prove every site is authenticated (auth stays
9
- * site-specific, verified by skill-level verifiers).
10
- *
11
- * Reauthentication: when a skill raises BrowserAuthRequired, shut the
12
- * headless backend down cleanly, then reauth in one of two variants:
13
- * - Variant B (default): Pi-owned headed Chrome *with* CDP, so Pi can
14
- * navigate to the login page before handing control to the user.
15
- * - Variant A (fallback): plain headed Chrome with no MCP/CDP, for login
16
- * providers that reject instrumented browsers.
17
- * Afterwards the backend restarts headless and automation resumes.
18
- *
19
- * Launchers are injectable so the orchestration is unit-testable.
20
- */
21
1
  import { launchSetupBrowser } from './chrome-launcher.js';
22
2
  import { ensureNamedProfile, PI_PROFILE_NAME } from './named-profile.js';
23
3
  import { markBootstrapped } from './persistent-store.js';
@@ -38,16 +18,10 @@ export function reauthInstructions(url, variant) {
38
18
  'When you are done, close the window (plain variant) or tell the agent to continue.',
39
19
  ].join('\n');
40
20
  }
41
- /**
42
- * First-run bootstrap. Holds no locks itself — the caller (browser_setup
43
- * tool) must ensure no backend is running on the profile. Resolves with the
44
- * window exit code after marking the profile initialized.
45
- */
46
21
  export async function runBootstrap(options, events) {
47
22
  options.signal?.throwIfAborted();
48
23
  const profileDir = options.profileDir ?? DEFAULT_PROFILE_DIR;
49
24
  events?.onSetupNeeded?.(SETUP_INSTRUCTIONS);
50
- // Same named identity automation uses: sign in here, automate there.
51
25
  ensureNamedProfile(profileDir);
52
26
  const launch = options.launch ?? launchSetupBrowser;
53
27
  const code = await launch({
@@ -64,13 +38,6 @@ export async function runBootstrap(options, events) {
64
38
  events?.onSetupComplete?.(profileDir);
65
39
  return code;
66
40
  }
67
- /**
68
- * Run reauthentication against a stopped backend: the caller must have
69
- * already shut the headless backend down (or pass a backend whose
70
- * restartBackend handles it). Variant B restarts headed with CDP and leaves
71
- * the headed backend running for the handoff; Variant A opens a plain window
72
- * and waits for close. Returns the user-facing instruction to relay.
73
- */
74
41
  export async function runReauth(options) {
75
42
  options.signal?.throwIfAborted();
76
43
  const variant = options.variant ?? 'instrumented';
@@ -102,10 +69,6 @@ export async function runReauth(options) {
102
69
  options.events?.onReauthComplete?.(profileDir);
103
70
  return message;
104
71
  }
105
- /**
106
- * Resume headless automation after reauth: restart the backend headless.
107
- * Returns true when the backend reports running afterwards.
108
- */
109
72
  export async function resumeHeadless(backend) {
110
73
  await backend.restart(false);
111
74
  return backend.running();
@@ -1,20 +1,3 @@
1
- /**
2
- * Shared-backend registry + per-agent page ownership (multi-agent support).
3
- *
4
- * Many Pi agents share one Pi-owned Chrome: the first backend writes
5
- * `<profile>.backend.json` ({ pid, browserUrl, sessionId, startedAt });
6
- * latecomers attach to that browserUrl instead of launching a second Chrome
7
- * (the profile lock still forbids two processes — sharing is by attach).
8
- *
9
- * Tab separation is by ownership, not visuals: every page a Pi session opens
10
- * (or Pi-navigates, in shared modes) is recorded in `<profile>.pages.json`
11
- * with its owner session. Entries whose owner pid is dead are pruned
12
- * lazily. The close guard refuses pages owned by another *live* session.
13
- *
14
- * Page-id routing (upstream `experimentalPageIdRouting`) plus explicit
15
- * pageIds on every call keeps agents from driving each other's tabs; this
16
- * registry adds the missing pieces: discovery, ownership, and cleanup scope.
17
- */
18
1
  import type { McpPageEntry } from './existing-flow.js';
19
2
  export interface BackendAdvert {
20
3
  pid: number;
@@ -24,13 +7,9 @@ export interface BackendAdvert {
24
7
  startedAt: string;
25
8
  }
26
9
  export declare function backendAdvertPathFor(profileDir: string): string;
27
- /** This process's agent-session identity (one per extension load). */
28
10
  export declare function newSessionId(): string;
29
- /** Publish this backend for peer agents. Overwrites stale entries. */
30
11
  export declare function advertiseBackend(profileDir: string, advert: BackendAdvert): void;
31
- /** Read a live peer advert, or undefined (absent/corrupt/dead owner). */
32
12
  export declare function readLiveAdvert(profileDir: string): BackendAdvert | undefined;
33
- /** Withdraw our advert (backend shutdown). Best effort. */
34
13
  export declare function withdrawAdvert(profileDir: string, sessionId: string): void;
35
14
  export interface PageOwner {
36
15
  sessionId: string;
@@ -44,16 +23,12 @@ export interface OwnedPage {
44
23
  openedAt: string;
45
24
  }
46
25
  export declare function pageRegistryPathFor(profileDir: string): string;
47
- /** Drop entries whose owner process is gone. */
48
26
  export declare function pruneDeadOwners(profileDir: string): OwnedPage[];
49
- /** Claim a page for a session (open or Pi-navigate). */
50
27
  export declare function claimPage(profileDir: string, page: McpPageEntry, owner: PageOwner, at?: string): void;
51
- /** Release one page (closed) or all of a session's pages (shutdown). */
52
28
  export declare function releasePages(profileDir: string, filter: {
53
29
  pageId?: number;
54
30
  sessionId?: string;
55
31
  }): void;
56
- /** Live sessions currently holding pages (peers to avoid disturbing). */
57
32
  export declare function livePeerSessions(profileDir: string): PageOwner[];
58
33
  export type CloseVerdict = {
59
34
  ok: true;
@@ -61,10 +36,5 @@ export type CloseVerdict = {
61
36
  ok: false;
62
37
  reason: string;
63
38
  };
64
- /**
65
- * Shared-mode close verdict: our own or unclaimed pages may close; pages
66
- * owned by another live session need explicit force. Stale ids fail safe
67
- * with a re-list hint (ids shift when tabs close).
68
- */
69
39
  export declare function checkSharedCloseAllowed(entries: McpPageEntry[], pageId: number, profileDir: string, self: PageOwner, ownedUrls: ReadonlySet<string>): CloseVerdict;
70
40
  //# sourceMappingURL=shared-backend.d.ts.map
@@ -1,20 +1,3 @@
1
- /**
2
- * Shared-backend registry + per-agent page ownership (multi-agent support).
3
- *
4
- * Many Pi agents share one Pi-owned Chrome: the first backend writes
5
- * `<profile>.backend.json` ({ pid, browserUrl, sessionId, startedAt });
6
- * latecomers attach to that browserUrl instead of launching a second Chrome
7
- * (the profile lock still forbids two processes — sharing is by attach).
8
- *
9
- * Tab separation is by ownership, not visuals: every page a Pi session opens
10
- * (or Pi-navigates, in shared modes) is recorded in `<profile>.pages.json`
11
- * with its owner session. Entries whose owner pid is dead are pruned
12
- * lazily. The close guard refuses pages owned by another *live* session.
13
- *
14
- * Page-id routing (upstream `experimentalPageIdRouting`) plus explicit
15
- * pageIds on every call keeps agents from driving each other's tabs; this
16
- * registry adds the missing pieces: discovery, ownership, and cleanup scope.
17
- */
18
1
  import { readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
19
2
  import { dirname } from 'node:path';
20
3
  import { randomUUID } from 'node:crypto';
@@ -30,16 +13,13 @@ function isPidAlive(pid) {
30
13
  return false;
31
14
  }
32
15
  }
33
- /** This process's agent-session identity (one per extension load). */
34
16
  export function newSessionId() {
35
17
  return randomUUID();
36
18
  }
37
- /** Publish this backend for peer agents. Overwrites stale entries. */
38
19
  export function advertiseBackend(profileDir, advert) {
39
20
  mkdirSync(dirname(backendAdvertPathFor(profileDir)), { recursive: true });
40
21
  writeFileSync(backendAdvertPathFor(profileDir), `${JSON.stringify(advert, null, 2)}\n`, 'utf8');
41
22
  }
42
- /** Read a live peer advert, or undefined (absent/corrupt/dead owner). */
43
23
  export function readLiveAdvert(profileDir) {
44
24
  try {
45
25
  const advert = JSON.parse(readFileSync(backendAdvertPathFor(profileDir), 'utf8'));
@@ -54,7 +34,6 @@ export function readLiveAdvert(profileDir) {
54
34
  return undefined;
55
35
  }
56
36
  }
57
- /** Withdraw our advert (backend shutdown). Best effort. */
58
37
  export function withdrawAdvert(profileDir, sessionId) {
59
38
  try {
60
39
  const raw = JSON.parse(readFileSync(backendAdvertPathFor(profileDir), 'utf8'));
@@ -62,7 +41,6 @@ export function withdrawAdvert(profileDir, sessionId) {
62
41
  rmSync(backendAdvertPathFor(profileDir), { force: true });
63
42
  }
64
43
  catch {
65
- // Another backend already replaced it; leave it alone.
66
44
  }
67
45
  }
68
46
  export function pageRegistryPathFor(profileDir) {
@@ -87,19 +65,16 @@ function writeRegistry(profileDir, pages) {
87
65
  mkdirSync(dirname(pageRegistryPathFor(profileDir)), { recursive: true });
88
66
  writeFileSync(pageRegistryPathFor(profileDir), `${JSON.stringify(pages, null, 2)}\n`, 'utf8');
89
67
  }
90
- /** Drop entries whose owner process is gone. */
91
68
  export function pruneDeadOwners(profileDir) {
92
69
  const live = readRegistry(profileDir).filter((entry) => isPidAlive(entry.owner.pid));
93
70
  writeRegistry(profileDir, live);
94
71
  return live;
95
72
  }
96
- /** Claim a page for a session (open or Pi-navigate). */
97
73
  export function claimPage(profileDir, page, owner, at = new Date().toISOString()) {
98
74
  const pages = pruneDeadOwners(profileDir).filter((entry) => entry.pageId !== page.pageId);
99
75
  pages.push({ pageId: page.pageId, url: page.url, title: page.title, owner, openedAt: at });
100
76
  writeRegistry(profileDir, pages);
101
77
  }
102
- /** Release one page (closed) or all of a session's pages (shutdown). */
103
78
  export function releasePages(profileDir, filter) {
104
79
  const pages = pruneDeadOwners(profileDir).filter((entry) => {
105
80
  if (filter.pageId !== undefined && entry.pageId === filter.pageId)
@@ -110,7 +85,6 @@ export function releasePages(profileDir, filter) {
110
85
  });
111
86
  writeRegistry(profileDir, pages);
112
87
  }
113
- /** Live sessions currently holding pages (peers to avoid disturbing). */
114
88
  export function livePeerSessions(profileDir) {
115
89
  const seen = new Map();
116
90
  for (const entry of pruneDeadOwners(profileDir)) {
@@ -119,11 +93,6 @@ export function livePeerSessions(profileDir) {
119
93
  }
120
94
  return [...seen.values()];
121
95
  }
122
- /**
123
- * Shared-mode close verdict: our own or unclaimed pages may close; pages
124
- * owned by another live session need explicit force. Stale ids fail safe
125
- * with a re-list hint (ids shift when tabs close).
126
- */
127
96
  export function checkSharedCloseAllowed(entries, pageId, profileDir, self, ownedUrls) {
128
97
  const target = entries.find((entry) => entry.pageId === pageId);
129
98
  if (!target) {
@@ -1,25 +1,3 @@
1
- /**
2
- * Existing-mode tab broker bridge (spec section 18).
3
- *
4
- * Pi cannot drive the extension directly, and raw MCP `new_page` calls would
5
- * create unmanaged foreground tabs — so tab creation goes through a tiny
6
- * loopback HTTP bridge the extension polls:
7
- *
8
- * ```text
9
- * Agent → BrowserSession.openPage(url) → ExistingSession
10
- * → POST /v1/request {url} → token
11
- * → extension polls GET /v1/pending → openPiTab(url) (inactive, grouped)
12
- * → extension POSTs /v1/complete {token, tabId}
13
- * → Pi waits for the token, then MCP selects the tab — never activating it
14
- * ```
15
- *
16
- * Correlation is by unique token, never "first tab whose URL matches"
17
- * (duplicate Gmail/GitHub tabs are common). Requests expire (default 2 min)
18
- * so a dead extension cannot leak queue entries. Only Pi-owned tabs are
19
- * tracked, so session-end cleanup never touches user tabs (spec 19).
20
- *
21
- * Security: binds 127.0.0.1 only and rejects non-loopback remotes.
22
- */
23
1
  export declare const DEFAULT_BRIDGE_PORT = 31973;
24
2
  export interface TabRequest {
25
3
  url: string;
@@ -35,9 +13,7 @@ export interface TabCompletion {
35
13
  }
36
14
  export interface TabBridgeOptions {
37
15
  port?: number;
38
- /** Pending requests older than this are dropped. Default 2 minutes. */
39
16
  requestTtlMs?: number;
40
- /** Completed entries kept for late waiters. Default 5 minutes. */
41
17
  completionTtlMs?: number;
42
18
  }
43
19
  export declare class TabBridge {
@@ -53,15 +29,12 @@ export declare class TabBridge {
53
29
  baseUrl(): string;
54
30
  start(): Promise<string>;
55
31
  stop(): Promise<void>;
56
- /** Enqueue an open-tab request; returns the correlation token. */
57
32
  requestTab(url: string, token?: string): string;
58
- /** Wait for the extension to complete `token` (throws on timeout/error). */
59
33
  waitForTab(token: string, options?: {
60
34
  timeoutMs?: number;
61
35
  pollMs?: number;
62
36
  signal?: AbortSignal;
63
37
  }): Promise<TabCompletion>;
64
- /** Tab IDs Pi created and still owns (session-end cleanup scope). */
65
38
  ownedTabIds(): number[];
66
39
  pendingCount(): number;
67
40
  private sweep;
@@ -1,25 +1,3 @@
1
- /**
2
- * Existing-mode tab broker bridge (spec section 18).
3
- *
4
- * Pi cannot drive the extension directly, and raw MCP `new_page` calls would
5
- * create unmanaged foreground tabs — so tab creation goes through a tiny
6
- * loopback HTTP bridge the extension polls:
7
- *
8
- * ```text
9
- * Agent → BrowserSession.openPage(url) → ExistingSession
10
- * → POST /v1/request {url} → token
11
- * → extension polls GET /v1/pending → openPiTab(url) (inactive, grouped)
12
- * → extension POSTs /v1/complete {token, tabId}
13
- * → Pi waits for the token, then MCP selects the tab — never activating it
14
- * ```
15
- *
16
- * Correlation is by unique token, never "first tab whose URL matches"
17
- * (duplicate Gmail/GitHub tabs are common). Requests expire (default 2 min)
18
- * so a dead extension cannot leak queue entries. Only Pi-owned tabs are
19
- * tracked, so session-end cleanup never touches user tabs (spec 19).
20
- *
21
- * Security: binds 127.0.0.1 only and rejects non-loopback remotes.
22
- */
23
1
  import { createServer } from 'node:http';
24
2
  import { randomUUID } from 'node:crypto';
25
3
  export const DEFAULT_BRIDGE_PORT = 31973;
@@ -96,7 +74,6 @@ export class TabBridge {
96
74
  this.server = undefined;
97
75
  await new Promise((resolve) => server.close(() => resolve()));
98
76
  }
99
- /** Enqueue an open-tab request; returns the correlation token. */
100
77
  requestTab(url, token = randomUUID()) {
101
78
  if (typeof url !== 'string' || url.length === 0)
102
79
  throw new Error('Tab URL is required.');
@@ -104,15 +81,12 @@ export class TabBridge {
104
81
  this.pending.set(token, { url, token, createdAt: Date.now() });
105
82
  return token;
106
83
  }
107
- /** Wait for the extension to complete `token` (throws on timeout/error). */
108
84
  async waitForTab(token, options) {
109
85
  const timeoutMs = options?.timeoutMs ?? 30_000;
110
86
  const pollMs = options?.pollMs ?? 100;
111
87
  const deadline = Date.now() + timeoutMs;
112
88
  while (Date.now() < deadline) {
113
89
  if (options?.signal?.aborted) {
114
- // Do not leave a cancelled request for the extension to consume; it
115
- // would create an unmanaged tab after the caller has given up.
116
90
  this.pending.delete(token);
117
91
  throw new Error('Tab wait aborted.');
118
92
  }
@@ -128,7 +102,6 @@ export class TabBridge {
128
102
  this.pending.delete(token);
129
103
  throw new Error(`Timed out waiting for the extension to open the tab (token ${token}).`);
130
104
  }
131
- /** Tab IDs Pi created and still owns (session-end cleanup scope). */
132
105
  ownedTabIds() {
133
106
  return [...this.completed.values()]
134
107
  .filter((c) => c.tabId !== undefined && !c.error)
@@ -2,17 +2,8 @@ export declare function augmentToolDescription(prefixedName: string, description
2
2
  export declare function postProcessToolResult(originalName: string, text: string): string;
3
3
  export declare const OVERLAY_RECOVERABLE: Set<string>;
4
4
  export declare function looksOverlayBlocked(text: string): boolean;
5
- /**
6
- * Conservative login-wall detection: URL match, or at least two independent
7
- * content signals (a lone "Sign in" link on a homepage must not trigger).
8
- */
9
5
  export declare function looksLikeLoginWall(url: string | undefined, text: string): boolean;
10
6
  export type PageState = 'ok' | 'login-wall' | 'challenge';
11
- /**
12
- * Classify what kind of gate (if any) a page presents. Login walls need an
13
- * identity; challenges (bot checks) may clear on their own and never need
14
- * one — escalating identity for a challenge is wrong.
15
- */
16
7
  export declare function classifyPageState(url: string | undefined, text: string): PageState;
17
8
  export declare function extractTextContent(content: unknown): string;
18
9
  //# sourceMappingURL=tool-augment.d.ts.map
@@ -18,8 +18,6 @@ export function postProcessToolResult(originalName, text) {
18
18
  }
19
19
  return text;
20
20
  }
21
- // Click-family tools worth one automatic recovery attempt when an overlay
22
- // blocks them: dismiss with Escape, then retry once.
23
21
  export const OVERLAY_RECOVERABLE = new Set([
24
22
  'click',
25
23
  'click_at',
@@ -34,9 +32,6 @@ export function looksOverlayBlocked(text) {
34
32
  return /overlay|obscured|intercept|behind another element|not clickable|not visible/i.test(text);
35
33
  }
36
34
  const LOGIN_URL = /(^|\/)(login|log-in|signin|sign-in|auth|authenticate|challenge|verify|2fa|totp|sso)(\/|$|[?#])/i;
37
- // Identity-specific phrases only. Bot-check vocabulary (Turnstile, "just a
38
- // moment") belongs to CHALLENGE below — mixing them makes one interstitial
39
- // count twice and misclassify challenges as login walls.
40
35
  const LOGIN_CONTENT = [
41
36
  /log in to continue/i,
42
37
  /sign in to continue/i,
@@ -44,21 +39,12 @@ const LOGIN_CONTENT = [
44
39
  /2-step verification/i,
45
40
  /enter your password/i,
46
41
  ];
47
- /**
48
- * Conservative login-wall detection: URL match, or at least two independent
49
- * content signals (a lone "Sign in" link on a homepage must not trigger).
50
- */
51
42
  export function looksLikeLoginWall(url, text) {
52
43
  if (url && LOGIN_URL.test(url))
53
44
  return true;
54
45
  return LOGIN_CONTENT.filter((pattern) => pattern.test(text)).length >= 2;
55
46
  }
56
47
  const CHALLENGE = /just a moment|verifying you are human|cf-turnstile|attention required|security check|prove you are human/i;
57
- /**
58
- * Classify what kind of gate (if any) a page presents. Login walls need an
59
- * identity; challenges (bot checks) may clear on their own and never need
60
- * one — escalating identity for a challenge is wrong.
61
- */
62
48
  export function classifyPageState(url, text) {
63
49
  if (looksLikeLoginWall(url, text))
64
50
  return 'login-wall';
package/dist/vision.d.ts CHANGED
@@ -1,14 +1,3 @@
1
- /**
2
- * Optional vision-model integration.
3
- *
4
- * Captures a screenshot via the upstream take_screenshot tool and sends it
5
- * to a vision model from Pi's model registry. This lets the agent identify
6
- * elements by visual attributes (color, layout, coordinates) when the
7
- * accessibility tree is insufficient, e.g. canvas/WebGL scenes.
8
- *
9
- * The pi-ai host module is loaded lazily so the extension runs on hosts
10
- * without it; only the vision tool requires it.
11
- */
12
1
  export interface VisionModelConfig {
13
2
  provider: string;
14
3
  model: string;
@@ -29,12 +18,6 @@ export declare function createRegistryVisionCaller(visionConfig: VisionModelConf
29
18
  interface BrowserClient {
30
19
  callTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
31
20
  }
32
- /**
33
- * Capture a screenshot through the browser client and analyze it with the
34
- * provided vision caller. Returns MCP-style content, never throws except on
35
- * abort: failures degrade to actionable error text pointing at the
36
- * accessibility tree instead.
37
- */
38
21
  export declare function handleAnalyzeScreenshot(client: BrowserClient, callVision: (instruction: string, imageBase64: string, mimeType: string, signal?: AbortSignal) => Promise<string>, args: {
39
22
  instruction?: string;
40
23
  pageId?: number;
package/dist/vision.js CHANGED
@@ -1,14 +1,3 @@
1
- /**
2
- * Optional vision-model integration.
3
- *
4
- * Captures a screenshot via the upstream take_screenshot tool and sends it
5
- * to a vision model from Pi's model registry. This lets the agent identify
6
- * elements by visual attributes (color, layout, coordinates) when the
7
- * accessibility tree is insufficient, e.g. canvas/WebGL scenes.
8
- *
9
- * The pi-ai host module is loaded lazily so the extension runs on hosts
10
- * without it; only the vision tool requires it.
11
- */
12
1
  export const VISUAL_SYSTEM_PROMPT = `You are a visual analysis assistant for browser automation. You receive a screenshot of a web page and an instruction.
13
2
 
14
3
  COORDINATES:
@@ -70,12 +59,6 @@ export function createRegistryVisionCaller(visionConfig, registry) {
70
59
  .join('');
71
60
  };
72
61
  }
73
- /**
74
- * Capture a screenshot through the browser client and analyze it with the
75
- * provided vision caller. Returns MCP-style content, never throws except on
76
- * abort: failures degrade to actionable error text pointing at the
77
- * accessibility tree instead.
78
- */
79
62
  export async function handleAnalyzeScreenshot(client, callVision, args, signal) {
80
63
  const instruction = String(args.instruction ?? '');
81
64
  const screenshotArgs = typeof args.pageId === 'number' ? { pageId: args.pageId } : {};
@@ -41,6 +41,8 @@ Budgets live in [`performance-budgets.json`](../performance-budgets.json). They
41
41
  | Published map files | 0 |
42
42
  | Production dependency closure | 140 packages |
43
43
 
44
+ The published artifact stays byte-lean by design: `tsconfig.json` sets `removeComments` so emitted JavaScript and declarations carry no comment bytes, and build maps never ship. Source commentary lives in the repository, not in the installed package.
45
+
44
46
  ## Reproduce locally and in CI
45
47
 
46
48
  ```sh