@nuanu-ai/agentbrowse 0.2.23 → 0.2.25
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/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +37 -23
- package/dist/commands/action-executor.d.ts +2 -0
- package/dist/commands/action-executor.d.ts.map +1 -1
- package/dist/commands/action-executor.js +1 -0
- package/dist/commands/action-result-resolution.d.ts +2 -2
- package/dist/commands/action-result-resolution.d.ts.map +1 -1
- package/dist/commands/action-result-resolution.js +26 -6
- package/dist/commands/click-action-executor.d.ts +2 -0
- package/dist/commands/click-action-executor.d.ts.map +1 -1
- package/dist/commands/click-action-executor.js +11 -0
- package/dist/commands/click-activation-policy.d.ts +5 -0
- package/dist/commands/click-activation-policy.d.ts.map +1 -0
- package/dist/commands/click-activation-policy.js +13 -0
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +11 -3
- package/dist/commands/launch.js +16 -6
- package/dist/commands/navigate.d.ts.map +1 -1
- package/dist/commands/navigate.js +7 -7
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +15 -6
- package/dist/commands/screenshot.d.ts.map +1 -1
- package/dist/commands/screenshot.js +25 -11
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +92 -6
- package/dist/control-semantics.d.ts.map +1 -1
- package/dist/control-semantics.js +6 -1
- package/dist/owned-process.d.ts +10 -0
- package/dist/owned-process.d.ts.map +1 -1
- package/dist/owned-process.js +74 -0
- package/dist/playwright-runtime.d.ts +6 -0
- package/dist/playwright-runtime.d.ts.map +1 -1
- package/dist/playwright-runtime.js +76 -0
- package/dist/solver/browser-launcher.js +35 -8
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/commands/act.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/commands/act.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAsCnD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA2HxF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,CAAC;AA4V7B,wBAAsB,GAAG,CACvB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAitBf"}
|
package/dist/commands/act.js
CHANGED
|
@@ -7,6 +7,7 @@ import { outputContractFailure, outputFailure, outputJSON } from '../output.js';
|
|
|
7
7
|
import { capturePageObservation, captureLocatorContextHash, captureLocatorState, createAcceptanceProbe, diagnoseNoObservableProgress, genericClickObservationChanged, locatorStateChanged, pageObservationChanged, rankLocatorCandidates, shouldVerifyObservableProgress, waitForAcceptanceProbe, } from './action-acceptance.js';
|
|
8
8
|
import { captureActionFailureArtifacts, startActionTrace } from './action-artifacts.js';
|
|
9
9
|
import { buildLocator, resolveLocatorRoot } from './action-fallbacks.js';
|
|
10
|
+
import { clickActivationStrategyForTarget } from './click-activation-policy.js';
|
|
10
11
|
import { resolveSubmitResult } from './action-result-resolution.js';
|
|
11
12
|
import { projectActionValue } from './action-value-projection.js';
|
|
12
13
|
import { applyActionWithFallbacks } from './action-executor.js';
|
|
@@ -23,6 +24,7 @@ function ensureValue(action, value) {
|
|
|
23
24
|
return value;
|
|
24
25
|
throw new Error(`Act value is required for action: ${action}`);
|
|
25
26
|
}
|
|
27
|
+
const MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS = [0, 25, 50];
|
|
26
28
|
function emitActPreflightFailure(params) {
|
|
27
29
|
return outputContractFailure({
|
|
28
30
|
error: params.error,
|
|
@@ -465,30 +467,41 @@ export async function act(session, targetRef, action, value) {
|
|
|
465
467
|
if (!liveTarget.domSignature) {
|
|
466
468
|
return false;
|
|
467
469
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
470
|
+
for (let attemptIndex = 0; attemptIndex < MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS.length; attemptIndex += 1) {
|
|
471
|
+
const delayMs = MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS[attemptIndex] ?? 0;
|
|
472
|
+
if (attemptIndex > 0) {
|
|
473
|
+
attempts.push(`domSignature.rebind.retry:${strategy}:${attemptIndex + 1}`);
|
|
474
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
475
|
+
}
|
|
476
|
+
const snapshot = await readLocatorBindingSnapshot(resolvedLocator).catch(() => null);
|
|
477
|
+
if (!snapshot?.domSignature) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (snapshot.domSignature === liveTarget.domSignature) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
if (!isCompatibleMutableFieldBinding(liveTarget, snapshot)) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
attempts.push(`domSignature.rebound:${strategy}`);
|
|
487
|
+
const updatedTarget = updateTarget(session, targetRef, {
|
|
488
|
+
domSignature: snapshot.domSignature,
|
|
489
|
+
label: snapshot.label ?? liveTarget.label,
|
|
490
|
+
lifecycle: 'live',
|
|
491
|
+
lifecycleReason: undefined,
|
|
492
|
+
availability: { state: 'available' },
|
|
493
|
+
semantics: {
|
|
494
|
+
...liveTarget.semantics,
|
|
495
|
+
name: snapshot.label ?? liveTarget.semantics?.name,
|
|
496
|
+
role: snapshot.role ?? liveTarget.semantics?.role,
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
if (updatedTarget) {
|
|
500
|
+
liveTarget = updatedTarget;
|
|
501
|
+
}
|
|
502
|
+
return true;
|
|
490
503
|
}
|
|
491
|
-
return
|
|
504
|
+
return false;
|
|
492
505
|
};
|
|
493
506
|
const assertResolvedTargetStillValid = async (resolvedLocator, stage) => {
|
|
494
507
|
if (liveTarget.pageSignature &&
|
|
@@ -596,6 +609,7 @@ export async function act(session, targetRef, action, value) {
|
|
|
596
609
|
});
|
|
597
610
|
attempts.push(`resolve:${strategy}`);
|
|
598
611
|
const usedFallback = await applyActionWithFallbacks(page, locatorRoot, resolvedLocator, action, executionValue, attempts, target.controlFamily, {
|
|
612
|
+
clickActivationStrategy: clickActivationStrategyForTarget(target, action),
|
|
599
613
|
guards: {
|
|
600
614
|
assertStillValid: async (stage) => {
|
|
601
615
|
await assertResolvedTargetStillValid(resolvedLocator, stage);
|
|
@@ -3,9 +3,11 @@ import type { TargetControlFamily } from '../runtime-state.js';
|
|
|
3
3
|
import type { BrowseAction } from './browse-actions.js';
|
|
4
4
|
import type { LocatorRoot } from './action-fallbacks.js';
|
|
5
5
|
import type { ActionExecutionGuards } from './action-execution-guards.js';
|
|
6
|
+
import type { ClickActivationStrategy } from './click-activation-policy.js';
|
|
6
7
|
type ActionExecutionOptions = {
|
|
7
8
|
beforeClickRetry?: () => Promise<void>;
|
|
8
9
|
guards?: ActionExecutionGuards;
|
|
10
|
+
clickActivationStrategy?: ClickActivationStrategy;
|
|
9
11
|
};
|
|
10
12
|
export declare function applyActionWithFallbacks(page: Page, root: LocatorRoot, locator: Locator, action: BrowseAction, value: string | undefined, attempts: string[], family?: TargetControlFamily, options?: ActionExecutionOptions): Promise<boolean>;
|
|
11
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAc5E,KAAK,sBAAsB,GAAG;IAC5B,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,CAAC,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CAsDlB"}
|
|
@@ -14,6 +14,7 @@ export async function applyActionWithFallbacks(page, root, locator, action, valu
|
|
|
14
14
|
return applyTriggerAction(page, locator, attempts, {
|
|
15
15
|
beforeRetry: options?.beforeClickRetry,
|
|
16
16
|
guards: options?.guards,
|
|
17
|
+
clickActivationStrategy: options?.clickActivationStrategy,
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
return applyEditableClickAction(page, locator, attempts, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AcceptanceProbe, type PageObservation } from './action-acceptance.js';
|
|
2
|
-
export type SubmitResultClaimKind = 'hard_result' | 'hard_blocker' | 'soft_result_candidate' | 'noisy_change';
|
|
2
|
+
export type SubmitResultClaimKind = 'hard_result' | 'hard_blocker' | 'intermediate_progress' | 'soft_result_candidate' | 'noisy_change';
|
|
3
3
|
export type SubmitResultClaim = {
|
|
4
4
|
kind: SubmitResultClaimKind;
|
|
5
5
|
source: string;
|
|
@@ -7,7 +7,7 @@ export type SubmitResultClaim = {
|
|
|
7
7
|
ownerScopeRef?: string;
|
|
8
8
|
};
|
|
9
9
|
export type SubmitResultResolution = {
|
|
10
|
-
finalVerdict: 'outcome' | 'blocked' | 'none';
|
|
10
|
+
finalVerdict: 'outcome' | 'blocked' | 'progress' | 'none';
|
|
11
11
|
claims: SubmitResultClaim[];
|
|
12
12
|
decisiveClaims: SubmitResultClaim[];
|
|
13
13
|
acceptAsProgress: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-result-resolution.d.ts","sourceRoot":"","sources":["../../src/commands/action-result-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,MAAM,qBAAqB,GAC7B,aAAa,GACb,cAAc,GACd,uBAAuB,GACvB,cAAc,CAAC;AAEnB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"action-result-resolution.d.ts","sourceRoot":"","sources":["../../src/commands/action-result-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,MAAM,qBAAqB,GAC7B,aAAa,GACb,cAAc,GACd,uBAAuB,GACvB,uBAAuB,GACvB,cAAc,CAAC;AAEnB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IAC1D,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,CAAC,iBAAiB,CAAC,GACvC,IAAI,CAAC,sBAAsB,EAAE,QAAQ,GAAG,kBAAkB,CAAC,CA+B7D;AAmBD,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,eAAe,EACtB,oBAAoB,EAAE,eAAe,GAAG,IAAI,GAC3C,OAAO,CAAC,sBAAsB,CAAC,CAoFjC"}
|
|
@@ -15,6 +15,13 @@ export function reduceSubmitResultClaims(claims) {
|
|
|
15
15
|
decisiveClaims: hardResults,
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
const intermediateProgressClaims = claims.filter((claim) => claim.kind === 'intermediate_progress');
|
|
19
|
+
if (intermediateProgressClaims.length > 0) {
|
|
20
|
+
return {
|
|
21
|
+
finalVerdict: 'progress',
|
|
22
|
+
decisiveClaims: intermediateProgressClaims,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
18
25
|
return {
|
|
19
26
|
finalVerdict: 'none',
|
|
20
27
|
decisiveClaims: [],
|
|
@@ -62,6 +69,8 @@ export async function resolveSubmitResult(probe, afterPageObservation) {
|
|
|
62
69
|
}
|
|
63
70
|
const resultSignalChanged = beforePageObservation.resultSignalHash !== afterPageObservation.resultSignalHash &&
|
|
64
71
|
afterPageObservation.resultSignalHash !== null;
|
|
72
|
+
const submitSignalChanged = beforePageObservation.submitSignalHash !== afterPageObservation.submitSignalHash &&
|
|
73
|
+
afterPageObservation.submitSignalHash !== null;
|
|
65
74
|
const afterSurfaceContextHash = await captureAfterSurfaceContextHash(probe);
|
|
66
75
|
const ownerScopeChanged = probe.beforeSurfaceContextHash !== null &&
|
|
67
76
|
afterSurfaceContextHash !== null &&
|
|
@@ -77,18 +86,29 @@ export async function resolveSubmitResult(probe, afterPageObservation) {
|
|
|
77
86
|
});
|
|
78
87
|
}
|
|
79
88
|
else if (ownerScopeChanged) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
if (submitSignalChanged) {
|
|
90
|
+
claims.push({
|
|
91
|
+
kind: 'intermediate_progress',
|
|
92
|
+
source: 'owner-scope',
|
|
93
|
+
reason: 'Owner scope changed after submit with a submit-relevant follow-up state change.',
|
|
94
|
+
ownerScopeRef: probe.surface?.ref,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
claims.push({
|
|
99
|
+
kind: 'noisy_change',
|
|
100
|
+
source: 'owner-scope',
|
|
101
|
+
reason: 'Owner scope changed after submit without a result-bearing signal.',
|
|
102
|
+
ownerScopeRef: probe.surface?.ref,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
86
105
|
}
|
|
87
106
|
const reduced = reduceSubmitResultClaims(claims);
|
|
88
107
|
return {
|
|
89
108
|
...reduced,
|
|
90
109
|
claims,
|
|
91
110
|
acceptAsProgress: reduced.finalVerdict === 'outcome' ||
|
|
111
|
+
reduced.finalVerdict === 'progress' ||
|
|
92
112
|
(reduced.finalVerdict === 'none' &&
|
|
93
113
|
claims.some((claim) => claim.kind === 'soft_result_candidate')),
|
|
94
114
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Locator, Page } from 'playwright-core';
|
|
2
|
+
import type { ClickActivationStrategy } from './click-activation-policy.js';
|
|
2
3
|
import { type ActionExecutionGuards } from './action-execution-guards.js';
|
|
3
4
|
type ClickRetryOptions = {
|
|
4
5
|
beforeRetry?: () => Promise<void>;
|
|
5
6
|
guards?: ActionExecutionGuards;
|
|
7
|
+
clickActivationStrategy?: ClickActivationStrategy;
|
|
6
8
|
};
|
|
7
9
|
export declare function applyEditableClickAction(page: Page, locator: Locator, attempts: string[], options?: ClickRetryOptions): Promise<boolean>;
|
|
8
10
|
export declare function applyTriggerAction(page: Page, locator: Locator, attempts: string[], options?: ClickRetryOptions): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"click-action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/click-action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAMrD,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEnG,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"click-action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/click-action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAMrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEnG,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD,CAAC;AA2GF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB"}
|
|
@@ -28,6 +28,17 @@ async function ensureLocatorRetryReady(locator, error, options) {
|
|
|
28
28
|
}
|
|
29
29
|
async function applyClickSequence(page, locator, attempts, options) {
|
|
30
30
|
await handoffFocusFromActiveIframe(locator, attempts);
|
|
31
|
+
if (options?.clickActivationStrategy === 'dom') {
|
|
32
|
+
await runActionExecutionGuard(options?.guards, 'click.evaluate.primary');
|
|
33
|
+
attempts.push('locator.evaluate.click.primary');
|
|
34
|
+
await locator.evaluate((element) => {
|
|
35
|
+
if (!(element instanceof HTMLElement)) {
|
|
36
|
+
throw new Error('unsupported_js_click_fallback');
|
|
37
|
+
}
|
|
38
|
+
element.click();
|
|
39
|
+
});
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
31
42
|
attempts.push('locator.click');
|
|
32
43
|
try {
|
|
33
44
|
await locator.click({ timeout: LOCATOR_CLICK_TIMEOUT_MS });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TargetDescriptor } from '../runtime-state.js';
|
|
2
|
+
import type { BrowseAction } from './browse-actions.js';
|
|
3
|
+
export type ClickActivationStrategy = 'pointer' | 'dom';
|
|
4
|
+
export declare function clickActivationStrategyForTarget(target: Pick<TargetDescriptor, 'kind' | 'framePath' | 'semantics'>, action: BrowseAction): ClickActivationStrategy;
|
|
5
|
+
//# sourceMappingURL=click-activation-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"click-activation-policy.d.ts","sourceRoot":"","sources":["../../src/commands/click-activation-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,KAAK,CAAC;AAExD,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC,EAClE,MAAM,EAAE,YAAY,GACnB,uBAAuB,CAezB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function clickActivationStrategyForTarget(target, action) {
|
|
2
|
+
if (action !== 'click') {
|
|
3
|
+
return 'pointer';
|
|
4
|
+
}
|
|
5
|
+
const withinIframe = Boolean(target.framePath?.length);
|
|
6
|
+
if (!withinIframe) {
|
|
7
|
+
return 'pointer';
|
|
8
|
+
}
|
|
9
|
+
const kind = target.kind?.trim().toLowerCase();
|
|
10
|
+
const role = target.semantics?.role?.trim().toLowerCase();
|
|
11
|
+
const isTab = kind === 'tab' || role === 'tab';
|
|
12
|
+
return isTab ? 'dom' : 'pointer';
|
|
13
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAoMnD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyMf"}
|
package/dist/commands/extract.js
CHANGED
|
@@ -6,7 +6,7 @@ import { AgentpayStructuredOutputTruncatedError } from '../agentpay-stagehand-ll
|
|
|
6
6
|
import { saveSession } from '../session.js';
|
|
7
7
|
import { getSurface, getTarget, markSurfaceLifecycle, markTargetLifecycle, setCurrentPage, } from '../runtime-state.js';
|
|
8
8
|
import { outputContractFailure, outputJSON } from '../output.js';
|
|
9
|
-
import { connectPlaywright, disconnectPlaywright, resolvePageByRef as resolvePlaywrightPageByRef, syncSessionPage, } from '../playwright-runtime.js';
|
|
9
|
+
import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, resolvePageByRef as resolvePlaywrightPageByRef, syncSessionPage, } from '../playwright-runtime.js';
|
|
10
10
|
import { normalizePageSignature } from './descriptor-validation.js';
|
|
11
11
|
import { readScopedDialogText } from './extract-scoped-dialog-text.js';
|
|
12
12
|
import { resolveScopedExtractContext } from './extract-scope-resolution.js';
|
|
@@ -174,7 +174,7 @@ export async function extract(session, schemaJson, scopeRef) {
|
|
|
174
174
|
const targetScope = scopeRef ? getTarget(session, scopeRef) : null;
|
|
175
175
|
const surfaceScope = !targetScope && scopeRef ? getSurface(session, scopeRef) : null;
|
|
176
176
|
const scopeTarget = targetScope ?? surfaceScope;
|
|
177
|
-
|
|
177
|
+
let pageRef = scopeTarget?.pageRef ?? session.runtime?.currentPageRef ?? 'p0';
|
|
178
178
|
if (scopeRef && !scopeTarget) {
|
|
179
179
|
return outputContractFailure({
|
|
180
180
|
error: 'unknown_scope_ref',
|
|
@@ -221,7 +221,15 @@ export async function extract(session, schemaJson, scopeRef) {
|
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
223
|
try {
|
|
224
|
-
|
|
224
|
+
let sourcePage;
|
|
225
|
+
if (scopeTarget) {
|
|
226
|
+
sourcePage = await resolvePlaywrightPageByRef(browser, session, pageRef);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
230
|
+
pageRef = resolvedPage.pageRef;
|
|
231
|
+
sourcePage = resolvedPage.page;
|
|
232
|
+
}
|
|
225
233
|
let page = sourcePage;
|
|
226
234
|
let scopedResolution = null;
|
|
227
235
|
const { url, title } = await syncSessionPage(session, pageRef, sourcePage);
|
package/dist/commands/launch.js
CHANGED
|
@@ -9,7 +9,7 @@ import { findChromePid } from '../stagehand.js';
|
|
|
9
9
|
import { applyAgentpayGatewayEnv, tryResolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
|
|
10
10
|
import { connectPlaywright, disconnectPlaywright, syncLaunchPage } from '../playwright-runtime.js';
|
|
11
11
|
import { summarizeSecretCatalog, syncSecretCatalogForUrl } from '../secrets/catalog-sync.js';
|
|
12
|
-
import { terminateOwnedPid } from '../owned-process.js';
|
|
12
|
+
import { cleanupManagedBrowserPids, terminateOwnedPid } from '../owned-process.js';
|
|
13
13
|
import { info } from '../output.js';
|
|
14
14
|
const DEFAULT_PROFILE = 'default';
|
|
15
15
|
const COMPACT_WINDOW = {
|
|
@@ -30,12 +30,22 @@ export async function launch(url, opts) {
|
|
|
30
30
|
}
|
|
31
31
|
async function cleanupOwnedSession() {
|
|
32
32
|
const existingSession = loadSession();
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const excludePids = new Set();
|
|
34
|
+
if (isOwnedSession(existingSession)) {
|
|
35
|
+
excludePids.add(existingSession.pid);
|
|
36
|
+
const termination = await terminateOwnedPid(existingSession.pid);
|
|
37
|
+
if (termination === 'still_alive') {
|
|
38
|
+
return buildLaunchFailure(new Error(`Existing owned browser pid ${existingSession.pid} did not exit after SIGTERM/SIGKILL.`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Phase-1 ownership removed port-global prekill, so launch needs a second guardrail
|
|
42
|
+
// for crashes that happen before a session record is persisted or cleaned up.
|
|
43
|
+
const orphanCleanup = await cleanupManagedBrowserPids({ excludePids });
|
|
44
|
+
if (orphanCleanup.blocked.length > 0) {
|
|
45
|
+
return buildLaunchFailure(new Error(`Orphaned managed browser pid${orphanCleanup.blocked.length === 1 ? '' : 's'} ${orphanCleanup.blocked.join(', ')} did not exit after SIGTERM/SIGKILL.`));
|
|
35
46
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return buildLaunchFailure(new Error(`Existing owned browser pid ${existingSession.pid} did not exit after SIGTERM/SIGKILL.`));
|
|
47
|
+
if (orphanCleanup.terminated.length > 0) {
|
|
48
|
+
info(`[launch] cleaned up orphaned managed browser pid${orphanCleanup.terminated.length === 1 ? '' : 's'} ${orphanCleanup.terminated.join(', ')}`);
|
|
39
49
|
}
|
|
40
50
|
return null;
|
|
41
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigate.d.ts","sourceRoot":"","sources":["../../src/commands/navigate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"navigate.d.ts","sourceRoot":"","sources":["../../src/commands/navigate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAgBnD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDvF"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse navigate <url> — Navigate the current tab to a URL.
|
|
3
3
|
*/
|
|
4
|
-
import { clearProtectedExposure } from '../runtime-state.js';
|
|
4
|
+
import { clearProtectedExposure, setCurrentPage } from '../runtime-state.js';
|
|
5
5
|
import { saveSession } from '../session.js';
|
|
6
6
|
import { outputContractFailure, outputJSON, info } from '../output.js';
|
|
7
7
|
import { summarizeSecretCatalog, syncSecretCatalogForUrl, tryResolveCatalogHost, } from '../secrets/catalog-sync.js';
|
|
8
|
-
import { connectPlaywright, disconnectPlaywright,
|
|
8
|
+
import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, syncSessionPage, } from '../playwright-runtime.js';
|
|
9
9
|
export async function navigate(session, targetUrl) {
|
|
10
10
|
let browser = null;
|
|
11
11
|
let failureMessage = null;
|
|
@@ -22,18 +22,18 @@ export async function navigate(session, targetUrl) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
try {
|
|
25
|
-
|
|
25
|
+
let pageRef = session.runtime?.currentPageRef ?? 'p0';
|
|
26
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
27
|
+
pageRef = resolvedPage.pageRef;
|
|
28
|
+
const page = resolvedPage.page;
|
|
26
29
|
const previousUrl = session.runtime?.pages?.[pageRef]?.url;
|
|
27
30
|
const previousHost = tryResolveCatalogHost(previousUrl);
|
|
28
|
-
const pages = listPages(browser);
|
|
29
|
-
const page = session.runtime?.pages[pageRef] || pages.length > 1
|
|
30
|
-
? await resolvePageByRef(browser, session, pageRef)
|
|
31
|
-
: pages[0];
|
|
32
31
|
if (!page) {
|
|
33
32
|
throw new Error('no_open_pages');
|
|
34
33
|
}
|
|
35
34
|
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
|
36
35
|
const { url, title } = await syncSessionPage(session, pageRef, page);
|
|
36
|
+
setCurrentPage(session, pageRef);
|
|
37
37
|
clearProtectedExposure(session, pageRef);
|
|
38
38
|
const nextHost = tryResolveCatalogHost(url);
|
|
39
39
|
if (nextHost && nextHost !== previousHost) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../src/commands/observe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../src/commands/observe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA0DnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAU/D,eAAO,MAAM,yBAAyB;;;;iBA6T7B,CAAC;gBAEE,CAAA;;;;CA/TyD,CAAC;AACtE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGrC,CAAC;AAEF,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8TzF"}
|
package/dist/commands/observe.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* browse observe ["<instruction>"] — Discover available actions on the page.
|
|
3
3
|
*/
|
|
4
4
|
import { saveSession } from '../session.js';
|
|
5
|
-
import { ensureRuntimeState, incrementMetric, replaceTargetsForPage } from '../runtime-state.js';
|
|
6
|
-
import { connectPlaywright, disconnectPlaywright,
|
|
5
|
+
import { ensureRuntimeState, incrementMetric, replaceTargetsForPage, setCurrentPage, } from '../runtime-state.js';
|
|
6
|
+
import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, syncSessionPage, } from '../playwright-runtime.js';
|
|
7
7
|
import { outputContractFailure, outputJSON } from '../output.js';
|
|
8
8
|
import { domRuntimeResolution, stagehandRuntimeResolution } from '../runtime-resolution.js';
|
|
9
9
|
import { withStagehand } from '../stagehand-runtime.js';
|
|
@@ -26,7 +26,7 @@ export const __testStagehandDescriptor = {
|
|
|
26
26
|
};
|
|
27
27
|
export async function observe(session, instruction) {
|
|
28
28
|
const runtime = ensureRuntimeState(session);
|
|
29
|
-
|
|
29
|
+
let pageRef = runtime.currentPageRef;
|
|
30
30
|
let domPassError = null;
|
|
31
31
|
let stagehandFallbackReason = null;
|
|
32
32
|
let browser = null;
|
|
@@ -34,8 +34,11 @@ export async function observe(session, instruction) {
|
|
|
34
34
|
if (!instruction) {
|
|
35
35
|
try {
|
|
36
36
|
browser = await connectPlaywright(session.cdpUrl);
|
|
37
|
-
const
|
|
37
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
38
|
+
pageRef = resolvedPage.pageRef;
|
|
39
|
+
const page = resolvedPage.page;
|
|
38
40
|
const { url, title } = await syncSessionPage(session, pageRef, page);
|
|
41
|
+
setCurrentPage(session, pageRef);
|
|
39
42
|
const collectedTargets = await collectDomTargets(page);
|
|
40
43
|
let observeAccessibilityStats;
|
|
41
44
|
const domTargets = compressSemanticallyDuplicateTargets(orderBySurfaceCompetition(annotateDomTargets(await enrichDomTargetsWithAccessibility(page, collectedTargets, {
|
|
@@ -96,8 +99,11 @@ export async function observe(session, instruction) {
|
|
|
96
99
|
if (!browser) {
|
|
97
100
|
browser = await connectPlaywright(session.cdpUrl);
|
|
98
101
|
}
|
|
99
|
-
const
|
|
102
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
103
|
+
pageRef = resolvedPage.pageRef;
|
|
104
|
+
const page = resolvedPage.page;
|
|
100
105
|
const { url, title } = await syncSessionPage(session, pageRef, page);
|
|
106
|
+
setCurrentPage(session, pageRef);
|
|
101
107
|
const collectedTargets = await collectDomTargets(page, {
|
|
102
108
|
includeActivationAffordances: true,
|
|
103
109
|
});
|
|
@@ -198,8 +204,11 @@ export async function observe(session, instruction) {
|
|
|
198
204
|
});
|
|
199
205
|
}
|
|
200
206
|
try {
|
|
201
|
-
const
|
|
207
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
208
|
+
pageRef = resolvedPage.pageRef;
|
|
209
|
+
const page = resolvedPage.page;
|
|
202
210
|
const { url, title } = await syncSessionPage(session, pageRef, page);
|
|
211
|
+
setCurrentPage(session, pageRef);
|
|
203
212
|
const signals = compactSignals(await collectPageSignals(page).catch(() => []));
|
|
204
213
|
const actions = await withStagehand(session, async (stagehand) => {
|
|
205
214
|
incrementMetric(session, 'stagehandCalls');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/commands/screenshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/commands/screenshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAWnD,wBAAsB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmEzF"}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse screenshot [--path <file>] — Capture a screenshot of the current page.
|
|
3
3
|
*/
|
|
4
|
-
import { getProtectedExposure } from '../runtime-state.js';
|
|
4
|
+
import { getProtectedExposure, setCurrentPage } from '../runtime-state.js';
|
|
5
5
|
import { saveSession } from '../session.js';
|
|
6
6
|
import { outputFailure, outputJSON, outputContractFailure, info } from '../output.js';
|
|
7
|
-
import { connectPlaywright, disconnectPlaywright,
|
|
7
|
+
import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, syncSessionPage, } from '../playwright-runtime.js';
|
|
8
8
|
import { buildProtectedScreenshotBlockedResult } from '../secrets/protected-artifact-guard.js';
|
|
9
9
|
export async function screenshot(session, filePath) {
|
|
10
10
|
const outputPath = filePath ?? `/tmp/browse-screenshot-${Date.now()}.png`;
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
return outputFailure(buildProtectedScreenshotBlockedResult(
|
|
11
|
+
let pageRef = session.runtime?.currentPageRef ?? 'p0';
|
|
12
|
+
const initialProtectedExposure = getProtectedExposure(session, pageRef);
|
|
13
|
+
if (initialProtectedExposure) {
|
|
14
|
+
return outputFailure(buildProtectedScreenshotBlockedResult(initialProtectedExposure));
|
|
15
15
|
}
|
|
16
16
|
let browser = null;
|
|
17
17
|
let failureMessage = null;
|
|
18
|
+
let page = null;
|
|
18
19
|
try {
|
|
19
20
|
browser = await connectPlaywright(session.cdpUrl);
|
|
20
21
|
}
|
|
@@ -27,16 +28,29 @@ export async function screenshot(session, filePath) {
|
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const resolvedPage = await resolveCurrentPageContext(browser, session);
|
|
32
|
+
pageRef = resolvedPage.pageRef;
|
|
33
|
+
page = resolvedPage.page;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
failureMessage = `Screenshot failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
37
|
+
}
|
|
38
|
+
const recoveredProtectedExposure = getProtectedExposure(session, pageRef);
|
|
39
|
+
if (recoveredProtectedExposure) {
|
|
40
|
+
if (browser) {
|
|
41
|
+
disconnectPlaywright(browser);
|
|
42
|
+
browser = null;
|
|
43
|
+
}
|
|
44
|
+
return outputFailure(buildProtectedScreenshotBlockedResult(recoveredProtectedExposure));
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
34
47
|
if (!page) {
|
|
35
|
-
throw new Error('no_open_pages');
|
|
48
|
+
throw new Error(failureMessage?.replace(/^Screenshot failed:\s*/, '') ?? 'no_open_pages');
|
|
36
49
|
}
|
|
37
50
|
await page.screenshot({ path: outputPath });
|
|
38
51
|
info(`Screenshot saved: ${outputPath}`);
|
|
39
52
|
const { url, title } = await syncSessionPage(session, pageRef, page);
|
|
53
|
+
setCurrentPage(session, pageRef);
|
|
40
54
|
saveSession(session);
|
|
41
55
|
outputJSON({ success: true, pageRef, path: outputPath, url, title });
|
|
42
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmKH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAgG5C"}
|
package/dist/commands/status.js
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { loadSession, isSessionAlive, getSessionPort, supportsCaptchaSolve } from '../session.js';
|
|
5
5
|
import { outputJSON } from '../output.js';
|
|
6
|
+
function normalizeComparableUrl(url) {
|
|
7
|
+
const raw = url?.trim();
|
|
8
|
+
if (!raw) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(raw);
|
|
13
|
+
parsed.hash = '';
|
|
14
|
+
parsed.search = '';
|
|
15
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
function tryResolveHost(url) {
|
|
7
22
|
if (!url) {
|
|
8
23
|
return undefined;
|
|
@@ -23,6 +38,7 @@ function buildProtectedStatusPayload(params) {
|
|
|
23
38
|
? { captchaSolveCapable: params.captchaSolveCapable }
|
|
24
39
|
: {}),
|
|
25
40
|
runtime: params.runtimeSummary,
|
|
41
|
+
...(params.currentPageMismatch ? { currentPageMismatch: params.currentPageMismatch } : {}),
|
|
26
42
|
protectedExposureActive: true,
|
|
27
43
|
pageRef: params.protectedExposure.pageRef,
|
|
28
44
|
url: params.pageUrl ?? 'unknown',
|
|
@@ -36,6 +52,70 @@ function buildProtectedStatusPayload(params) {
|
|
|
36
52
|
reason: 'AgentBrowse is treating the current page as sensitive because a protected fill was executed and values may still be visible.',
|
|
37
53
|
};
|
|
38
54
|
}
|
|
55
|
+
function matchesStoredPage(page, livePage) {
|
|
56
|
+
const storedUrl = normalizeComparableUrl(page?.url);
|
|
57
|
+
const currentUrl = normalizeComparableUrl(livePage.url);
|
|
58
|
+
if (!storedUrl || !currentUrl || storedUrl !== currentUrl) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (page?.title && livePage.title && page.title === livePage.title) {
|
|
62
|
+
return 'url-title-match';
|
|
63
|
+
}
|
|
64
|
+
return 'url-match';
|
|
65
|
+
}
|
|
66
|
+
function resolveCurrentPageMismatch(session, livePage) {
|
|
67
|
+
const runtime = session?.runtime;
|
|
68
|
+
if (!runtime || !livePage) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const persistedPageRef = runtime.currentPageRef;
|
|
72
|
+
const currentPage = runtime.pages[persistedPageRef];
|
|
73
|
+
if (!currentPage) {
|
|
74
|
+
return {
|
|
75
|
+
persistedPageRef,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (matchesStoredPage(currentPage, livePage)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const openerPageRef = currentPage.openerPageRef;
|
|
82
|
+
if (openerPageRef && matchesStoredPage(runtime.pages[openerPageRef], livePage)) {
|
|
83
|
+
return {
|
|
84
|
+
persistedPageRef,
|
|
85
|
+
livePageRef: openerPageRef,
|
|
86
|
+
recoveredVia: 'opener',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const exactMatches = Object.values(runtime.pages).filter((page) => {
|
|
90
|
+
if (page.pageRef === persistedPageRef || page.pageRef === openerPageRef) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return matchesStoredPage(page, livePage) === 'url-title-match';
|
|
94
|
+
});
|
|
95
|
+
if (exactMatches.length === 1) {
|
|
96
|
+
return {
|
|
97
|
+
persistedPageRef,
|
|
98
|
+
livePageRef: exactMatches[0].pageRef,
|
|
99
|
+
recoveredVia: 'url-title-match',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const urlOnlyMatches = Object.values(runtime.pages).filter((page) => {
|
|
103
|
+
if (page.pageRef === persistedPageRef || page.pageRef === openerPageRef) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return matchesStoredPage(page, livePage) === 'url-match';
|
|
107
|
+
});
|
|
108
|
+
if (urlOnlyMatches.length === 1) {
|
|
109
|
+
return {
|
|
110
|
+
persistedPageRef,
|
|
111
|
+
livePageRef: urlOnlyMatches[0].pageRef,
|
|
112
|
+
recoveredVia: 'url-match',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
persistedPageRef,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
39
119
|
export async function status() {
|
|
40
120
|
const session = loadSession();
|
|
41
121
|
const port = getSessionPort(session);
|
|
@@ -57,15 +137,17 @@ export async function status() {
|
|
|
57
137
|
const listRes = await fetch(`http://localhost:${port}/json/list`);
|
|
58
138
|
const targets = (await listRes.json());
|
|
59
139
|
const page = targets.find((t) => t.type === 'page' && !t.url.startsWith('devtools://'));
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
140
|
+
const currentPageMismatch = resolveCurrentPageMismatch(session, page);
|
|
141
|
+
const effectivePageRef = currentPageMismatch?.livePageRef ?? session?.runtime?.currentPageRef;
|
|
142
|
+
const protectedExposure = effectivePageRef
|
|
143
|
+
? session?.runtime?.protectedExposureByPage?.[effectivePageRef]
|
|
63
144
|
: undefined;
|
|
64
145
|
if (protectedExposure) {
|
|
65
146
|
outputJSON(buildProtectedStatusPayload({
|
|
66
147
|
runtimeSummary,
|
|
67
148
|
pageUrl: page?.url,
|
|
68
149
|
pageTitle: page?.title,
|
|
150
|
+
currentPageMismatch,
|
|
69
151
|
protectedExposure,
|
|
70
152
|
}));
|
|
71
153
|
return;
|
|
@@ -76,6 +158,7 @@ export async function status() {
|
|
|
76
158
|
url: page?.url ?? 'unknown',
|
|
77
159
|
title: page?.title ?? 'unknown',
|
|
78
160
|
runtime: runtimeSummary,
|
|
161
|
+
...(currentPageMismatch ? { currentPageMismatch } : {}),
|
|
79
162
|
});
|
|
80
163
|
return;
|
|
81
164
|
}
|
|
@@ -90,9 +173,10 @@ export async function status() {
|
|
|
90
173
|
const listRes = await fetch(`http://localhost:${port}/json/list`);
|
|
91
174
|
const targets = (await listRes.json());
|
|
92
175
|
const page = targets.find((t) => t.type === 'page' && !t.url.startsWith('devtools://'));
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
176
|
+
const currentPageMismatch = resolveCurrentPageMismatch(session, page);
|
|
177
|
+
const effectivePageRef = currentPageMismatch?.livePageRef ?? session.runtime?.currentPageRef;
|
|
178
|
+
const protectedExposure = effectivePageRef
|
|
179
|
+
? session.runtime?.protectedExposureByPage?.[effectivePageRef]
|
|
96
180
|
: undefined;
|
|
97
181
|
if (protectedExposure) {
|
|
98
182
|
outputJSON(buildProtectedStatusPayload({
|
|
@@ -100,6 +184,7 @@ export async function status() {
|
|
|
100
184
|
pageUrl: page?.url,
|
|
101
185
|
pageTitle: page?.title,
|
|
102
186
|
captchaSolveCapable: supportsCaptchaSolve(session),
|
|
187
|
+
currentPageMismatch,
|
|
103
188
|
protectedExposure,
|
|
104
189
|
}));
|
|
105
190
|
return;
|
|
@@ -111,6 +196,7 @@ export async function status() {
|
|
|
111
196
|
url: page?.url ?? 'unknown',
|
|
112
197
|
title: page?.title ?? 'unknown',
|
|
113
198
|
runtime: runtimeSummary,
|
|
199
|
+
...(currentPageMismatch ? { currentPageMismatch } : {}),
|
|
114
200
|
});
|
|
115
201
|
return;
|
|
116
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAyCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAsCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAyCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAsCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,CAoG/F;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,uBAAuB,CAyBzB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,mBAAmB,GAAG,SAAS,CA0EjC;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,sBAAsB,GAAG,SAAS,CAiDpC"}
|
|
@@ -148,7 +148,12 @@ export function inferAllowedActionsFromFacts(facts) {
|
|
|
148
148
|
actions.add('click');
|
|
149
149
|
actions.add('press');
|
|
150
150
|
}
|
|
151
|
-
if (kind === 'button' ||
|
|
151
|
+
if (kind === 'button' ||
|
|
152
|
+
kind === 'link' ||
|
|
153
|
+
kind === 'tab' ||
|
|
154
|
+
role === 'button' ||
|
|
155
|
+
role === 'link' ||
|
|
156
|
+
role === 'tab') {
|
|
152
157
|
actions.add('click');
|
|
153
158
|
actions.add('press');
|
|
154
159
|
}
|
package/dist/owned-process.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
export type OwnedPidTerminationResult = 'not_found' | 'terminated' | 'sigkilled' | 'still_alive';
|
|
2
|
+
export type ManagedBrowserCleanupResult = {
|
|
3
|
+
terminated: number[];
|
|
4
|
+
blocked: number[];
|
|
5
|
+
};
|
|
2
6
|
export declare function isPidAlive(pid: number): boolean;
|
|
3
7
|
export declare function terminateOwnedPid(pid: number): Promise<OwnedPidTerminationResult>;
|
|
8
|
+
export declare function cleanupManagedBrowserPids(options?: {
|
|
9
|
+
excludePids?: Iterable<number>;
|
|
10
|
+
}): Promise<ManagedBrowserCleanupResult>;
|
|
11
|
+
export declare function listManagedBrowserPids(options?: {
|
|
12
|
+
processTable?: string;
|
|
13
|
+
}): number[];
|
|
4
14
|
//# sourceMappingURL=owned-process.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owned-process.d.ts","sourceRoot":"","sources":["../src/owned-process.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"owned-process.d.ts","sourceRoot":"","sources":["../src/owned-process.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,yBAAyB,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,aAAa,CAAC;AACjG,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW/C;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsBvF;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,OAAO,CAAC,2BAA2B,CAAC,CAqBtC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,MAAM,EAAE,CAuBV"}
|
package/dist/owned-process.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
1
4
|
const TERM_WAIT_ATTEMPTS = 10;
|
|
2
5
|
const TERM_WAIT_MS = 100;
|
|
3
6
|
const KILL_WAIT_ATTEMPTS = 5;
|
|
4
7
|
const KILL_WAIT_MS = 20;
|
|
8
|
+
const MANAGED_PROFILE_ROOTS = [
|
|
9
|
+
path.join(homedir(), '.agentpay', 'agentbrowse', 'profiles'),
|
|
10
|
+
path.join(homedir(), '.agentpay', 'solver', 'profiles'),
|
|
11
|
+
];
|
|
5
12
|
export function isPidAlive(pid) {
|
|
6
13
|
try {
|
|
7
14
|
process.kill(pid, 0);
|
|
@@ -36,6 +43,45 @@ export async function terminateOwnedPid(pid) {
|
|
|
36
43
|
}
|
|
37
44
|
return 'still_alive';
|
|
38
45
|
}
|
|
46
|
+
export async function cleanupManagedBrowserPids(options = {}) {
|
|
47
|
+
const excludePids = new Set(options.excludePids ?? []);
|
|
48
|
+
const terminated = [];
|
|
49
|
+
const blocked = [];
|
|
50
|
+
for (const pid of listManagedBrowserPids()) {
|
|
51
|
+
if (excludePids.has(pid)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const result = await terminateOwnedPid(pid);
|
|
55
|
+
if (result === 'still_alive') {
|
|
56
|
+
blocked.push(pid);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (result !== 'not_found') {
|
|
60
|
+
terminated.push(pid);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { terminated, blocked };
|
|
64
|
+
}
|
|
65
|
+
export function listManagedBrowserPids(options = {}) {
|
|
66
|
+
const processTable = options.processTable ?? readProcessTable();
|
|
67
|
+
const pids = new Set();
|
|
68
|
+
for (const line of processTable.split(/\r?\n/)) {
|
|
69
|
+
const match = line.trimStart().match(/^(\d+)\s+(.*)$/);
|
|
70
|
+
if (!match) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const pid = Number(match[1]);
|
|
74
|
+
const command = match[2] ?? '';
|
|
75
|
+
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!isManagedBrowserCommand(command)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
pids.add(pid);
|
|
82
|
+
}
|
|
83
|
+
return Array.from(pids);
|
|
84
|
+
}
|
|
39
85
|
async function waitForPidExit(pid, attempts, intervalMs) {
|
|
40
86
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
41
87
|
if (!isPidAlive(pid)) {
|
|
@@ -55,3 +101,31 @@ function getErrorCode(error) {
|
|
|
55
101
|
function sleep(ms) {
|
|
56
102
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
103
|
}
|
|
104
|
+
function readProcessTable() {
|
|
105
|
+
try {
|
|
106
|
+
return execFileSync('ps', ['-Ao', 'pid=,command='], { encoding: 'utf-8' });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function isManagedBrowserCommand(command) {
|
|
113
|
+
if (!command.includes('--remote-debugging-address=127.0.0.1')) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const userDataDir = readCommandFlag(command, '--user-data-dir');
|
|
117
|
+
if (!userDataDir) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const normalizedUserDataDir = path.resolve(userDataDir);
|
|
121
|
+
return MANAGED_PROFILE_ROOTS.some((root) => isWithinRoot(normalizedUserDataDir, root));
|
|
122
|
+
}
|
|
123
|
+
function readCommandFlag(command, flag) {
|
|
124
|
+
const escapedFlag = flag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
125
|
+
const match = command.match(new RegExp(`${escapedFlag}=(?:"([^"]+)"|'([^']+)'|(\\S+))`));
|
|
126
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
127
|
+
}
|
|
128
|
+
function isWithinRoot(candidatePath, rootPath) {
|
|
129
|
+
const relative = path.relative(rootPath, candidatePath);
|
|
130
|
+
return relative.length > 0 && !relative.startsWith(`..${path.sep}`) && relative !== '..';
|
|
131
|
+
}
|
|
@@ -9,6 +9,11 @@ type LaunchPageMetadata = {
|
|
|
9
9
|
title: string;
|
|
10
10
|
targetId?: string;
|
|
11
11
|
};
|
|
12
|
+
export type ResolvedCurrentPageContext = {
|
|
13
|
+
pageRef: string;
|
|
14
|
+
page: Page;
|
|
15
|
+
recoveredVia?: 'opener' | 'sole-live-page';
|
|
16
|
+
};
|
|
12
17
|
export declare function readLaunchPageMetadata(browser: Browser, options?: {
|
|
13
18
|
requestedUrl?: string;
|
|
14
19
|
fallbackUrl?: string;
|
|
@@ -26,6 +31,7 @@ export declare function syncLaunchPage(session: BrowseSession, browser: Browser,
|
|
|
26
31
|
targetId?: string;
|
|
27
32
|
}>;
|
|
28
33
|
export declare function resolvePageByRef(browser: Browser, session: BrowseSession, pageRef: string): Promise<Page>;
|
|
34
|
+
export declare function resolveCurrentPageContext(browser: Browser, session: BrowseSession): Promise<ResolvedCurrentPageContext>;
|
|
29
35
|
export declare function syncSessionPage(session: BrowseSession, pageRef: string, page: Page, options?: {
|
|
30
36
|
settleTimeoutMs?: number;
|
|
31
37
|
}): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-runtime.d.ts","sourceRoot":"","sources":["../src/playwright-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAExE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAO3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,EAAE,CAElD;
|
|
1
|
+
{"version":3,"file":"playwright-runtime.d.ts","sourceRoot":"","sources":["../src/playwright-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAExE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAO3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,EAAE,CAElD;AA8CD,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAQF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,CAAC,EAAE,QAAQ,GAAG,gBAAgB,CAAC;CAC5C,CAAC;AA4IF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,GACL,OAAO,CAAC,kBAAkB,CAAC,CA+C7B;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,GACL,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC5D;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA8Cf;AA+CD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,0BAA0B,CAAC,CAqCrC;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB,GACL,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB5D"}
|
|
@@ -17,6 +17,10 @@ export function listPages(browser) {
|
|
|
17
17
|
function buildPageResolutionError(kind, pageRef) {
|
|
18
18
|
return new Error(`${kind === 'unknown' ? 'unknown_page_ref' : 'stale_page_ref'}:${pageRef}`);
|
|
19
19
|
}
|
|
20
|
+
function isPageResolutionError(error, kind, pageRef) {
|
|
21
|
+
return (error instanceof Error &&
|
|
22
|
+
error.message === `${kind === 'unknown' ? 'unknown_page_ref' : 'stale_page_ref'}:${pageRef}`);
|
|
23
|
+
}
|
|
20
24
|
function sleep(ms) {
|
|
21
25
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
26
|
}
|
|
@@ -250,6 +254,78 @@ export async function resolvePageByRef(browser, session, pageRef) {
|
|
|
250
254
|
}
|
|
251
255
|
throw buildPageResolutionError('stale', pageRef);
|
|
252
256
|
}
|
|
257
|
+
async function findRecoverableCurrentPageRef(browser, session, stalePageRef) {
|
|
258
|
+
const runtimePages = session.runtime?.pages ?? {};
|
|
259
|
+
const stalePageState = runtimePages[stalePageRef];
|
|
260
|
+
const openerPageRef = stalePageState?.openerPageRef;
|
|
261
|
+
if (openerPageRef && openerPageRef !== stalePageRef) {
|
|
262
|
+
try {
|
|
263
|
+
await resolvePageByRef(browser, session, openerPageRef);
|
|
264
|
+
return {
|
|
265
|
+
pageRef: openerPageRef,
|
|
266
|
+
recoveredVia: 'opener',
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Keep recovery fail-closed unless opener or a single live alternative is provably valid.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const liveCandidateRefs = [];
|
|
274
|
+
for (const candidatePageRef of Object.keys(runtimePages)) {
|
|
275
|
+
if (candidatePageRef === stalePageRef || candidatePageRef === openerPageRef) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
await resolvePageByRef(browser, session, candidatePageRef);
|
|
280
|
+
liveCandidateRefs.push(candidatePageRef);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// Ignore stale or unknown candidates while looking for a single surviving live page.
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (liveCandidateRefs.length === 1) {
|
|
287
|
+
return {
|
|
288
|
+
pageRef: liveCandidateRefs[0],
|
|
289
|
+
recoveredVia: 'sole-live-page',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
export async function resolveCurrentPageContext(browser, session) {
|
|
295
|
+
const currentPageRef = session.runtime?.currentPageRef ?? 'p0';
|
|
296
|
+
try {
|
|
297
|
+
const page = await resolvePageByRef(browser, session, currentPageRef);
|
|
298
|
+
return {
|
|
299
|
+
pageRef: currentPageRef,
|
|
300
|
+
page,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (isPageResolutionError(error, 'unknown', currentPageRef)) {
|
|
305
|
+
const pages = listPages(browser);
|
|
306
|
+
if (pages.length === 1) {
|
|
307
|
+
return {
|
|
308
|
+
pageRef: currentPageRef,
|
|
309
|
+
page: pages[0],
|
|
310
|
+
recoveredVia: 'sole-live-page',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
if (!isPageResolutionError(error, 'stale', currentPageRef)) {
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
const recovered = await findRecoverableCurrentPageRef(browser, session, currentPageRef);
|
|
319
|
+
if (!recovered) {
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
pageRef: recovered.pageRef,
|
|
324
|
+
page: await resolvePageByRef(browser, session, recovered.pageRef),
|
|
325
|
+
recoveredVia: recovered.recoveredVia,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
253
329
|
export async function syncSessionPage(session, pageRef, page, options = {}) {
|
|
254
330
|
ensureRuntimeState(session);
|
|
255
331
|
const { url, title, targetId } = await readSettledSessionPageMetadata(session, pageRef, page, options);
|
|
@@ -90,14 +90,9 @@ async function discoverCdpUrl(options) {
|
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
92
|
const activePort = readDevToolsActivePort(activePortPath);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (activePort?.port) {
|
|
97
|
-
const cdpUrl = await discoverCdpUrlOnPort(activePort.port);
|
|
98
|
-
if (cdpUrl) {
|
|
99
|
-
return cdpUrl;
|
|
100
|
-
}
|
|
93
|
+
const discoveredViaActivePort = await discoverCdpUrlFromActivePort(activePort);
|
|
94
|
+
if (discoveredViaActivePort) {
|
|
95
|
+
return discoveredViaActivePort;
|
|
101
96
|
}
|
|
102
97
|
}
|
|
103
98
|
await sleep(CDP_DISCOVERY_INTERVAL_MS);
|
|
@@ -117,6 +112,38 @@ async function discoverCdpUrlOnPort(port) {
|
|
|
117
112
|
return null;
|
|
118
113
|
}
|
|
119
114
|
}
|
|
115
|
+
async function discoverCdpUrlFromActivePort(activePort) {
|
|
116
|
+
if (!activePort) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const candidatePorts = new Set();
|
|
120
|
+
if (activePort.browserWSEndpoint) {
|
|
121
|
+
const wsPort = portFromBrowserWSEndpoint(activePort.browserWSEndpoint);
|
|
122
|
+
if (wsPort) {
|
|
123
|
+
candidatePorts.add(wsPort);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (activePort.port > 0) {
|
|
127
|
+
candidatePorts.add(activePort.port);
|
|
128
|
+
}
|
|
129
|
+
for (const port of candidatePorts) {
|
|
130
|
+
const cdpUrl = await discoverCdpUrlOnPort(port);
|
|
131
|
+
if (cdpUrl) {
|
|
132
|
+
return cdpUrl;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function portFromBrowserWSEndpoint(browserWSEndpoint) {
|
|
138
|
+
try {
|
|
139
|
+
const url = new URL(browserWSEndpoint);
|
|
140
|
+
const port = Number(url.port);
|
|
141
|
+
return Number.isFinite(port) && port > 0 ? port : null;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
120
147
|
function readDevToolsActivePort(activePortPath) {
|
|
121
148
|
if (!existsSync(activePortPath)) {
|
|
122
149
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuanu-ai/agentbrowse",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser automation CLI for AI agents: control a CDP browser, observe UI surfaces, act on refs, extract data, capture screenshots, complete protected fills, and solve captchas",
|
|
6
6
|
"keywords": [
|