@nuanu-ai/agentbrowse 0.2.22 → 0.2.24
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 +10 -0
- 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/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/close.d.ts +12 -1
- package/dist/commands/close.d.ts.map +1 -1
- package/dist/commands/close.js +19 -21
- package/dist/commands/launch.d.ts +22 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +131 -58
- package/dist/control-semantics.d.ts.map +1 -1
- package/dist/control-semantics.js +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -9
- package/dist/owned-process.d.ts +14 -0
- package/dist/owned-process.d.ts.map +1 -0
- package/dist/owned-process.js +131 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -7
- package/dist/solver/browser-launcher.js +35 -8
- package/dist/solver/types.d.ts +2 -0
- package/dist/solver/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,16 @@ agentbrowse launch https://example.com
|
|
|
47
47
|
agentbrowse launch https://example.com --headless
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
By default, `launch` runs in headful mode. Use `--headless` only when you
|
|
51
|
+
intentionally want a hidden browser:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
agentbrowse launch https://example.com --headless
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you want to state the default mode explicitly in scripts, use
|
|
58
|
+
`--headful`.
|
|
59
|
+
|
|
50
60
|
Navigate current session:
|
|
51
61
|
|
|
52
62
|
```bash
|
|
@@ -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,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
|
+
}
|
package/dist/commands/close.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse close — Close the browser and clean up session.
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export type CloseSuccessResult = {
|
|
5
|
+
success: true;
|
|
6
|
+
};
|
|
7
|
+
export type CloseFailureResult = {
|
|
8
|
+
success: false;
|
|
9
|
+
error: 'browser_close_failed';
|
|
10
|
+
outcomeType: 'blocked';
|
|
11
|
+
message: 'Browser close failed.';
|
|
12
|
+
reason: string;
|
|
13
|
+
};
|
|
14
|
+
export type CloseResult = CloseSuccessResult | CloseFailureResult;
|
|
15
|
+
export declare function close(): Promise<CloseResult>;
|
|
5
16
|
//# sourceMappingURL=close.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,sBAAsB,CAAC;IAC9B,WAAW,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAMlE,wBAAsB,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAwBlD"}
|
package/dist/commands/close.js
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse close — Close the browser and clean up session.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { loadSession, deleteSession } from '../session.js';
|
|
5
|
+
import { info } from '../output.js';
|
|
6
|
+
import { terminateOwnedPid } from '../owned-process.js';
|
|
7
|
+
function isOwnedSessionRecord(session) {
|
|
8
|
+
return session?.identity?.ownership === 'agentbrowse';
|
|
9
|
+
}
|
|
7
10
|
export async function close() {
|
|
8
11
|
const session = loadSession();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
info(`Killed Chrome on port ${port}`);
|
|
12
|
+
if (isOwnedSessionRecord(session)) {
|
|
13
|
+
const termination = await terminateOwnedPid(session.pid);
|
|
14
|
+
if (termination === 'still_alive') {
|
|
15
|
+
info(`Owned browser pid ${session.pid} did not exit; keeping session record`);
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
error: 'browser_close_failed',
|
|
19
|
+
outcomeType: 'blocked',
|
|
20
|
+
message: 'Browser close failed.',
|
|
21
|
+
reason: `Owned browser pid ${session.pid} did not exit after SIGTERM/SIGKILL.`,
|
|
22
|
+
};
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
catch {
|
|
26
|
-
/* no process on port */
|
|
24
|
+
info(`Killed owned browser pid ${session.pid}`);
|
|
27
25
|
}
|
|
28
26
|
if (session) {
|
|
29
27
|
deleteSession();
|
|
30
28
|
}
|
|
31
|
-
|
|
29
|
+
return { success: true };
|
|
32
30
|
}
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse launch [url] — Start browser session, optionally navigate.
|
|
3
3
|
*/
|
|
4
|
+
import type { SecretCatalogSummary } from '../secrets/catalog-sync.js';
|
|
4
5
|
export type LaunchCliOptions = {
|
|
5
6
|
compact?: boolean;
|
|
6
7
|
profile?: string;
|
|
7
8
|
headless?: boolean;
|
|
9
|
+
proxy?: string;
|
|
10
|
+
noProxy?: boolean;
|
|
8
11
|
};
|
|
9
|
-
export
|
|
12
|
+
export type LaunchSuccessResult = {
|
|
13
|
+
success: true;
|
|
14
|
+
runtime: 'managed';
|
|
15
|
+
captchaSolveCapable: true;
|
|
16
|
+
profile: string;
|
|
17
|
+
cdpUrl: string;
|
|
18
|
+
url: string;
|
|
19
|
+
title: string;
|
|
20
|
+
secretCatalog?: SecretCatalogSummary;
|
|
21
|
+
};
|
|
22
|
+
export type LaunchFailureResult = {
|
|
23
|
+
success: false;
|
|
24
|
+
error: 'browser_launch_failed';
|
|
25
|
+
outcomeType: 'blocked';
|
|
26
|
+
message: 'Browser launch failed.';
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
export type LaunchResult = LaunchSuccessResult | LaunchFailureResult;
|
|
30
|
+
export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<LaunchResult>;
|
|
10
31
|
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAWvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;IACnB,mBAAmB,EAAE,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;IAC/B,WAAW,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAErE,wBAAsB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAazF"}
|
package/dist/commands/launch.js
CHANGED
|
@@ -1,92 +1,122 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* browse launch [url] — Start browser session, optionally navigate.
|
|
3
3
|
*/
|
|
4
|
-
import { execSync } from 'node:child_process';
|
|
5
4
|
import { readConfig } from '../solver/config.js';
|
|
6
5
|
import { ensureProfile } from '../solver/profile-manager.js';
|
|
7
6
|
import { launchSolver } from '../solver/browser-launcher.js';
|
|
8
|
-
import { saveSession } from '../session.js';
|
|
7
|
+
import { buildOwnedSession, isOwnedSession, loadSession, saveSession } from '../session.js';
|
|
9
8
|
import { findChromePid } from '../stagehand.js';
|
|
10
|
-
import { outputContractFailure, outputJSON } from '../output.js';
|
|
11
9
|
import { applyAgentpayGatewayEnv, tryResolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
|
|
12
10
|
import { connectPlaywright, disconnectPlaywright, syncLaunchPage } from '../playwright-runtime.js';
|
|
13
11
|
import { summarizeSecretCatalog, syncSecretCatalogForUrl } from '../secrets/catalog-sync.js';
|
|
14
|
-
|
|
12
|
+
import { cleanupManagedBrowserPids, terminateOwnedPid } from '../owned-process.js';
|
|
13
|
+
import { info } from '../output.js';
|
|
15
14
|
const DEFAULT_PROFILE = 'default';
|
|
16
15
|
const COMPACT_WINDOW = {
|
|
17
16
|
width: 1280,
|
|
18
17
|
height: 900,
|
|
19
18
|
};
|
|
20
19
|
export async function launch(url, opts) {
|
|
21
|
-
await
|
|
20
|
+
const cleanupFailure = await cleanupOwnedSession();
|
|
21
|
+
if (cleanupFailure) {
|
|
22
|
+
return cleanupFailure;
|
|
23
|
+
}
|
|
22
24
|
const compact = opts?.compact ?? true;
|
|
23
25
|
const profileName = opts?.profile ?? DEFAULT_PROFILE;
|
|
24
26
|
const headless = opts?.headless ?? false;
|
|
25
|
-
|
|
27
|
+
const proxyOverride = opts?.proxy;
|
|
28
|
+
const noProxy = opts?.noProxy ?? false;
|
|
29
|
+
return launchManaged(url, profileName, headless, compact, proxyOverride, noProxy);
|
|
26
30
|
}
|
|
27
|
-
async function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
catch {
|
|
36
|
-
// Skip stale PID.
|
|
31
|
+
async function cleanupOwnedSession() {
|
|
32
|
+
const existingSession = loadSession();
|
|
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.`));
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.`));
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (orphanCleanup.terminated.length > 0) {
|
|
48
|
+
info(`[launch] cleaned up orphaned managed browser pid${orphanCleanup.terminated.length === 1 ? '' : 's'} ${orphanCleanup.terminated.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function buildLaunchFailure(err) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: 'browser_launch_failed',
|
|
56
|
+
outcomeType: 'blocked',
|
|
57
|
+
message: 'Browser launch failed.',
|
|
58
|
+
reason: formatUnknownError(err),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getPortFromCdpUrl(cdpUrl) {
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(cdpUrl);
|
|
64
|
+
if (!url.port) {
|
|
65
|
+
return undefined;
|
|
50
66
|
}
|
|
67
|
+
const port = Number(url.port);
|
|
68
|
+
return Number.isFinite(port) && port > 0 ? port : undefined;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
|
-
async function launchManaged(url, profileName, headless, compact) {
|
|
74
|
+
async function launchManaged(url, profileName, headless, compact, proxyOverride, noProxy = false) {
|
|
54
75
|
let session;
|
|
55
76
|
let browser = null;
|
|
56
77
|
let secretCatalogSummary;
|
|
57
78
|
try {
|
|
58
|
-
const
|
|
79
|
+
const baseProfile = ensureProfile(profileName);
|
|
59
80
|
const config = readConfig();
|
|
81
|
+
const runtimeProxy = resolveLaunchProxy({
|
|
82
|
+
profileProxy: baseProfile.fingerprint.proxy,
|
|
83
|
+
configProxy: config.defaults?.proxy,
|
|
84
|
+
cliProxy: proxyOverride,
|
|
85
|
+
noProxy,
|
|
86
|
+
});
|
|
87
|
+
const profile = withLaunchProxy(baseProfile, runtimeProxy);
|
|
60
88
|
const gateway = tryResolveAgentpayGatewayConfig();
|
|
61
89
|
if (gateway) {
|
|
62
90
|
applyAgentpayGatewayEnv(gateway);
|
|
63
91
|
}
|
|
92
|
+
if (runtimeProxy) {
|
|
93
|
+
info(`[launch] starting browser with proxy ${runtimeProxy.server}`);
|
|
94
|
+
}
|
|
64
95
|
session = await launchSolver(profile, {
|
|
65
96
|
headless: headless || config.defaults?.headless,
|
|
66
97
|
url,
|
|
67
|
-
cdpPort:
|
|
98
|
+
cdpPort: undefined,
|
|
68
99
|
windowSize: compact ? COMPACT_WINDOW : undefined,
|
|
69
100
|
});
|
|
70
101
|
}
|
|
71
102
|
catch (err) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
return buildLaunchFailure(err);
|
|
104
|
+
}
|
|
105
|
+
const cdpPort = getPortFromCdpUrl(session.cdpUrl);
|
|
106
|
+
const chromePid = findChromePid(cdpPort);
|
|
107
|
+
if (!chromePid) {
|
|
108
|
+
await session.close().catch(() => undefined);
|
|
109
|
+
return buildLaunchFailure(new Error('Launched browser PID could not be resolved.'));
|
|
78
110
|
}
|
|
79
|
-
const
|
|
80
|
-
const persistedSession = {
|
|
111
|
+
const persistedSession = buildOwnedSession({
|
|
81
112
|
cdpUrl: session.cdpUrl,
|
|
82
113
|
pid: chromePid,
|
|
83
|
-
port: CDP_PORT,
|
|
84
114
|
profile: profileName,
|
|
85
115
|
launchedAt: new Date().toISOString(),
|
|
86
116
|
capabilities: {
|
|
87
117
|
captchaSolve: true,
|
|
88
118
|
},
|
|
89
|
-
};
|
|
119
|
+
});
|
|
90
120
|
saveSession(persistedSession);
|
|
91
121
|
let currentUrl = session.page.url();
|
|
92
122
|
let title = await session.page.title().catch(() => '');
|
|
@@ -111,10 +141,12 @@ async function launchManaged(url, profileName, headless, compact) {
|
|
|
111
141
|
}
|
|
112
142
|
if (url) {
|
|
113
143
|
const snapshot = await syncSecretCatalogForUrl(persistedSession, currentUrl || url);
|
|
114
|
-
|
|
144
|
+
if (snapshot) {
|
|
145
|
+
secretCatalogSummary = summarizeSecretCatalog(snapshot);
|
|
146
|
+
}
|
|
115
147
|
}
|
|
116
148
|
saveSession(persistedSession);
|
|
117
|
-
|
|
149
|
+
return {
|
|
118
150
|
success: true,
|
|
119
151
|
runtime: 'managed',
|
|
120
152
|
captchaSolveCapable: true,
|
|
@@ -123,21 +155,7 @@ async function launchManaged(url, profileName, headless, compact) {
|
|
|
123
155
|
url: currentUrl,
|
|
124
156
|
title,
|
|
125
157
|
secretCatalog: secretCatalogSummary,
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function getPidsOnPort(port) {
|
|
129
|
-
try {
|
|
130
|
-
const out = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
|
|
131
|
-
if (!out)
|
|
132
|
-
return [];
|
|
133
|
-
return out
|
|
134
|
-
.split('\n')
|
|
135
|
-
.map((value) => Number(value))
|
|
136
|
-
.filter((value) => Number.isFinite(value) && value > 0);
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
158
|
+
};
|
|
141
159
|
}
|
|
142
160
|
function formatUnknownError(err) {
|
|
143
161
|
if (err instanceof Error)
|
|
@@ -154,6 +172,61 @@ function formatUnknownError(err) {
|
|
|
154
172
|
}
|
|
155
173
|
return String(err);
|
|
156
174
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
175
|
+
function resolveLaunchProxy(options) {
|
|
176
|
+
if (options.noProxy) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
if (options.cliProxy) {
|
|
180
|
+
return normalizeProxySetting(options.cliProxy);
|
|
181
|
+
}
|
|
182
|
+
if (options.configProxy) {
|
|
183
|
+
return normalizeProxySetting(options.configProxy);
|
|
184
|
+
}
|
|
185
|
+
if (options.profileProxy) {
|
|
186
|
+
return normalizeProxySetting(options.profileProxy);
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
function withLaunchProxy(profile, proxy) {
|
|
191
|
+
return {
|
|
192
|
+
...profile,
|
|
193
|
+
fingerprint: {
|
|
194
|
+
...profile.fingerprint,
|
|
195
|
+
proxy,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function normalizeProxySetting(value) {
|
|
200
|
+
if (typeof value === 'string') {
|
|
201
|
+
return parseProxyString(value);
|
|
202
|
+
}
|
|
203
|
+
const normalized = parseProxyString(value.server);
|
|
204
|
+
const username = value.username?.trim() || normalized.username;
|
|
205
|
+
const password = value.password ?? normalized.password;
|
|
206
|
+
return {
|
|
207
|
+
server: normalized.server,
|
|
208
|
+
...(username ? { username } : {}),
|
|
209
|
+
...(password ? { password } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function parseProxyString(value) {
|
|
213
|
+
const trimmed = value.trim();
|
|
214
|
+
if (!trimmed) {
|
|
215
|
+
throw new Error('Proxy value must not be empty.');
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const parsed = new URL(trimmed);
|
|
219
|
+
const server = `${parsed.protocol}//${parsed.host}`;
|
|
220
|
+
if (!parsed.hostname) {
|
|
221
|
+
throw new Error('missing hostname');
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
server,
|
|
225
|
+
...(parsed.username ? { username: decodeURIComponent(parsed.username) } : {}),
|
|
226
|
+
...(parsed.password ? { password: decodeURIComponent(parsed.password) } : {}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return { server: trimmed };
|
|
231
|
+
}
|
|
159
232
|
}
|
|
@@ -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/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA0QA,iBAAe,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgKhE;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAWzF"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ loadEnv();
|
|
|
6
6
|
import { preflightAgentpayGateway } from './agentpay-gateway.js';
|
|
7
7
|
import { browseCommand, browseCommandName } from './command-name.js';
|
|
8
8
|
import { loadSession } from './session.js';
|
|
9
|
-
import { outputError, fatal, info } from './output.js';
|
|
9
|
+
import { outputError, outputJSON, fatal, info } from './output.js';
|
|
10
10
|
function usageText() {
|
|
11
11
|
return `Usage: ${browseCommandName()} <command> [args] [options]
|
|
12
12
|
|
|
@@ -32,8 +32,10 @@ Options:
|
|
|
32
32
|
--compact Launch browser in compact window size (1280x900, default)
|
|
33
33
|
--full Launch browser in full-size window
|
|
34
34
|
--profile <name> Solver profile name for launch (default: "default")
|
|
35
|
+
--proxy <url> Launch browser through proxy for this run only
|
|
36
|
+
--proxyless Ignore configured/profile proxy for this run only
|
|
37
|
+
--headful Explicit alias for headful browser mode (default)
|
|
35
38
|
--headless Launch browser in headless mode
|
|
36
|
-
--no-headless Explicit alias for visible browser mode
|
|
37
39
|
--path <file> Output path for screenshot
|
|
38
40
|
--timeout <seconds> Timeout for solve-captcha command (default: 90)
|
|
39
41
|
--help Show this help message
|
|
@@ -113,13 +115,15 @@ function parseLaunchArgs(args) {
|
|
|
113
115
|
let compact = true;
|
|
114
116
|
let profile;
|
|
115
117
|
let headless = false;
|
|
118
|
+
let proxy;
|
|
119
|
+
let noProxy = false;
|
|
116
120
|
for (let i = 0; i < args.length; i++) {
|
|
117
121
|
const arg = args[i];
|
|
118
122
|
if (arg === '--headless') {
|
|
119
123
|
headless = true;
|
|
120
124
|
continue;
|
|
121
125
|
}
|
|
122
|
-
if (arg === '--
|
|
126
|
+
if (arg === '--headful') {
|
|
123
127
|
headless = false;
|
|
124
128
|
continue;
|
|
125
129
|
}
|
|
@@ -134,21 +138,36 @@ function parseLaunchArgs(args) {
|
|
|
134
138
|
if (arg === '--profile') {
|
|
135
139
|
const value = args[i + 1];
|
|
136
140
|
if (!value || value.startsWith('--')) {
|
|
137
|
-
outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--headless]')}`);
|
|
141
|
+
outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
|
|
138
142
|
}
|
|
139
143
|
profile = value;
|
|
140
144
|
i += 1;
|
|
141
145
|
continue;
|
|
142
146
|
}
|
|
147
|
+
if (arg === '--proxy') {
|
|
148
|
+
const value = args[i + 1];
|
|
149
|
+
if (!value || value.startsWith('--')) {
|
|
150
|
+
outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
|
|
151
|
+
}
|
|
152
|
+
proxy = value;
|
|
153
|
+
noProxy = false;
|
|
154
|
+
i += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (arg === '--proxyless') {
|
|
158
|
+
proxy = undefined;
|
|
159
|
+
noProxy = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
143
162
|
if (arg.startsWith('--')) {
|
|
144
163
|
outputError(`Unknown launch option: ${arg}`);
|
|
145
164
|
}
|
|
146
165
|
if (url) {
|
|
147
|
-
outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--headless]')}`);
|
|
166
|
+
outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
|
|
148
167
|
}
|
|
149
168
|
url = arg;
|
|
150
169
|
}
|
|
151
|
-
return { url, compact, profile, headless };
|
|
170
|
+
return { url, compact, profile, headless, proxy, noProxy };
|
|
152
171
|
}
|
|
153
172
|
function parseCaptchaTimeout(args) {
|
|
154
173
|
const timeoutRaw = getFlag(args, '--timeout');
|
|
@@ -220,11 +239,13 @@ async function main(argv = process.argv) {
|
|
|
220
239
|
case 'launch': {
|
|
221
240
|
const { launch } = await import('./commands/launch.js');
|
|
222
241
|
const launchArgs = parseLaunchArgs(args);
|
|
223
|
-
await launch(launchArgs.url, {
|
|
242
|
+
outputJSON(await launch(launchArgs.url, {
|
|
224
243
|
compact: launchArgs.compact,
|
|
225
244
|
profile: launchArgs.profile,
|
|
226
245
|
headless: launchArgs.headless,
|
|
227
|
-
|
|
246
|
+
proxy: launchArgs.proxy,
|
|
247
|
+
noProxy: launchArgs.noProxy,
|
|
248
|
+
}));
|
|
228
249
|
break;
|
|
229
250
|
}
|
|
230
251
|
case 'navigate': {
|
|
@@ -322,7 +343,7 @@ async function main(argv = process.argv) {
|
|
|
322
343
|
}
|
|
323
344
|
case 'close': {
|
|
324
345
|
const { close } = await import('./commands/close.js');
|
|
325
|
-
await close();
|
|
346
|
+
outputJSON(await close());
|
|
326
347
|
break;
|
|
327
348
|
}
|
|
328
349
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type OwnedPidTerminationResult = 'not_found' | 'terminated' | 'sigkilled' | 'still_alive';
|
|
2
|
+
export type ManagedBrowserCleanupResult = {
|
|
3
|
+
terminated: number[];
|
|
4
|
+
blocked: number[];
|
|
5
|
+
};
|
|
6
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
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[];
|
|
14
|
+
//# sourceMappingURL=owned-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const TERM_WAIT_ATTEMPTS = 10;
|
|
5
|
+
const TERM_WAIT_MS = 100;
|
|
6
|
+
const KILL_WAIT_ATTEMPTS = 5;
|
|
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
|
+
];
|
|
12
|
+
export function isPidAlive(pid) {
|
|
13
|
+
try {
|
|
14
|
+
process.kill(pid, 0);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const code = getErrorCode(error);
|
|
19
|
+
if (code === 'EPERM') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function terminateOwnedPid(pid) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 'SIGTERM');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return isPidAlive(pid) ? 'still_alive' : 'not_found';
|
|
31
|
+
}
|
|
32
|
+
if (await waitForPidExit(pid, TERM_WAIT_ATTEMPTS, TERM_WAIT_MS)) {
|
|
33
|
+
return 'terminated';
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid, 'SIGKILL');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return isPidAlive(pid) ? 'still_alive' : 'sigkilled';
|
|
40
|
+
}
|
|
41
|
+
if (await waitForPidExit(pid, KILL_WAIT_ATTEMPTS, KILL_WAIT_MS)) {
|
|
42
|
+
return 'sigkilled';
|
|
43
|
+
}
|
|
44
|
+
return 'still_alive';
|
|
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
|
+
}
|
|
85
|
+
async function waitForPidExit(pid, attempts, intervalMs) {
|
|
86
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
87
|
+
if (!isPidAlive(pid)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
await sleep(intervalMs);
|
|
91
|
+
}
|
|
92
|
+
return !isPidAlive(pid);
|
|
93
|
+
}
|
|
94
|
+
function getErrorCode(error) {
|
|
95
|
+
if (!error || typeof error !== 'object') {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const code = Reflect.get(error, 'code');
|
|
99
|
+
return typeof code === 'string' ? code : undefined;
|
|
100
|
+
}
|
|
101
|
+
function sleep(ms) {
|
|
102
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
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
|
+
}
|
package/dist/session.d.ts
CHANGED
|
@@ -19,12 +19,21 @@ export interface BrowseSession {
|
|
|
19
19
|
launchedAt: string;
|
|
20
20
|
port?: number;
|
|
21
21
|
profile?: string;
|
|
22
|
+
identity?: BrowseSessionIdentity;
|
|
22
23
|
capabilities?: {
|
|
23
24
|
captchaSolve?: boolean;
|
|
24
25
|
};
|
|
25
26
|
runtime?: BrowseRuntimeState;
|
|
26
27
|
transientSecretCache?: Record<string, CachedTransientSecretEntry>;
|
|
27
28
|
}
|
|
29
|
+
export interface BrowseSessionIdentity {
|
|
30
|
+
browserInstanceRef: string;
|
|
31
|
+
endpoint: string;
|
|
32
|
+
pid: number;
|
|
33
|
+
profile?: string;
|
|
34
|
+
launchedAt: string;
|
|
35
|
+
ownership: 'agentbrowse';
|
|
36
|
+
}
|
|
28
37
|
export declare function cleanupTransientSecretCache(session: BrowseSession, options?: {
|
|
29
38
|
now?: string;
|
|
30
39
|
}): boolean;
|
|
@@ -34,11 +43,15 @@ export declare function getCachedTransientSecret(session: BrowseSession, intentI
|
|
|
34
43
|
}): CachedTransientSecretEntry | null;
|
|
35
44
|
export declare function deleteCachedTransientSecret(session: BrowseSession, intentId: string): boolean;
|
|
36
45
|
export declare function serializeSession(session: BrowseSession): string;
|
|
46
|
+
export declare function buildOwnedSession(session: Omit<BrowseSession, 'port' | 'identity'>): BrowseSession;
|
|
37
47
|
export declare function saveSession(session: BrowseSession): void;
|
|
38
48
|
export declare function loadSession(): BrowseSession | null;
|
|
39
49
|
export declare function getSessionPort(session: BrowseSession | null): number;
|
|
40
50
|
export declare function deleteSession(): void;
|
|
41
51
|
export declare function supportsCaptchaSolve(session: BrowseSession | null): boolean;
|
|
52
|
+
export declare function isOwnedSession(session: BrowseSession | null | undefined): session is BrowseSession & {
|
|
53
|
+
identity: BrowseSessionIdentity;
|
|
54
|
+
};
|
|
42
55
|
/** Check if the Chrome process from the session is still alive. */
|
|
43
56
|
export declare function isSessionAlive(session: BrowseSession): boolean;
|
|
44
57
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;CACnE;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,YAAY,CAAC,EAAE;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,aAAa,CAAC;CAC1B;AAkDD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAqBT;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,0BAA0B,GAChC,0BAA0B,CAQ5B;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,0BAA0B,GAAG,IAAI,CAMnC;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAW7F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAW/D;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,CAAC,GAChD,aAAa,CAaf;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAGxD;AAED,wBAAgB,WAAW,IAAI,aAAa,GAAG,IAAI,CAclD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAWpE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAE3E;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GACxC,OAAO,IAAI,aAAa,GAAG;IAAE,QAAQ,EAAE,qBAAqB,CAAA;CAAE,CAahE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAO9D"}
|
package/dist/session.js
CHANGED
|
@@ -20,6 +20,30 @@ function ensureDir() {
|
|
|
20
20
|
mkdirSync(SESSION_DIR, { recursive: true });
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
function parseSessionPort(cdpUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(cdpUrl);
|
|
26
|
+
if (!url.port) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const port = Number(url.port);
|
|
30
|
+
return Number.isFinite(port) && port > 0 ? port : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseBrowserInstanceRef(cdpUrl) {
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(cdpUrl);
|
|
39
|
+
const pathSegments = url.pathname.split('/').filter(Boolean);
|
|
40
|
+
return pathSegments.at(-1) ?? cdpUrl;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
const pathSegments = cdpUrl.split('/').filter(Boolean);
|
|
44
|
+
return pathSegments.at(-1) ?? cdpUrl;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
23
47
|
function isExpired(expiresAt, now) {
|
|
24
48
|
if (!expiresAt) {
|
|
25
49
|
return false;
|
|
@@ -79,6 +103,20 @@ export function serializeSession(session) {
|
|
|
79
103
|
return value;
|
|
80
104
|
}, 2);
|
|
81
105
|
}
|
|
106
|
+
export function buildOwnedSession(session) {
|
|
107
|
+
return {
|
|
108
|
+
...session,
|
|
109
|
+
port: parseSessionPort(session.cdpUrl),
|
|
110
|
+
identity: {
|
|
111
|
+
browserInstanceRef: parseBrowserInstanceRef(session.cdpUrl),
|
|
112
|
+
endpoint: session.cdpUrl,
|
|
113
|
+
pid: session.pid,
|
|
114
|
+
profile: session.profile,
|
|
115
|
+
launchedAt: session.launchedAt,
|
|
116
|
+
ownership: 'agentbrowse',
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
82
120
|
export function saveSession(session) {
|
|
83
121
|
ensureDir();
|
|
84
122
|
writeFileSync(SESSION_PATH, serializeSession(session));
|
|
@@ -105,13 +143,9 @@ export function getSessionPort(session) {
|
|
|
105
143
|
if (session?.port)
|
|
106
144
|
return session.port;
|
|
107
145
|
if (session?.cdpUrl) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return Number(url.port);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
// Ignore malformed cdpUrl and use default fallback.
|
|
146
|
+
const parsedPort = parseSessionPort(session.cdpUrl);
|
|
147
|
+
if (parsedPort) {
|
|
148
|
+
return parsedPort;
|
|
115
149
|
}
|
|
116
150
|
}
|
|
117
151
|
return 9222;
|
|
@@ -123,6 +157,17 @@ export function deleteSession() {
|
|
|
123
157
|
export function supportsCaptchaSolve(session) {
|
|
124
158
|
return session?.capabilities?.captchaSolve === true;
|
|
125
159
|
}
|
|
160
|
+
export function isOwnedSession(session) {
|
|
161
|
+
if (!session?.identity) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return (session.identity.ownership === 'agentbrowse' &&
|
|
165
|
+
session.identity.endpoint === session.cdpUrl &&
|
|
166
|
+
session.identity.pid === session.pid &&
|
|
167
|
+
session.identity.launchedAt === session.launchedAt &&
|
|
168
|
+
session.identity.browserInstanceRef.length > 0 &&
|
|
169
|
+
session.identity.profile === session.profile);
|
|
170
|
+
}
|
|
126
171
|
/** Check if the Chrome process from the session is still alive. */
|
|
127
172
|
export function isSessionAlive(session) {
|
|
128
173
|
try {
|
|
@@ -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/dist/solver/types.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type ProxyConfig = {
|
|
|
24
24
|
username?: string;
|
|
25
25
|
password?: string;
|
|
26
26
|
};
|
|
27
|
+
export type ProxySetting = string | ProxyConfig;
|
|
27
28
|
export type ProfileInfo = {
|
|
28
29
|
name: string;
|
|
29
30
|
fingerprint: BrowserFingerprint;
|
|
@@ -37,6 +38,7 @@ export type SolverConfig = {
|
|
|
37
38
|
};
|
|
38
39
|
defaults?: {
|
|
39
40
|
headless?: boolean;
|
|
41
|
+
proxy?: ProxySetting;
|
|
40
42
|
};
|
|
41
43
|
};
|
|
42
44
|
export type CaptchaType = 'recaptcha-v2' | 'hcaptcha' | 'turnstile';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/solver/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/solver/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,CAAC;AAEhD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,YAAY,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,sBAAsB,CAAC;AACrE,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,UAAU,GAAG,cAAc,CAAC;AAEjF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuanu-ai/agentbrowse",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
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": [
|