pi-browser-use 0.11.0 → 0.11.2
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/dist/annotate.d.ts +0 -14
- package/dist/annotate.js +0 -14
- package/dist/artifacts.d.ts +0 -2
- package/dist/artifacts.js +0 -2
- package/dist/auth-verifiers.d.ts +0 -29
- package/dist/auth-verifiers.js +0 -31
- package/dist/chrome-launcher.d.ts +0 -59
- package/dist/chrome-launcher.js +0 -54
- package/dist/client.d.ts +0 -4
- package/dist/client.js +0 -17
- package/dist/config.d.ts +0 -25
- package/dist/config.js +0 -23
- package/dist/doctor.d.ts +0 -8
- package/dist/doctor.js +0 -5
- package/dist/existing-flow.d.ts +0 -31
- package/dist/existing-flow.js +0 -29
- package/dist/focus-policy.d.ts +0 -13
- package/dist/focus-policy.js +0 -13
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mcp-server.d.ts +0 -2
- package/dist/mcp-server.js +0 -10
- package/dist/named-profile.d.ts +0 -36
- package/dist/named-profile.js +0 -37
- package/dist/persistent-backend.d.ts +2 -56
- package/dist/persistent-backend.js +30 -65
- package/dist/persistent-store.d.ts +0 -24
- package/dist/persistent-store.js +0 -24
- package/dist/profile-lock.d.ts +0 -21
- package/dist/profile-lock.js +0 -27
- package/dist/profile.d.ts +0 -10
- package/dist/profile.js +0 -14
- package/dist/runtime.d.ts +0 -3
- package/dist/runtime.js +3 -92
- package/dist/session-manager.d.ts +0 -48
- package/dist/session-manager.js +0 -46
- package/dist/session.d.ts +0 -42
- package/dist/session.js +0 -18
- package/dist/settings.d.ts +0 -5
- package/dist/settings.js +0 -5
- package/dist/setup-flow.d.ts +0 -41
- package/dist/setup-flow.js +0 -37
- package/dist/shared-backend.d.ts +0 -30
- package/dist/shared-backend.js +0 -31
- package/dist/tab-bridge.d.ts +0 -27
- package/dist/tab-bridge.js +0 -27
- package/dist/tool-augment.d.ts +0 -9
- package/dist/tool-augment.js +0 -14
- package/dist/vision.d.ts +0 -17
- package/dist/vision.js +0 -17
- package/docs/performance.md +2 -0
- package/extension/README.md +2 -0
- package/extension/background.js +4 -9
- package/package.json +2 -2
- 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;
|
package/dist/settings.d.ts
CHANGED
|
@@ -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();
|
package/dist/setup-flow.d.ts
CHANGED
|
@@ -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
|
package/dist/setup-flow.js
CHANGED
|
@@ -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();
|
package/dist/shared-backend.d.ts
CHANGED
|
@@ -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
|
package/dist/shared-backend.js
CHANGED
|
@@ -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) {
|
package/dist/tab-bridge.d.ts
CHANGED
|
@@ -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;
|
package/dist/tab-bridge.js
CHANGED
|
@@ -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)
|
package/dist/tool-augment.d.ts
CHANGED
|
@@ -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
|
package/dist/tool-augment.js
CHANGED
|
@@ -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 } : {};
|
package/docs/performance.md
CHANGED
|
@@ -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
|