@trusty-squire/mcp 1.1.13 → 1.1.14-rc.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/README.md +19 -11
- package/dist/bot/browser-process-owner.d.ts +75 -0
- package/dist/bot/browser-process-owner.d.ts.map +1 -0
- package/dist/bot/browser-process-owner.js +931 -0
- package/dist/bot/browser-process-owner.js.map +1 -0
- package/dist/bot/browser-process-runtime.d.ts +159 -0
- package/dist/bot/browser-process-runtime.d.ts.map +1 -0
- package/dist/bot/browser-process-runtime.js +905 -0
- package/dist/bot/browser-process-runtime.js.map +1 -0
- package/dist/bot/browser.d.ts +45 -194
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +666 -2358
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/compact-observation-v2.d.ts +9 -0
- package/dist/bot/compact-observation-v2.d.ts.map +1 -1
- package/dist/bot/compact-observation-v2.js +141 -82
- package/dist/bot/compact-observation-v2.js.map +1 -1
- package/dist/bot/identity-runtime.d.ts +56 -0
- package/dist/bot/identity-runtime.d.ts.map +1 -0
- package/dist/bot/identity-runtime.js +141 -0
- package/dist/bot/identity-runtime.js.map +1 -0
- package/dist/bot/owned-pages.d.ts +16 -0
- package/dist/bot/owned-pages.d.ts.map +1 -0
- package/dist/bot/owned-pages.js +58 -0
- package/dist/bot/owned-pages.js.map +1 -0
- package/dist/bot/page-driver.d.ts +35 -0
- package/dist/bot/page-driver.d.ts.map +1 -0
- package/dist/bot/page-driver.js +302 -0
- package/dist/bot/page-driver.js.map +1 -0
- package/dist/bot/provision-session.d.ts +4 -0
- package/dist/bot/provision-session.d.ts.map +1 -1
- package/dist/bot/provision-session.js +122 -29
- package/dist/bot/provision-session.js.map +1 -1
- package/dist/bot/session/lifecycle.d.ts.map +1 -1
- package/dist/bot/session/lifecycle.js +218 -14
- package/dist/bot/session/lifecycle.js.map +1 -1
- package/dist/bot/session/multisession-flag.d.ts +2 -0
- package/dist/bot/session/multisession-flag.d.ts.map +1 -0
- package/dist/bot/session/multisession-flag.js +23 -0
- package/dist/bot/session/multisession-flag.js.map +1 -0
- package/dist/tools/provision-drive.d.ts +12 -12
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Step 3 of the multi-session browser broker migration: a Chrome lifetime
|
|
2
|
+
// that outlives any single session's reference to it. Production still admits
|
|
3
|
+
// exactly one session at a time (the exclusive profile lease in
|
|
4
|
+
// session/lifecycle.ts is unchanged) — this only separates "who owns Chrome's
|
|
5
|
+
// process lifetime" from "who is currently borrowing its tabs" so a later PR
|
|
6
|
+
// can attach a second session without re-deriving that boundary.
|
|
7
|
+
//
|
|
8
|
+
// Generic over the launched handle (BrowserController in production, a fake
|
|
9
|
+
// in tests) so this file never has to construct a real browser to be tested.
|
|
10
|
+
export class IncompatibleIdentityRuntimeSettingsError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "IncompatibleIdentityRuntimeSettingsError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function defaultSettingsCompatible(live, requested) {
|
|
17
|
+
return JSON.stringify(live) === JSON.stringify(requested);
|
|
18
|
+
}
|
|
19
|
+
/** Owns one Chrome for one identity/profile. Launch is single-flight; tab
|
|
20
|
+
* acquisition and Chrome shutdown are deliberately separate operations so
|
|
21
|
+
* releasing a session never has to mean tearing the browser down. */
|
|
22
|
+
export class IdentityRuntime {
|
|
23
|
+
handle = null;
|
|
24
|
+
settings = null;
|
|
25
|
+
pendingSettings = null;
|
|
26
|
+
epoch = 0;
|
|
27
|
+
launchPromise = null;
|
|
28
|
+
leaseCount = 0;
|
|
29
|
+
settingsCompatible;
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
this.settingsCompatible = opts.settingsCompatible ?? defaultSettingsCompatible;
|
|
32
|
+
}
|
|
33
|
+
currentEpoch() {
|
|
34
|
+
return this.epoch;
|
|
35
|
+
}
|
|
36
|
+
isLive() {
|
|
37
|
+
return this.handle !== null;
|
|
38
|
+
}
|
|
39
|
+
// True while some caller's launch() is in flight (single-flighted — see
|
|
40
|
+
// acquire()). Lets a caller that lost a synchronous race for the OS-level
|
|
41
|
+
// resource the launch itself protects (see session/lifecycle.ts's
|
|
42
|
+
// experimental-multisession fallback) tell "this identity is being (or
|
|
43
|
+
// already was) launched IN THIS PROCESS" from "busy for an unrelated
|
|
44
|
+
// reason" before deciding to join it.
|
|
45
|
+
isLaunching() {
|
|
46
|
+
return this.launchPromise !== null;
|
|
47
|
+
}
|
|
48
|
+
activeLeaseCount() {
|
|
49
|
+
return this.leaseCount;
|
|
50
|
+
}
|
|
51
|
+
// Detects a stale reference: true once the runtime has moved past the epoch
|
|
52
|
+
// a caller was handed at acquire time (a relaunch happened underneath it).
|
|
53
|
+
isEpochStale(epoch) {
|
|
54
|
+
return epoch !== this.epoch;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Acquires this identity's Chrome, launching it at most once for any number
|
|
58
|
+
* of concurrent callers. `launch` runs only for the caller that wins the
|
|
59
|
+
* single-flight race; every other concurrent caller (and every caller while
|
|
60
|
+
* a compatible instance is already live) awaits that one launch or reuses
|
|
61
|
+
* the live handle without relaunching.
|
|
62
|
+
*
|
|
63
|
+
* A request whose settings differ from the currently live (or in-flight)
|
|
64
|
+
* launch is rejected rather than silently applied to the shared context —
|
|
65
|
+
* the caller must forgetAfterShutdown() a live runtime before requesting
|
|
66
|
+
* different settings.
|
|
67
|
+
*/
|
|
68
|
+
async acquire(settings, launch) {
|
|
69
|
+
const liveSettings = this.settings ?? this.pendingSettings;
|
|
70
|
+
if (liveSettings !== null && !this.settingsCompatible(liveSettings, settings)) {
|
|
71
|
+
throw new IncompatibleIdentityRuntimeSettingsError("IdentityRuntime.acquire: requested settings are incompatible with the live/in-flight " +
|
|
72
|
+
"identity. Refusing to mutate a shared browser context — call forgetAfterShutdown() " +
|
|
73
|
+
"after closing it, then acquire() again for a fresh identity.");
|
|
74
|
+
}
|
|
75
|
+
if (this.handle !== null) {
|
|
76
|
+
this.leaseCount += 1;
|
|
77
|
+
return {
|
|
78
|
+
handle: this.handle,
|
|
79
|
+
epoch: this.epoch,
|
|
80
|
+
reused: true,
|
|
81
|
+
releaseTabs: this.makeReleaseTabs(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (this.launchPromise === null) {
|
|
85
|
+
this.pendingSettings = settings;
|
|
86
|
+
this.launchPromise = launch(settings).finally(() => {
|
|
87
|
+
this.pendingSettings = null;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
let handle;
|
|
91
|
+
try {
|
|
92
|
+
handle = await this.launchPromise;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.launchPromise = null;
|
|
96
|
+
}
|
|
97
|
+
// Only the first continuation to resume after the shared launch settles
|
|
98
|
+
// records it as the live identity — every other concurrent waiter sees
|
|
99
|
+
// `this.handle !== null` below and joins as a reuse rather than
|
|
100
|
+
// double-counting the same launch as a second (re)launch.
|
|
101
|
+
const wonLaunch = this.handle === null;
|
|
102
|
+
if (wonLaunch) {
|
|
103
|
+
this.handle = handle;
|
|
104
|
+
this.settings = settings;
|
|
105
|
+
this.epoch += 1;
|
|
106
|
+
}
|
|
107
|
+
const resolvedHandle = this.handle;
|
|
108
|
+
if (resolvedHandle === null) {
|
|
109
|
+
throw new Error("IdentityRuntime: invariant violated — no live handle after acquire");
|
|
110
|
+
}
|
|
111
|
+
this.leaseCount += 1;
|
|
112
|
+
return {
|
|
113
|
+
handle: resolvedHandle,
|
|
114
|
+
epoch: this.epoch,
|
|
115
|
+
reused: !wonLaunch,
|
|
116
|
+
releaseTabs: this.makeReleaseTabs(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
makeReleaseTabs() {
|
|
120
|
+
let released = false;
|
|
121
|
+
return () => {
|
|
122
|
+
if (released)
|
|
123
|
+
return;
|
|
124
|
+
released = true;
|
|
125
|
+
this.leaseCount = Math.max(0, this.leaseCount - 1);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Forgets the current identity so the next acquire() launches fresh. Does
|
|
130
|
+
* NOT close Chrome — the caller closes the handle itself (its close()
|
|
131
|
+
* semantics, timeouts, and force-close fallbacks stay exactly where they
|
|
132
|
+
* are today) and calls this afterward, in a `finally`, regardless of
|
|
133
|
+
* outcome. Safe to call when nothing is live.
|
|
134
|
+
*/
|
|
135
|
+
forgetAfterShutdown() {
|
|
136
|
+
this.handle = null;
|
|
137
|
+
this.settings = null;
|
|
138
|
+
this.leaseCount = 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=identity-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-runtime.js","sourceRoot":"","sources":["../../src/bot/identity-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
/** Session-local page ownership; context membership is never ownership proof. */
|
|
3
|
+
export declare class OwnedPages {
|
|
4
|
+
private readonly onPopup;
|
|
5
|
+
private readonly owner;
|
|
6
|
+
private readonly pages;
|
|
7
|
+
constructor(onPopup: (page: Page) => void);
|
|
8
|
+
/** Only call for a known primary/recovery page, or a proven popup below. */
|
|
9
|
+
register(page: Page): void;
|
|
10
|
+
has(page: Page): boolean;
|
|
11
|
+
/** True only when a DIFFERENT registry has positively claimed the page. */
|
|
12
|
+
claimedByAnother(page: Page): boolean;
|
|
13
|
+
live(): Page[];
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=owned-pages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owned-pages.d.ts","sourceRoot":"","sources":["../../src/bot/owned-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMvC,iFAAiF;AACjF,qBAAa,UAAU;IAIT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;gBAExB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI;IAE1D,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IA0B1B,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAIxB,2EAA2E;IAC3E,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAKrC,IAAI,IAAI,IAAI,EAAE;IAId,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Claims survive registry disposal while the Page is reachable: a different
|
|
2
|
+
// controller must never acquire an old session's page by registering it again.
|
|
3
|
+
const owners = new WeakMap();
|
|
4
|
+
/** Session-local page ownership; context membership is never ownership proof. */
|
|
5
|
+
export class OwnedPages {
|
|
6
|
+
onPopup;
|
|
7
|
+
owner = Symbol("browser-session");
|
|
8
|
+
pages = new Map();
|
|
9
|
+
constructor(onPopup) {
|
|
10
|
+
this.onPopup = onPopup;
|
|
11
|
+
}
|
|
12
|
+
/** Only call for a known primary/recovery page, or a proven popup below. */
|
|
13
|
+
register(page) {
|
|
14
|
+
const owner = owners.get(page);
|
|
15
|
+
if (owner !== undefined && owner !== this.owner) {
|
|
16
|
+
throw new Error("Browser page already belongs to another session");
|
|
17
|
+
}
|
|
18
|
+
if (page.isClosed() || this.pages.has(page))
|
|
19
|
+
return;
|
|
20
|
+
owners.set(page, this.owner);
|
|
21
|
+
// Playwright emits this on the opener using lineage captured when the
|
|
22
|
+
// target was created. Do not resolve page.opener() later: it can disappear
|
|
23
|
+
// when the opener closes. No event/proven opener means no assignment.
|
|
24
|
+
const popup = (child) => {
|
|
25
|
+
if (!this.has(page) || child.context() !== page.context())
|
|
26
|
+
return;
|
|
27
|
+
if (owners.has(child))
|
|
28
|
+
return; // duplicate/foreign attribution fails closed
|
|
29
|
+
this.register(child);
|
|
30
|
+
if (this.has(child))
|
|
31
|
+
this.onPopup(child);
|
|
32
|
+
};
|
|
33
|
+
const dispose = () => {
|
|
34
|
+
page.off("popup", popup);
|
|
35
|
+
page.off("close", dispose);
|
|
36
|
+
this.pages.delete(page);
|
|
37
|
+
};
|
|
38
|
+
this.pages.set(page, dispose);
|
|
39
|
+
page.on("popup", popup);
|
|
40
|
+
page.on("close", dispose);
|
|
41
|
+
}
|
|
42
|
+
has(page) {
|
|
43
|
+
return this.pages.has(page) && !page.isClosed();
|
|
44
|
+
}
|
|
45
|
+
/** True only when a DIFFERENT registry has positively claimed the page. */
|
|
46
|
+
claimedByAnother(page) {
|
|
47
|
+
const owner = owners.get(page);
|
|
48
|
+
return owner !== undefined && owner !== this.owner;
|
|
49
|
+
}
|
|
50
|
+
live() {
|
|
51
|
+
return [...this.pages.keys()].filter((page) => this.has(page));
|
|
52
|
+
}
|
|
53
|
+
dispose() {
|
|
54
|
+
for (const dispose of this.pages.values())
|
|
55
|
+
dispose();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=owned-pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owned-pages.js","sourceRoot":"","sources":["../../src/bot/owned-pages.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM,MAAM,GAAG,IAAI,OAAO,EAAgB,CAAC;AAE3C,iFAAiF;AACjF,MAAM,OAAO,UAAU;IAIQ;IAHZ,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAClC,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAErD,YAA6B,OAA6B;QAA7B,YAAO,GAAP,OAAO,CAAsB;IAAG,CAAC;IAE9D,4EAA4E;IAC5E,QAAQ,CAAC,IAAU;QACjB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACpD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,sEAAsE;QACtE,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,KAAK,GAAG,CAAC,KAAW,EAAQ,EAAE;YAClC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,OAAO,EAAE;gBAAE,OAAO;YAClE,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,6CAA6C;YAC5E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,GAAG,CAAC,IAAU;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClD,CAAC;IAED,2EAA2E;IAC3E,gBAAgB,CAAC,IAAU;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC;IACrD,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO;QACL,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright";
|
|
2
|
+
import { OwnedPages } from "./owned-pages.js";
|
|
3
|
+
/** One session's page lifetime; never launches or terminates a browser process. */
|
|
4
|
+
export declare class PageDriver {
|
|
5
|
+
private readonly getContext;
|
|
6
|
+
private readonly humanize;
|
|
7
|
+
page: Page | null;
|
|
8
|
+
primaryPage: Page | null;
|
|
9
|
+
private openedTabs;
|
|
10
|
+
readonly ownedPages: OwnedPages;
|
|
11
|
+
private readonly documentSubscriptions;
|
|
12
|
+
private mainDocumentSequence;
|
|
13
|
+
private readonly mainDocumentIdentities;
|
|
14
|
+
private readonly trackedMainDocumentPages;
|
|
15
|
+
harnessAttachedPage: boolean;
|
|
16
|
+
oauthProductPage: Page | null;
|
|
17
|
+
oauthProviderPage: Page | null;
|
|
18
|
+
oauthProviderPageClosed: boolean;
|
|
19
|
+
constructor(getContext: () => BrowserContext | null, humanize: boolean);
|
|
20
|
+
private get context();
|
|
21
|
+
private sleep;
|
|
22
|
+
trackMainDocument(page: Page): void;
|
|
23
|
+
mainDocumentIdentity(): string;
|
|
24
|
+
trackOpenedTabs(page: Page): void;
|
|
25
|
+
currentUrl(): string;
|
|
26
|
+
recoverActivePage(): boolean;
|
|
27
|
+
armOpenedTabAdoption(): void;
|
|
28
|
+
adoptOpenedTab(graceMs?: number): Promise<string | null>;
|
|
29
|
+
private takeFollowableTab;
|
|
30
|
+
adoptLivePage(): boolean;
|
|
31
|
+
goto(url: string): Promise<void>;
|
|
32
|
+
reload(): Promise<void>;
|
|
33
|
+
disposeRegistrations(): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=page-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-driver.d.ts","sourceRoot":"","sources":["../../src/bot/page-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,mFAAmF;AACnF,qBAAa,UAAU;IA8CnB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IA9C3B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAQ;IAMzB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAQhC,OAAO,CAAC,UAAU,CAAc;IAEhC,QAAQ,CAAC,UAAU,aAIhB;IAEH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA+B;IAErE,OAAO,CAAC,oBAAoB,CAAK;IAEjC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA+B;IAEtE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuB;IAIhE,mBAAmB,UAAS;IAO5B,gBAAgB,EAAE,IAAI,GAAG,IAAI,CAAQ;IAErC,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEtC,uBAAuB,UAAS;gBAEb,UAAU,EAAE,MAAM,cAAc,GAAG,IAAI,EACvC,QAAQ,EAAE,OAAO;IAEpC,OAAO,KAAK,OAAO,GAElB;IACD,OAAO,CAAC,KAAK;IAIb,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IA4BnC,oBAAoB,IAAI,MAAM;IAS9B,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAOjC,UAAU,IAAI,MAAM;IAIpB,iBAAiB,IAAI,OAAO;IAS5B,oBAAoB,IAAI,IAAI;IAWtB,cAAc,CAAC,OAAO,SAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA8BzD,OAAO,CAAC,iBAAiB;IAiBzB,aAAa,IAAI,OAAO;IAmBlB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsGhC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,oBAAoB,IAAI,IAAI;CAK7B"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { OwnedPages } from "./owned-pages.js";
|
|
2
|
+
/** One session's page lifetime; never launches or terminates a browser process. */
|
|
3
|
+
export class PageDriver {
|
|
4
|
+
getContext;
|
|
5
|
+
humanize;
|
|
6
|
+
page = null;
|
|
7
|
+
// The page start() configured with the controller's navigation/captcha
|
|
8
|
+
// handlers. OAuth may temporarily switch `this.page` to a popup, but session
|
|
9
|
+
// reuse must always restore this original page rather than adopting a popup
|
|
10
|
+
// whose lifecycle handlers were never installed.
|
|
11
|
+
primaryPage = null;
|
|
12
|
+
// Tabs the PAGE opened (target=_blank / window.open) since the operator armed
|
|
13
|
+
// adoption for an action, oldest first. A real user lands on the tab their
|
|
14
|
+
// click opened — an email magic-link button in Gmail is the case this exists
|
|
15
|
+
// for — so the operator has to follow it too. Reading the link's href instead
|
|
16
|
+
// is not an option: a single-use login token is sealed and must never be
|
|
17
|
+
// handed to the model as text.
|
|
18
|
+
openedTabs = [];
|
|
19
|
+
ownedPages = new OwnedPages((page) => {
|
|
20
|
+
this.trackMainDocument(page);
|
|
21
|
+
this.openedTabs.push(page);
|
|
22
|
+
if (this.openedTabs.length > 8)
|
|
23
|
+
this.openedTabs.splice(0, this.openedTabs.length - 8);
|
|
24
|
+
});
|
|
25
|
+
documentSubscriptions = new Map();
|
|
26
|
+
mainDocumentSequence = 0;
|
|
27
|
+
mainDocumentIdentities = new WeakMap();
|
|
28
|
+
trackedMainDocumentPages = new WeakSet();
|
|
29
|
+
// The replay harness owns this context so it can route the storefront from a
|
|
30
|
+
// HAR, then remove that route before checkout becomes live.
|
|
31
|
+
harnessAttachedPage = false;
|
|
32
|
+
// T6/T7 — OAuth handshake bookkeeping. Legacy startOAuth() adopts a
|
|
33
|
+
// popup window as the active page, so keep the product tab parked here
|
|
34
|
+
// until settleAfterOAuth() restores it. The operator's oauth_login action
|
|
35
|
+
// keeps the observed product page active for the click and opens a recovery
|
|
36
|
+
// tab before the provider can redirect or close either OAuth transport.
|
|
37
|
+
oauthProductPage = null;
|
|
38
|
+
oauthProviderPage = null;
|
|
39
|
+
oauthProviderPageClosed = false;
|
|
40
|
+
constructor(getContext, humanize) {
|
|
41
|
+
this.getContext = getContext;
|
|
42
|
+
this.humanize = humanize;
|
|
43
|
+
}
|
|
44
|
+
get context() {
|
|
45
|
+
return this.getContext();
|
|
46
|
+
}
|
|
47
|
+
sleep(ms) {
|
|
48
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
}
|
|
50
|
+
trackMainDocument(page) {
|
|
51
|
+
if (page.isClosed() || this.trackedMainDocumentPages.has(page))
|
|
52
|
+
return;
|
|
53
|
+
this.trackedMainDocumentPages.add(page);
|
|
54
|
+
this.mainDocumentIdentities.set(page, ++this.mainDocumentSequence);
|
|
55
|
+
// A REPLACED main document advances the identity; a same-document History
|
|
56
|
+
// API navigation does not. Playwright emits `framenavigated` for both, so
|
|
57
|
+
// keying on it made every `history.replaceState` inside an SPA checkout
|
|
58
|
+
// retire every operator ref mid-form — the identity churned faster than a
|
|
59
|
+
// multi-field address block could be filled. `domcontentloaded` fires once
|
|
60
|
+
// per real main-frame document (playwright's client `Frame` gates it on
|
|
61
|
+
// `!this._parentFrame`), which is exactly the document-replacement signal.
|
|
62
|
+
// A same-document route change to a genuinely different logical page is
|
|
63
|
+
// still caught by the observation epoch's normalized origin+pathname fold
|
|
64
|
+
// (compactV2EpochDoc), which is the backstop this narrowing relies on.
|
|
65
|
+
const onDocument = () => {
|
|
66
|
+
this.mainDocumentIdentities.set(page, ++this.mainDocumentSequence);
|
|
67
|
+
};
|
|
68
|
+
const dispose = () => {
|
|
69
|
+
page.off("domcontentloaded", onDocument);
|
|
70
|
+
page.off("close", dispose);
|
|
71
|
+
this.documentSubscriptions.delete(page);
|
|
72
|
+
this.trackedMainDocumentPages.delete(page);
|
|
73
|
+
};
|
|
74
|
+
this.documentSubscriptions.set(page, dispose);
|
|
75
|
+
page.on("domcontentloaded", onDocument);
|
|
76
|
+
page.on("close", dispose);
|
|
77
|
+
}
|
|
78
|
+
mainDocumentIdentity() {
|
|
79
|
+
const page = this.page;
|
|
80
|
+
if (page === null)
|
|
81
|
+
return "none";
|
|
82
|
+
this.trackMainDocument(page);
|
|
83
|
+
return String(this.mainDocumentIdentities.get(page));
|
|
84
|
+
}
|
|
85
|
+
// Register only explicitly created primary/recovery pages. Popup enrollment
|
|
86
|
+
// follows their creation-time opener events, never context-wide page events.
|
|
87
|
+
trackOpenedTabs(page) {
|
|
88
|
+
this.ownedPages.register(page);
|
|
89
|
+
this.trackMainDocument(page);
|
|
90
|
+
}
|
|
91
|
+
// URL of the active page (the OAuth page mid-handshake, the product
|
|
92
|
+
// page otherwise). Cheap — no screenshot, unlike getState().
|
|
93
|
+
currentUrl() {
|
|
94
|
+
return this.page !== null ? this.page.url() : "";
|
|
95
|
+
}
|
|
96
|
+
recoverActivePage() {
|
|
97
|
+
return this.adoptLivePage();
|
|
98
|
+
}
|
|
99
|
+
// ───────────── new-tab adoption ─────────────
|
|
100
|
+
//
|
|
101
|
+
// Arm adoption immediately BEFORE an action that may open a tab. Anything
|
|
102
|
+
// already queued belonged to an earlier action and is not this action's to
|
|
103
|
+
// follow.
|
|
104
|
+
armOpenedTabAdoption() {
|
|
105
|
+
this.openedTabs.length = 0;
|
|
106
|
+
}
|
|
107
|
+
// Adopt the newest live tab opened since armOpenedTabAdoption() as the active
|
|
108
|
+
// page, so the next observe/act reads the tab the click actually opened.
|
|
109
|
+
// Returns the adopted URL, or null when the action opened no followable tab.
|
|
110
|
+
//
|
|
111
|
+
// `graceMs` covers the window between the click returning and Playwright
|
|
112
|
+
// delivering the context "page" event; a caller that has already waited for
|
|
113
|
+
// the page to settle passes 0 and just drains what arrived.
|
|
114
|
+
async adoptOpenedTab(graceMs = 0) {
|
|
115
|
+
const deadline = Date.now() + Math.max(0, graceMs);
|
|
116
|
+
let candidate = this.takeFollowableTab();
|
|
117
|
+
while (candidate === null && Date.now() < deadline) {
|
|
118
|
+
await this.sleep(50);
|
|
119
|
+
candidate = this.takeFollowableTab();
|
|
120
|
+
}
|
|
121
|
+
if (candidate === null)
|
|
122
|
+
return null;
|
|
123
|
+
this.openedTabs.length = 0;
|
|
124
|
+
// A window.open target starts at about:blank and is navigated a tick later.
|
|
125
|
+
// Adopting it while blank would report an empty page to the host, so wait
|
|
126
|
+
// (bounded) for the document it was opened for.
|
|
127
|
+
const blank = (url) => url === "" || url === "about:blank" || url === "about:srcdoc";
|
|
128
|
+
for (let i = 0; i < 40 && !candidate.isClosed() && blank(candidate.url()); i++) {
|
|
129
|
+
await this.sleep(50);
|
|
130
|
+
}
|
|
131
|
+
if (!this.ownedPages.has(candidate))
|
|
132
|
+
return null;
|
|
133
|
+
this.page = candidate;
|
|
134
|
+
this.trackMainDocument(candidate);
|
|
135
|
+
await candidate.bringToFront().catch(() => undefined);
|
|
136
|
+
await candidate
|
|
137
|
+
.waitForLoadState("domcontentloaded", { timeout: 15_000 })
|
|
138
|
+
.catch(() => undefined);
|
|
139
|
+
return candidate.isClosed() ? null : candidate.url();
|
|
140
|
+
}
|
|
141
|
+
// Newest owned popup the operator may follow. Explicit primary/recovery
|
|
142
|
+
// pages never enter the queue; the active page and OAuth transports retain
|
|
143
|
+
// their existing lifecycle handling.
|
|
144
|
+
takeFollowableTab() {
|
|
145
|
+
for (let i = this.openedTabs.length - 1; i >= 0; i--) {
|
|
146
|
+
const tab = this.openedTabs[i];
|
|
147
|
+
if (!this.ownedPages.has(tab))
|
|
148
|
+
continue;
|
|
149
|
+
if (tab === this.page ||
|
|
150
|
+
tab === this.primaryPage ||
|
|
151
|
+
tab === this.oauthProductPage ||
|
|
152
|
+
tab === this.oauthProviderPage) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
return tab;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
adoptLivePage() {
|
|
160
|
+
if (this.page !== null && this.ownedPages.has(this.page))
|
|
161
|
+
return true;
|
|
162
|
+
if (this.context === null)
|
|
163
|
+
return false;
|
|
164
|
+
const pages = this.ownedPages.live();
|
|
165
|
+
if (pages.length === 0)
|
|
166
|
+
return false;
|
|
167
|
+
const product = this.oauthProductPage !== null && this.ownedPages.has(this.oauthProductPage)
|
|
168
|
+
? this.oauthProductPage
|
|
169
|
+
: null;
|
|
170
|
+
const nonAuth = [...pages]
|
|
171
|
+
.reverse()
|
|
172
|
+
.find((p) => !/accounts\.google\.com|github\.com\/login|login\.microsoftonline\.com/i.test(p.url()));
|
|
173
|
+
this.page = nonAuth ?? product ?? pages[pages.length - 1] ?? null;
|
|
174
|
+
return this.page !== null;
|
|
175
|
+
}
|
|
176
|
+
async goto(url) {
|
|
177
|
+
if (!this.page)
|
|
178
|
+
throw new Error("Browser not started");
|
|
179
|
+
// Retry transient network/proxy drops. A residential SOCKS tunnel
|
|
180
|
+
// intermittently resets a connection mid-navigation (Chrome surfaces
|
|
181
|
+
// net::ERR_SOCKS_CONNECTION_FAILED / ERR_CONNECTION_RESET / ERR_NETWORK_
|
|
182
|
+
// CHANGED / ERR_TIMED_OUT), especially on heavy onboarding pages that
|
|
183
|
+
// open many subresource connections at once (algolia's dashboard_setup).
|
|
184
|
+
// The host is reachable on the next attempt — a single goto failure
|
|
185
|
+
// shouldn't fail the whole signup. Only retry these connection-level
|
|
186
|
+
// errors; HTTP statuses and selector/logic errors fall straight through.
|
|
187
|
+
// net::ERR_ABORTED — a navigation superseded by a redirect/JS-nav during
|
|
188
|
+
// the domcontentloaded wait. Usually transient (a redirect race on the
|
|
189
|
+
// first hit of an auth-gated portal — MEASURED 2026-06-11: defang's
|
|
190
|
+
// portal.defang.io aborted on the initial goto); a retry lands the
|
|
191
|
+
// settled page. Distinct from ERR_CONNECTION_ABORTED (a dropped socket).
|
|
192
|
+
const TRANSIENT_NET = /ERR_SOCKS_CONNECTION_FAILED|ERR_CONNECTION_(?:RESET|CLOSED|FAILED|ABORTED)|ERR_NETWORK_CHANGED|ERR_TIMED_OUT|ERR_NAME_NOT_RESOLVED|net::ERR_EMPTY_RESPONSE|net::ERR_ABORTED/i;
|
|
193
|
+
const MAX_GOTO_ATTEMPTS = 3;
|
|
194
|
+
const sameOriginPathAndSearch = (a, b) => {
|
|
195
|
+
try {
|
|
196
|
+
const left = new URL(a);
|
|
197
|
+
const right = new URL(b);
|
|
198
|
+
return (left.origin === right.origin &&
|
|
199
|
+
left.pathname === right.pathname &&
|
|
200
|
+
left.search === right.search);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const landedAuthGateForTarget = (landedRaw, targetRaw) => {
|
|
207
|
+
try {
|
|
208
|
+
const landed = new URL(landedRaw);
|
|
209
|
+
const target = new URL(targetRaw);
|
|
210
|
+
if (landed.origin !== target.origin)
|
|
211
|
+
return false;
|
|
212
|
+
return /\/(?:sign[_-]?in|login|log[_-]?in|auth)(?:\/|$)/i.test(landed.pathname);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
for (let attempt = 1;; attempt++) {
|
|
219
|
+
try {
|
|
220
|
+
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 60000 });
|
|
221
|
+
// A SOCKS/connection drop does NOT always throw: Chrome resolves
|
|
222
|
+
// domcontentloaded on its own `chrome-error://chromewebdata/`
|
|
223
|
+
// interstitial and goto returns cleanly. The bot then ran the whole
|
|
224
|
+
// planner on a dead error page and gave up after one round (MEASURED
|
|
225
|
+
// 2026-06-11: galileo/lancedb landed on chrome-error with the app
|
|
226
|
+
// host as the title, never retried). Treat a chrome-error landing as
|
|
227
|
+
// the same transient class and retry it like a thrown net error.
|
|
228
|
+
const landed = this.page.url();
|
|
229
|
+
if (landed.startsWith("chrome-error://")) {
|
|
230
|
+
if (attempt >= MAX_GOTO_ATTEMPTS) {
|
|
231
|
+
throw new Error(`net::navigation landed on a Chrome error page for ${url} ` +
|
|
232
|
+
`after ${attempt} attempts (transient proxy/host failure)`);
|
|
233
|
+
}
|
|
234
|
+
await this.sleep(1500 * attempt);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
+
// Some client-routed apps commit the address bar to the requested SPA
|
|
242
|
+
// route but never fire the lifecycle event Playwright is waiting for.
|
|
243
|
+
// Treat that as a successful navigation: callers immediately inspect
|
|
244
|
+
// the DOM and have their own element-level waits.
|
|
245
|
+
if (/Timeout \d+ms exceeded/i.test(msg)) {
|
|
246
|
+
await this.sleep(500);
|
|
247
|
+
if (sameOriginPathAndSearch(this.page.url(), url))
|
|
248
|
+
break;
|
|
249
|
+
if (landedAuthGateForTarget(this.page.url(), url))
|
|
250
|
+
break;
|
|
251
|
+
await this.page
|
|
252
|
+
.waitForURL((landed) => sameOriginPathAndSearch(landed.toString(), url), {
|
|
253
|
+
timeout: 5000,
|
|
254
|
+
})
|
|
255
|
+
.then(() => undefined)
|
|
256
|
+
.catch(() => undefined);
|
|
257
|
+
if (sameOriginPathAndSearch(this.page.url(), url))
|
|
258
|
+
break;
|
|
259
|
+
if (landedAuthGateForTarget(this.page.url(), url))
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (attempt >= MAX_GOTO_ATTEMPTS || !TRANSIENT_NET.test(msg))
|
|
263
|
+
throw err;
|
|
264
|
+
// Linear backoff — give the tunnel a moment to recover a slot.
|
|
265
|
+
await this.sleep(1500 * attempt);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Post-load dwell. Cloudflare/reCAPTCHA scoring runs JS that
|
|
269
|
+
// collects behavior signals over a window (typically 500-2000ms);
|
|
270
|
+
// landing on a page and immediately interacting reads as bot-like.
|
|
271
|
+
// The "dwell" gives the scoring window enough wall-clock to settle
|
|
272
|
+
// and also gives any deferred JS time to register event listeners
|
|
273
|
+
// we'll later fire.
|
|
274
|
+
if (this.humanize) {
|
|
275
|
+
await this.sleep(rand(800, 2000));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Reload the current page. Used by the post-verify flow to make a SPA
|
|
279
|
+
// re-read a server-side state change (email verified) that the client
|
|
280
|
+
// hasn't picked up yet. Best-effort: a reload failure is non-fatal — the
|
|
281
|
+
// caller re-reads the page state regardless.
|
|
282
|
+
async reload() {
|
|
283
|
+
if (!this.page)
|
|
284
|
+
throw new Error("Browser not started");
|
|
285
|
+
try {
|
|
286
|
+
await this.page.reload({ waitUntil: "domcontentloaded", timeout: 20_000 });
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// reload failed (slow SPA / transient) — caller re-inspects anyway
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
disposeRegistrations() {
|
|
293
|
+
this.ownedPages.dispose();
|
|
294
|
+
for (const dispose of this.documentSubscriptions.values())
|
|
295
|
+
dispose();
|
|
296
|
+
this.openedTabs.length = 0;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function rand(min, max) {
|
|
300
|
+
return Math.floor(min + Math.random() * (max - min + 1));
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=page-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-driver.js","sourceRoot":"","sources":["../../src/bot/page-driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -102,6 +102,10 @@ export interface Observation {
|
|
|
102
102
|
state: "in_progress";
|
|
103
103
|
provider_page: "closed_or_detached";
|
|
104
104
|
next_action: "operate_observe";
|
|
105
|
+
} | {
|
|
106
|
+
state: "awaiting_human";
|
|
107
|
+
reason: string;
|
|
108
|
+
next_action: "operate_observe";
|
|
105
109
|
};
|
|
106
110
|
needs_user?: NeedsUserLogin;
|
|
107
111
|
user_email?: string;
|