teamshare-bridge 0.26.19 → 0.26.21
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/bridge/daemon.js +60 -0
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.js +29 -11
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/session-relay.d.ts +14 -0
- package/dist/bridge/session-relay.js +53 -2
- package/dist/bridge/session-relay.js.map +1 -1
- package/dist/cli/commands/research.d.ts +7 -3
- package/dist/cli/commands/research.js +67 -15
- package/dist/cli/commands/research.js.map +1 -1
- package/dist/cli/commands/usage.js +85 -32
- package/dist/cli/commands/usage.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/brief.js +3 -2
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/batch.d.ts +23 -0
- package/dist/lib/browser/batch.js +33 -0
- package/dist/lib/browser/batch.js.map +1 -1
- package/dist/lib/browser/manager.d.ts +1 -0
- package/dist/lib/browser/manager.js +1 -0
- package/dist/lib/browser/manager.js.map +1 -1
- package/dist/lib/browser/network-log.d.ts +28 -3
- package/dist/lib/browser/network-log.js +74 -2
- package/dist/lib/browser/network-log.js.map +1 -1
- package/dist/lib/browser/profile-sweep.d.ts +15 -0
- package/dist/lib/browser/profile-sweep.js +69 -0
- package/dist/lib/browser/profile-sweep.js.map +1 -0
- package/dist/lib/browser/screen-change.d.ts +5 -0
- package/dist/lib/browser/screen-change.js +22 -1
- package/dist/lib/browser/screen-change.js.map +1 -1
- package/dist/lib/browser/session-pool.d.ts +40 -0
- package/dist/lib/browser/session-pool.js +62 -1
- package/dist/lib/browser/session-pool.js.map +1 -1
- package/dist/lib/browser/session.d.ts +51 -8
- package/dist/lib/browser/session.js +144 -15
- package/dist/lib/browser/session.js.map +1 -1
- package/dist/lib/llm/usage-reader.d.ts +91 -4
- package/dist/lib/llm/usage-reader.js +750 -111
- package/dist/lib/llm/usage-reader.js.map +1 -1
- package/dist/lib/llm/usage.d.ts +9 -9
- package/dist/lib/llm/usage.js +9 -8
- package/dist/lib/llm/usage.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +342 -56
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/package.json +1 -1
|
@@ -41,4 +41,44 @@ export declare function pickDebugProfileDir(opts: {
|
|
|
41
41
|
sessionKey: string;
|
|
42
42
|
usedDirs: Iterable<string>;
|
|
43
43
|
namedDir?: string;
|
|
44
|
+
/** Force an isolated per-session profile, never the shared one (2026-11). */
|
|
45
|
+
forceIsolated?: boolean;
|
|
44
46
|
}): ProfileChoice;
|
|
47
|
+
/** Normalize a test-account label for comparison (2026-11). */
|
|
48
|
+
export declare function normalizeActor(raw: unknown): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the session a no-sessionId command runs against (2026-11).
|
|
51
|
+
*
|
|
52
|
+
* Explicit > the caller's remembered names > the derived identity default.
|
|
53
|
+
* Only the CALLER's windows count: another agent's Chrome on the same machine
|
|
54
|
+
* must never make my action ambiguous. With two or more of MY live windows
|
|
55
|
+
* and no name, the command is AMBIGUOUS — acting on "whichever was touched
|
|
56
|
+
* last" would drive the wrong Chrome.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveSessionForCommand(input: {
|
|
59
|
+
explicit?: string | null;
|
|
60
|
+
remembered: string[];
|
|
61
|
+
derived: string;
|
|
62
|
+
live: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
actor?: string | null;
|
|
65
|
+
}>;
|
|
66
|
+
}): {
|
|
67
|
+
sessionId: string;
|
|
68
|
+
} | {
|
|
69
|
+
ambiguous: Array<{
|
|
70
|
+
id: string;
|
|
71
|
+
actor?: string | null;
|
|
72
|
+
}>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Actor gate for tagged windows (2026-11). A window never changes hands:
|
|
76
|
+
* a login in the Admin window can only be the Admin.
|
|
77
|
+
*/
|
|
78
|
+
export declare function assertActorForLogin(input: {
|
|
79
|
+
sessionActor?: string | null;
|
|
80
|
+
callActor?: string | null;
|
|
81
|
+
}): null | {
|
|
82
|
+
code: 'ACTOR_REQUIRED' | 'WRONG_SESSION_ACTOR';
|
|
83
|
+
message: string;
|
|
84
|
+
};
|
|
@@ -6,6 +6,9 @@ exports.deriveSessionKey = deriveSessionKey;
|
|
|
6
6
|
exports.sessionSlug = sessionSlug;
|
|
7
7
|
exports.pickDebugPort = pickDebugPort;
|
|
8
8
|
exports.pickDebugProfileDir = pickDebugProfileDir;
|
|
9
|
+
exports.normalizeActor = normalizeActor;
|
|
10
|
+
exports.resolveSessionForCommand = resolveSessionForCommand;
|
|
11
|
+
exports.assertActorForLogin = assertActorForLogin;
|
|
9
12
|
/**
|
|
10
13
|
* Browser session identity + port/profile allocation (2026-10).
|
|
11
14
|
*
|
|
@@ -116,7 +119,7 @@ function pickDebugProfileDir(opts) {
|
|
|
116
119
|
}
|
|
117
120
|
return { dir: opts.namedDir, shared: false };
|
|
118
121
|
}
|
|
119
|
-
if (!used.has(key(opts.baseDir))) {
|
|
122
|
+
if (!used.has(key(opts.baseDir)) && !opts.forceIsolated) {
|
|
120
123
|
return { dir: opts.baseDir, shared: true };
|
|
121
124
|
}
|
|
122
125
|
const dir = (0, node_path_1.join)((0, node_path_1.dirname)(opts.baseDir), `${(0, node_path_1.basename)(opts.baseDir)}-sessions`, sessionSlug(opts.sessionKey));
|
|
@@ -125,4 +128,62 @@ function pickDebugProfileDir(opts) {
|
|
|
125
128
|
}
|
|
126
129
|
return { dir, shared: false };
|
|
127
130
|
}
|
|
131
|
+
/** Normalize a test-account label for comparison (2026-11). */
|
|
132
|
+
function normalizeActor(raw) {
|
|
133
|
+
if (typeof raw !== 'string')
|
|
134
|
+
return null;
|
|
135
|
+
const trimmed = raw.trim();
|
|
136
|
+
return trimmed ? trimmed.toLowerCase() : null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolve the session a no-sessionId command runs against (2026-11).
|
|
140
|
+
*
|
|
141
|
+
* Explicit > the caller's remembered names > the derived identity default.
|
|
142
|
+
* Only the CALLER's windows count: another agent's Chrome on the same machine
|
|
143
|
+
* must never make my action ambiguous. With two or more of MY live windows
|
|
144
|
+
* and no name, the command is AMBIGUOUS — acting on "whichever was touched
|
|
145
|
+
* last" would drive the wrong Chrome.
|
|
146
|
+
*/
|
|
147
|
+
function resolveSessionForCommand(input) {
|
|
148
|
+
if (input.explicit)
|
|
149
|
+
return { sessionId: input.explicit };
|
|
150
|
+
const liveById = new Map(input.live.map((s) => [s.id, s]));
|
|
151
|
+
const owned = input.remembered.filter((id) => liveById.has(id));
|
|
152
|
+
if (owned.length >= 2) {
|
|
153
|
+
return { ambiguous: owned.map((id) => liveById.get(id)) };
|
|
154
|
+
}
|
|
155
|
+
if (owned.length === 1)
|
|
156
|
+
return { sessionId: owned[0] };
|
|
157
|
+
// None of my remembered windows is live: fall back to my derived default.
|
|
158
|
+
// When it is not live either, the action fails with "No active browser
|
|
159
|
+
// session <derived>" — which is the correct answer, never another agent's
|
|
160
|
+
// window and never a guess.
|
|
161
|
+
return { sessionId: input.derived };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Actor gate for tagged windows (2026-11). A window never changes hands:
|
|
165
|
+
* a login in the Admin window can only be the Admin.
|
|
166
|
+
*/
|
|
167
|
+
function assertActorForLogin(input) {
|
|
168
|
+
const session = normalizeActor(input.sessionActor);
|
|
169
|
+
const call = normalizeActor(input.callActor);
|
|
170
|
+
if (!session)
|
|
171
|
+
return null;
|
|
172
|
+
if (!call) {
|
|
173
|
+
return {
|
|
174
|
+
code: 'ACTOR_REQUIRED',
|
|
175
|
+
message: `ACTOR_REQUIRED: this browser window is tagged for "${input.sessionActor}". ` +
|
|
176
|
+
`Pass actor:"${input.sessionActor}" on login (and sessionId on every action).`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (session !== call) {
|
|
180
|
+
return {
|
|
181
|
+
code: 'WRONG_SESSION_ACTOR',
|
|
182
|
+
message: `WRONG_SESSION_ACTOR: this window belongs to "${input.sessionActor}" — ` +
|
|
183
|
+
`it cannot sign in as "${input.callActor}". Open that account's own window ` +
|
|
184
|
+
`with browser_open({ sessionId, actor:"${input.callActor}" }) instead.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
128
189
|
//# sourceMappingURL=session-pool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-pool.js","sourceRoot":"","sources":["../../../src/lib/browser/session-pool.ts"],"names":[],"mappings":";;;AAoCA,kDAUC;AASD,4CAQC;AAGD,kCAQC;AAQD,sCAcC;AAoBD,
|
|
1
|
+
{"version":3,"file":"session-pool.js","sourceRoot":"","sources":["../../../src/lib/browser/session-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -45,6 +45,10 @@ export interface BrowserSessionOptions {
|
|
|
45
45
|
headless?: boolean;
|
|
46
46
|
/** Persistent user-data-dir when auto-launching (default the debug profile). */
|
|
47
47
|
profileDir?: string;
|
|
48
|
+
/** Test-account label this window belongs to (2026-11, one window per identity). */
|
|
49
|
+
actor?: string | null;
|
|
50
|
+
/** Force an isolated per-session profile, never shared (2026-11). */
|
|
51
|
+
isolated?: boolean;
|
|
48
52
|
/** Extra Chrome args for auto-launch. */
|
|
49
53
|
chromeArgs?: string[];
|
|
50
54
|
/**
|
|
@@ -94,6 +98,8 @@ export declare class BrowserSession {
|
|
|
94
98
|
* started (or the auto-answer browser) is never killed by us.
|
|
95
99
|
*/
|
|
96
100
|
private _ownedByBridge;
|
|
101
|
+
/** Test-account label this window is tagged for (2026-11). Never changes hands. */
|
|
102
|
+
private _actor;
|
|
97
103
|
/**
|
|
98
104
|
* Request/response + console evidence for the whole session (QA). Attached
|
|
99
105
|
* on both connect paths; the agent reads it with `browser_action
|
|
@@ -112,6 +118,10 @@ export declare class BrowserSession {
|
|
|
112
118
|
get sessionKey(): string;
|
|
113
119
|
/** The user-data-dir this session launches Chrome with. */
|
|
114
120
|
get profileDir(): string;
|
|
121
|
+
/** The test-account label this window is tagged for (2026-11). */
|
|
122
|
+
get actor(): string | null;
|
|
123
|
+
/** Tag an untagged window. A tagged window never changes hands. */
|
|
124
|
+
setActor(actor: string | null | undefined): void;
|
|
115
125
|
/**
|
|
116
126
|
* Reconfigure a DISCONNECTED session before reconnecting it (2026-10).
|
|
117
127
|
*
|
|
@@ -259,6 +269,19 @@ export declare class BrowserSession {
|
|
|
259
269
|
stoppedBecause: string;
|
|
260
270
|
}>;
|
|
261
271
|
navigate(url: string): Promise<PageInfo>;
|
|
272
|
+
/**
|
|
273
|
+
* Set the browser viewport before a design-verification screenshot
|
|
274
|
+
* (2026-11): the design's viewport IS the contract — a pixel diff at any
|
|
275
|
+
* other size measures the window, not the design. Applies
|
|
276
|
+
* Emulation.setDeviceMetricsOverride on the live target and reads the
|
|
277
|
+
* viewport back, so the caller knows what the page actually reports
|
|
278
|
+
* (a clamped value is still a successful, truthful set).
|
|
279
|
+
*/
|
|
280
|
+
setViewport(width: number, height: number, deviceScaleFactor?: number): Promise<{
|
|
281
|
+
width: number;
|
|
282
|
+
height: number;
|
|
283
|
+
deviceScaleFactor: number;
|
|
284
|
+
}>;
|
|
262
285
|
/**
|
|
263
286
|
* Capture a screenshot. Returns base64-encoded data.
|
|
264
287
|
*/
|
|
@@ -339,12 +362,18 @@ export declare class BrowserSession {
|
|
|
339
362
|
* role, accessible name, state and CSS-pixel centre. Includes elements
|
|
340
363
|
* inside portalled menus/popovers/dialogs, which is the whole point: a kebab
|
|
341
364
|
* menu that just opened is findable without guessing a selector.
|
|
365
|
+
*
|
|
366
|
+
* `filter` (2026-11) narrows by substring over role + name + tag, so
|
|
367
|
+
* `getRefs {filter:"button"}` answers "all buttons" without a second scan.
|
|
368
|
+
* Refs are ALWAYS the full-scan numbering — a filtered view never renumbers,
|
|
369
|
+
* or a ref from the list would not resolve.
|
|
342
370
|
*/
|
|
343
|
-
getRefs(): Promise<{
|
|
371
|
+
getRefs(filter?: string): Promise<{
|
|
344
372
|
at: number;
|
|
345
373
|
count: number;
|
|
346
374
|
refs: RefElement[];
|
|
347
375
|
lines: string[];
|
|
376
|
+
filter?: string;
|
|
348
377
|
}>;
|
|
349
378
|
/**
|
|
350
379
|
* Resolve a ref to a clickable point. A ref whose node left the DOM is
|
|
@@ -626,14 +655,28 @@ export declare class BrowserSession {
|
|
|
626
655
|
nativeDialog: NativeDialogState | null;
|
|
627
656
|
}>;
|
|
628
657
|
/**
|
|
629
|
-
*
|
|
658
|
+
* Wait for the screen to settle after an action (2026-11, qaVision only).
|
|
630
659
|
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
|
|
636
|
-
|
|
660
|
+
* The auto-answer loop re-captures context + screenshot on every iteration
|
|
661
|
+
* and aborts its vision call on material change; the QA browser used to
|
|
662
|
+
* return the screen MID-TRANSITION (click → compactSnapshot immediately).
|
|
663
|
+
* This waits for a real navigation, an SPA route change, or a material DOM
|
|
664
|
+
* change, then for a short quiet window (no new mutations, network idle-ish)
|
|
665
|
+
* before the caller reads the screen. Bounded (~4 s default) and
|
|
666
|
+
* best-effort: a timeout resolves with what is known, never a failure.
|
|
667
|
+
*/
|
|
668
|
+
waitForScreenSettle(opts?: {
|
|
669
|
+
since?: number;
|
|
670
|
+
quietMs?: number;
|
|
671
|
+
timeoutMs?: number;
|
|
672
|
+
fromUrl?: string;
|
|
673
|
+
}): Promise<{
|
|
674
|
+
changed: boolean;
|
|
675
|
+
navigated: boolean;
|
|
676
|
+
fromUrl: string | null;
|
|
677
|
+
toUrl: string | null;
|
|
678
|
+
elapsedMs: number;
|
|
679
|
+
}>;
|
|
637
680
|
/**
|
|
638
681
|
* Re-attach to the debug Chrome after the page died (2026-10).
|
|
639
682
|
*
|
|
@@ -115,6 +115,8 @@ class BrowserSession {
|
|
|
115
115
|
* started (or the auto-answer browser) is never killed by us.
|
|
116
116
|
*/
|
|
117
117
|
_ownedByBridge = false;
|
|
118
|
+
/** Test-account label this window is tagged for (2026-11). Never changes hands. */
|
|
119
|
+
_actor;
|
|
118
120
|
/**
|
|
119
121
|
* Request/response + console evidence for the whole session (QA). Attached
|
|
120
122
|
* on both connect paths; the agent reads it with `browser_action
|
|
@@ -134,6 +136,10 @@ class BrowserSession {
|
|
|
134
136
|
this._autoLaunch = opts?.autoLaunch ?? true;
|
|
135
137
|
this._headless = opts?.headless ?? false;
|
|
136
138
|
this._profileDir = opts?.profileDir ?? exports.DEBUG_PROFILE_DIR;
|
|
139
|
+
this._actor =
|
|
140
|
+
typeof opts?.actor === 'string' && opts.actor.trim()
|
|
141
|
+
? opts.actor.trim()
|
|
142
|
+
: null;
|
|
137
143
|
this._chromeArgs = opts?.chromeArgs ?? [];
|
|
138
144
|
this._allowCredentials = opts?.allowCredentials ?? false;
|
|
139
145
|
this._qaVision = opts?.qaVision ?? false;
|
|
@@ -146,6 +152,17 @@ class BrowserSession {
|
|
|
146
152
|
get profileDir() {
|
|
147
153
|
return this._profileDir;
|
|
148
154
|
}
|
|
155
|
+
/** The test-account label this window is tagged for (2026-11). */
|
|
156
|
+
get actor() {
|
|
157
|
+
return this._actor;
|
|
158
|
+
}
|
|
159
|
+
/** Tag an untagged window. A tagged window never changes hands. */
|
|
160
|
+
setActor(actor) {
|
|
161
|
+
if (this._actor)
|
|
162
|
+
return;
|
|
163
|
+
if (typeof actor === 'string' && actor.trim())
|
|
164
|
+
this._actor = actor.trim();
|
|
165
|
+
}
|
|
149
166
|
/**
|
|
150
167
|
* Reconfigure a DISCONNECTED session before reconnecting it (2026-10).
|
|
151
168
|
*
|
|
@@ -166,6 +183,8 @@ class BrowserSession {
|
|
|
166
183
|
this._headless = opts.headless;
|
|
167
184
|
if (opts.profileDir)
|
|
168
185
|
this._profileDir = opts.profileDir;
|
|
186
|
+
if (opts.actor !== undefined)
|
|
187
|
+
this.setActor(opts.actor);
|
|
169
188
|
if (opts.chromeArgs)
|
|
170
189
|
this._chromeArgs = opts.chromeArgs;
|
|
171
190
|
if (opts.allowCredentials !== undefined) {
|
|
@@ -383,8 +402,12 @@ class BrowserSession {
|
|
|
383
402
|
(0, cdp_1.onCdpEvent)(page, 'Page.frameNavigated', (params) => {
|
|
384
403
|
if (!(0, screen_change_1.isMainFrameNavigation)(params))
|
|
385
404
|
return;
|
|
405
|
+
// Stamp the load on the observer record (2026-11): the full-load half of
|
|
406
|
+
// the settle signal. The stamp lands after the new document exists, so a
|
|
407
|
+
// settle that started before the navigation still sees it.
|
|
386
408
|
setTimeout(() => {
|
|
387
409
|
void install();
|
|
410
|
+
void (0, cdp_1.evaluateJs)(page, '(() => { try { const o = window.__tsScreenObs; if (o) { o.navAt = Date.now(); o.url = location.href; o.urlAt = Date.now(); } } catch {} return true; })()').catch(() => undefined);
|
|
388
411
|
}, 600);
|
|
389
412
|
});
|
|
390
413
|
}
|
|
@@ -700,6 +723,32 @@ class BrowserSession {
|
|
|
700
723
|
this.ensureConnected();
|
|
701
724
|
return (0, cdp_1.navigateTo)(this.page, url);
|
|
702
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Set the browser viewport before a design-verification screenshot
|
|
728
|
+
* (2026-11): the design's viewport IS the contract — a pixel diff at any
|
|
729
|
+
* other size measures the window, not the design. Applies
|
|
730
|
+
* Emulation.setDeviceMetricsOverride on the live target and reads the
|
|
731
|
+
* viewport back, so the caller knows what the page actually reports
|
|
732
|
+
* (a clamped value is still a successful, truthful set).
|
|
733
|
+
*/
|
|
734
|
+
async setViewport(width, height, deviceScaleFactor = 1) {
|
|
735
|
+
this.ensureConnected();
|
|
736
|
+
const w = Math.max(320, Math.min(8192, Math.floor(width)));
|
|
737
|
+
const h = Math.max(320, Math.min(8192, Math.floor(height)));
|
|
738
|
+
const dpr = Math.max(1, Math.min(3, deviceScaleFactor || 1));
|
|
739
|
+
await (0, cdp_1.cdpSend)(this.page, 'Emulation.setDeviceMetricsOverride', {
|
|
740
|
+
width: w,
|
|
741
|
+
height: h,
|
|
742
|
+
deviceScaleFactor: dpr,
|
|
743
|
+
mobile: false,
|
|
744
|
+
});
|
|
745
|
+
const actual = await (0, cdp_1.viewportOf)(this.page);
|
|
746
|
+
return {
|
|
747
|
+
width: actual?.width ?? w,
|
|
748
|
+
height: actual?.height ?? h,
|
|
749
|
+
deviceScaleFactor: actual?.dpr ?? dpr,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
703
752
|
/**
|
|
704
753
|
* Capture a screenshot. Returns base64-encoded data.
|
|
705
754
|
*/
|
|
@@ -834,12 +883,28 @@ class BrowserSession {
|
|
|
834
883
|
* role, accessible name, state and CSS-pixel centre. Includes elements
|
|
835
884
|
* inside portalled menus/popovers/dialogs, which is the whole point: a kebab
|
|
836
885
|
* menu that just opened is findable without guessing a selector.
|
|
886
|
+
*
|
|
887
|
+
* `filter` (2026-11) narrows by substring over role + name + tag, so
|
|
888
|
+
* `getRefs {filter:"button"}` answers "all buttons" without a second scan.
|
|
889
|
+
* Refs are ALWAYS the full-scan numbering — a filtered view never renumbers,
|
|
890
|
+
* or a ref from the list would not resolve.
|
|
837
891
|
*/
|
|
838
|
-
async getRefs() {
|
|
892
|
+
async getRefs(filter) {
|
|
839
893
|
this.ensureConnected();
|
|
840
894
|
const { at, list } = await (0, refs_1.scanRefs)(this.page);
|
|
841
895
|
this._lastRefs = list;
|
|
842
|
-
|
|
896
|
+
const needle = (filter ?? '').trim().toLowerCase();
|
|
897
|
+
if (!needle) {
|
|
898
|
+
return { at, count: list.length, refs: list, lines: (0, refs_1.formatRefs)(list) };
|
|
899
|
+
}
|
|
900
|
+
const kept = list.filter((r) => `${r.role} ${r.name} ${r.tag}`.toLowerCase().includes(needle));
|
|
901
|
+
return {
|
|
902
|
+
at,
|
|
903
|
+
count: kept.length,
|
|
904
|
+
refs: kept,
|
|
905
|
+
lines: (0, refs_1.formatRefs)(kept),
|
|
906
|
+
filter,
|
|
907
|
+
};
|
|
843
908
|
}
|
|
844
909
|
/**
|
|
845
910
|
* Resolve a ref to a clickable point. A ref whose node left the DOM is
|
|
@@ -1571,22 +1636,86 @@ class BrowserSession {
|
|
|
1571
1636
|
};
|
|
1572
1637
|
}
|
|
1573
1638
|
/**
|
|
1574
|
-
*
|
|
1639
|
+
* Wait for the screen to settle after an action (2026-11, qaVision only).
|
|
1575
1640
|
*
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1641
|
+
* The auto-answer loop re-captures context + screenshot on every iteration
|
|
1642
|
+
* and aborts its vision call on material change; the QA browser used to
|
|
1643
|
+
* return the screen MID-TRANSITION (click → compactSnapshot immediately).
|
|
1644
|
+
* This waits for a real navigation, an SPA route change, or a material DOM
|
|
1645
|
+
* change, then for a short quiet window (no new mutations, network idle-ish)
|
|
1646
|
+
* before the caller reads the screen. Bounded (~4 s default) and
|
|
1647
|
+
* best-effort: a timeout resolves with what is known, never a failure.
|
|
1580
1648
|
*/
|
|
1581
|
-
async
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1649
|
+
async waitForScreenSettle(opts) {
|
|
1650
|
+
const started = Date.now();
|
|
1651
|
+
const since = opts?.since ?? started;
|
|
1652
|
+
const quietMs = Math.min(Math.max(opts?.quietMs ?? 300, 100), 2_000);
|
|
1653
|
+
const timeout = Math.min(Math.max(opts?.timeoutMs ?? 4_000, 500), 15_000);
|
|
1654
|
+
const deadline = started + timeout;
|
|
1655
|
+
const fromUrl = opts?.fromUrl ?? null;
|
|
1656
|
+
let toUrl = null;
|
|
1657
|
+
let navigated = false;
|
|
1658
|
+
let changed = false;
|
|
1659
|
+
let quietSince = Date.now();
|
|
1660
|
+
let lastMutated = 0;
|
|
1661
|
+
while (Date.now() < deadline) {
|
|
1662
|
+
if (this._nativeDialog)
|
|
1663
|
+
break;
|
|
1664
|
+
let seen = null;
|
|
1665
|
+
let href = null;
|
|
1666
|
+
try {
|
|
1667
|
+
const raw = (await (0, cdp_1.evaluateJs)(this.page, '(() => { const o = window.__tsScreenObs; const h = location.href; return { obs: o ? { mutated: o.mutated || 0, changed: o.changed || 0, url: o.url || null, urlAt: o.urlAt || 0, navAt: o.navAt || 0 } : null, href: h }; })()'));
|
|
1668
|
+
seen = raw?.obs ?? null;
|
|
1669
|
+
href = raw?.href ?? null;
|
|
1670
|
+
}
|
|
1671
|
+
catch {
|
|
1672
|
+
// Navigation destroyed the execution context: the page IS changing.
|
|
1673
|
+
// Wait for it to come back instead of reporting a settled screen.
|
|
1674
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (href)
|
|
1678
|
+
toUrl = href;
|
|
1679
|
+
const frameNav = (seen?.navAt ?? 0) >= since;
|
|
1680
|
+
const spaNav = (seen?.urlAt ?? 0) >= since ||
|
|
1681
|
+
(fromUrl !== null && href !== null && href !== fromUrl);
|
|
1682
|
+
if (frameNav || spaNav)
|
|
1683
|
+
navigated = true;
|
|
1684
|
+
if ((seen?.changed ?? 0) >= since || (seen?.mutated ?? 0) >= since) {
|
|
1685
|
+
changed = true;
|
|
1686
|
+
}
|
|
1687
|
+
const mutated = seen?.mutated ?? 0;
|
|
1688
|
+
if (mutated > lastMutated) {
|
|
1689
|
+
lastMutated = mutated;
|
|
1690
|
+
quietSince = Date.now();
|
|
1691
|
+
}
|
|
1692
|
+
const quietFor = Date.now() - quietSince;
|
|
1693
|
+
const networkQuiet = this.network.pendingCount() === 0;
|
|
1694
|
+
// Resolve when SOMETHING happened and the page went quiet — or when
|
|
1695
|
+
// nothing happened at all for the quiet window (a no-op click settles
|
|
1696
|
+
// immediately instead of burning the full timeout).
|
|
1697
|
+
if ((navigated || changed) && quietFor >= quietMs && networkQuiet)
|
|
1698
|
+
break;
|
|
1699
|
+
// Minimum observation window (2026-11): the observer debounces at
|
|
1700
|
+
// 500 ms and a click's mutations take a moment to arrive, so exiting on
|
|
1701
|
+
// the first quiet poll (~300 ms in) reported "settled" while the page
|
|
1702
|
+
// was still mid-transition. A genuinely idle page still exits after
|
|
1703
|
+
// ~700 ms, not the full timeout.
|
|
1704
|
+
if (!navigated &&
|
|
1705
|
+
!changed &&
|
|
1706
|
+
quietFor >= quietMs &&
|
|
1707
|
+
networkQuiet &&
|
|
1708
|
+
Date.now() - started >= Math.max(quietMs, 700))
|
|
1709
|
+
break;
|
|
1710
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(120, Math.max(0, deadline - Date.now()))));
|
|
1589
1711
|
}
|
|
1712
|
+
return {
|
|
1713
|
+
changed,
|
|
1714
|
+
navigated,
|
|
1715
|
+
fromUrl,
|
|
1716
|
+
toUrl,
|
|
1717
|
+
elapsedMs: Date.now() - started,
|
|
1718
|
+
};
|
|
1590
1719
|
}
|
|
1591
1720
|
/**
|
|
1592
1721
|
* Re-attach to the debug Chrome after the page died (2026-10).
|