@mearl/native-host 2.6.1 → 2.7.0
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 +11 -0
- package/dist/agentbay-browser.d.ts +12 -1
- package/dist/agentbay-lifecycle.d.ts +38 -0
- package/dist/cdp-backend/cdp-utils.d.ts +4 -1
- package/dist/cdp-backend/interaction-handlers.d.ts +2 -1
- package/dist/index.js +568 -49
- package/dist/internal-extension-actions.d.ts +3 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ mearl-native-host [options]
|
|
|
45
45
|
--daemon 独立 CDP daemon
|
|
46
46
|
--cdp-port <port> 指定 CDP 端口
|
|
47
47
|
--cdp-user-data-dir <dir> 指定 Chrome Profile
|
|
48
|
+
--agentbay-session-id <id> 连接 AgentBay CDP Session
|
|
49
|
+
--agentbay-idle-release-minutes <minutes> AgentBay 空闲回收时间(内部参数)
|
|
48
50
|
--browser-id <id> 指定浏览器注册 ID
|
|
49
51
|
--browser-name <name> 指定浏览器显示名称
|
|
50
52
|
--version, -v 查看版本
|
|
@@ -63,6 +65,15 @@ Chrome Extension
|
|
|
63
65
|
|
|
64
66
|
`browser_launch` 会为每个托管 Chrome 启动一个带独立 CDP 配置的 native-host 子进程,使多个 headless/Profile/TDBank 账号实例可以同时被现有浏览器注册表寻址。设计细节见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
|
|
65
67
|
|
|
68
|
+
AgentBay 会话创建时使用官方 `LifecyclePolicy`:默认 30 分钟没有 Mearl 指令时由本地宿主执行优雅关闭,120 分钟达到云端最大运行时长时由 AgentBay 强制释放。优雅关闭会先同步持久化 Browser Context,再删除 Session 和本地注册记录;执行中的 Mearl action 不计为空闲。正常任务仍应主动调用 `browser_close`,超时只用于异常退出兜底。
|
|
69
|
+
|
|
70
|
+
运维环境可覆盖默认值;两项均为正整数分钟,空闲时间至少为 3,最大运行时长不得小于空闲时间:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
MEARL_AGENTBAY_IDLE_RELEASE_MINUTES=30
|
|
74
|
+
MEARL_AGENTBAY_MAX_RUNTIME_MINUTES=120
|
|
75
|
+
```
|
|
76
|
+
|
|
66
77
|
## 侧边栏 Agent Chat
|
|
67
78
|
|
|
68
79
|
扩展侧边栏通过 Native Messaging 调用 native-host,再按用户选择启动持久的 Codex app-server 或 Qoder ACP 进程。两种 Provider 都支持流式消息、思考与工具进度、中断和审批,并复用各自 CLI 已保存的本地登录态。
|
|
@@ -9,6 +9,7 @@ export interface AgentBayBrowserRecord extends DaemonRecord {
|
|
|
9
9
|
name: string;
|
|
10
10
|
sessionId: string;
|
|
11
11
|
imageId?: string;
|
|
12
|
+
userAgentMode?: 'default' | 'desktop';
|
|
12
13
|
persistent?: boolean;
|
|
13
14
|
contextId?: string;
|
|
14
15
|
contextName?: string;
|
|
@@ -93,12 +94,21 @@ export interface AgentBayBrowserSummary {
|
|
|
93
94
|
copiedCookies?: AgentBayBrowserRecord['copiedCookies'];
|
|
94
95
|
running: boolean;
|
|
95
96
|
}
|
|
97
|
+
export interface AgentBayBrowserLaunchResult extends AgentBayBrowserSummary {
|
|
98
|
+
created: boolean;
|
|
99
|
+
reused: boolean;
|
|
100
|
+
}
|
|
96
101
|
export interface AgentBayBrowserManagerDeps {
|
|
97
102
|
nativeHostScript: string;
|
|
98
103
|
exportCookies: (domains: string[]) => Promise<TransferableCookie[]>;
|
|
99
104
|
getBrowserUserAgent: () => Promise<string>;
|
|
100
105
|
log: (...args: any[]) => void;
|
|
101
106
|
createAgentBayClient?: (apiKey: string) => Promise<AgentBayClientLike>;
|
|
107
|
+
connectCdp?: ConnectAgentBayCdp;
|
|
108
|
+
findBrowserRecord?: (selector: string) => {
|
|
109
|
+
record: AgentBayBrowserRecord;
|
|
110
|
+
running: boolean;
|
|
111
|
+
} | null;
|
|
102
112
|
persistBrowserRecord?: (record: AgentBayBrowserRecord) => void;
|
|
103
113
|
resolveApiKey?: () => string;
|
|
104
114
|
}
|
|
@@ -124,7 +134,8 @@ export declare function getAgentBayAccessUrl(sessionId: string, dependencies?: P
|
|
|
124
134
|
export declare function resolveAgentBayEndpoint(sessionId: string): Promise<string>;
|
|
125
135
|
export declare function keepAgentBaySessionAlive(sessionId: string): Promise<void>;
|
|
126
136
|
export declare function initializeAgentBayBrowserSession(session: Pick<AgentBaySessionLike, 'browser'>, input: Pick<BrowserLaunchParams, 'url'>, cookies: TransferableCookie[], options?: AgentBayBrowserSessionInitOptions): Promise<number>;
|
|
127
|
-
export declare function
|
|
137
|
+
export declare function reuseAgentBayBrowserSession(session: Pick<AgentBaySessionLike, 'browser'>, input: Pick<BrowserLaunchParams, 'url'>, cookies: TransferableCookie[], options?: Pick<AgentBayBrowserSessionInitOptions, 'connectCdp'>): Promise<number>;
|
|
138
|
+
export declare function launchAgentBayBrowser(input: BrowserLaunchParams, deps: AgentBayBrowserManagerDeps): Promise<AgentBayBrowserLaunchResult>;
|
|
128
139
|
export declare function releaseAgentBayBrowserResources(record: AgentBayBrowserRecord, agentBay: AgentBayClientLike, options?: {
|
|
129
140
|
deleteProfile?: boolean;
|
|
130
141
|
persistRecord?: (record: AgentBayBrowserRecord) => void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const MIN_AGENTBAY_IDLE_RELEASE_MINUTES = 3;
|
|
2
|
+
export declare const DEFAULT_AGENTBAY_IDLE_RELEASE_MINUTES = 30;
|
|
3
|
+
export declare const DEFAULT_AGENTBAY_MAX_RUNTIME_MINUTES = 120;
|
|
4
|
+
export declare const DEFAULT_AGENTBAY_LIFECYCLE_CHECK_INTERVAL_MS = 60000;
|
|
5
|
+
export interface AgentBayLifecycleConfig {
|
|
6
|
+
idleReleaseTimeout: number;
|
|
7
|
+
maxRuntime: number;
|
|
8
|
+
}
|
|
9
|
+
interface AgentBaySessionWatchdogOptions {
|
|
10
|
+
idleTimeoutMs: number;
|
|
11
|
+
checkIntervalMs?: number;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
keepAlive: () => Promise<void>;
|
|
14
|
+
close: () => Promise<void>;
|
|
15
|
+
log: (...args: unknown[]) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveAgentBayLifecycleConfig(env?: NodeJS.ProcessEnv): AgentBayLifecycleConfig;
|
|
18
|
+
export declare class AgentBaySessionWatchdog {
|
|
19
|
+
private readonly idleTimeoutMs;
|
|
20
|
+
private readonly checkIntervalMs;
|
|
21
|
+
private readonly now;
|
|
22
|
+
private readonly keepAlive;
|
|
23
|
+
private readonly close;
|
|
24
|
+
private readonly log;
|
|
25
|
+
private activeRequests;
|
|
26
|
+
private lastActivityAt;
|
|
27
|
+
private timer;
|
|
28
|
+
private checking;
|
|
29
|
+
private closing;
|
|
30
|
+
private stopped;
|
|
31
|
+
constructor(options: AgentBaySessionWatchdogOptions);
|
|
32
|
+
start(): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
beginActivity(): boolean;
|
|
35
|
+
endActivity(): void;
|
|
36
|
+
checkNow(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Contains: HTTP helper (nodeFetch), cookie utilities, page-load waiters,
|
|
5
5
|
* viewport helpers, and serialized function sources for page injection.
|
|
6
6
|
*/
|
|
7
|
-
import { type PageClickPointerType } from '@mearl/browser-core';
|
|
7
|
+
import { type PageClickPointerType, type PageDragPoint, type PageDragPointerType } from '@mearl/browser-core';
|
|
8
8
|
import type { PageSession } from './cdp-session.js';
|
|
9
9
|
export declare const TYPE_INTO_ELEMENT_SRC: string;
|
|
10
10
|
export declare const CLICK_ELEMENT_VIA_JS_SRC: string;
|
|
@@ -61,4 +61,7 @@ export declare function toCdpInputPoint(session: PageSession, x: number, y: numb
|
|
|
61
61
|
y: number;
|
|
62
62
|
}>;
|
|
63
63
|
export declare function dispatchCdpClick(session: PageSession, x: number, y: number, pointerType: PageClickPointerType): Promise<void>;
|
|
64
|
+
export declare function dispatchCdpDrag(session: PageSession, from: PageDragPoint, to: PageDragPoint, pointerType: PageDragPointerType, durationMs: number): Promise<{
|
|
65
|
+
steps: number;
|
|
66
|
+
}>;
|
|
64
67
|
export declare function resolveBackendNodeObjectId(session: PageSession, backendNodeId: number): Promise<string>;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Covers: click, type, scroll, eval, press, wait, navigate, upload, device emulation.
|
|
5
5
|
*/
|
|
6
|
-
import { type PageClickParams, type PageEvalParams, type PageHoverParams, type PageNavigateParams, type PagePressParams, type PageScrollParams, type PageTypeParams, type PageUploadParams, type PageWaitParams, type SetDeviceEmulationParams } from '@mearl/browser-core';
|
|
6
|
+
import { type PageClickParams, type PageDragParams, type PageDragResultData, type PageEvalParams, type PageHoverParams, type PageNavigateParams, type PagePressParams, type PageScrollParams, type PageTypeParams, type PageUploadParams, type PageWaitParams, type SetDeviceEmulationParams } from '@mearl/browser-core';
|
|
7
7
|
import type { PageSession } from './cdp-session.js';
|
|
8
8
|
export declare function cdpPageClick(session: PageSession, data: PageClickParams): Promise<any>;
|
|
9
|
+
export declare function cdpPageDrag(session: PageSession, data: PageDragParams): Promise<PageDragResultData>;
|
|
9
10
|
export declare function cdpPageType(session: PageSession, data: PageTypeParams): Promise<any>;
|
|
10
11
|
export declare function cdpPageScroll(session: PageSession, data: PageScrollParams): Promise<any>;
|
|
11
12
|
export declare function cdpPageEval(session: PageSession, data: PageEvalParams): Promise<any>;
|
package/dist/index.js
CHANGED
|
@@ -2068,6 +2068,83 @@ function getClickCoordsFromNode() {
|
|
|
2068
2068
|
};
|
|
2069
2069
|
}
|
|
2070
2070
|
|
|
2071
|
+
// ../browser-core/dist/pageDragging.js
|
|
2072
|
+
var PAGE_DRAG_DEFAULT_DURATION_MS = 400;
|
|
2073
|
+
var PAGE_DRAG_MAX_DURATION_MS = 5e3;
|
|
2074
|
+
var PAGE_DRAG_STEP_INTERVAL_MS = 25;
|
|
2075
|
+
var PAGE_DRAG_MIN_STEPS = 2;
|
|
2076
|
+
var PAGE_DRAG_MAX_STEPS = 60;
|
|
2077
|
+
function isPageDragPoint(value) {
|
|
2078
|
+
if (!value || typeof value !== "object")
|
|
2079
|
+
return false;
|
|
2080
|
+
const point = value;
|
|
2081
|
+
return Number.isFinite(point.x) && Number.isFinite(point.y) && point.x >= 0 && point.y >= 0;
|
|
2082
|
+
}
|
|
2083
|
+
function resolveEndpoint(value, name) {
|
|
2084
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
2085
|
+
return value.trim();
|
|
2086
|
+
if (isPageDragPoint(value))
|
|
2087
|
+
return { x: value.x, y: value.y };
|
|
2088
|
+
throw new Error(`page_drag: "${name}" must be a non-empty CSS/@ref selector or { x, y } viewport point.`);
|
|
2089
|
+
}
|
|
2090
|
+
function resolvePageDragInput(data) {
|
|
2091
|
+
if (!data || typeof data !== "object") {
|
|
2092
|
+
throw new Error("page_drag: payload is required");
|
|
2093
|
+
}
|
|
2094
|
+
if (data.dragMode !== void 0 && !["auto", "mouse", "touch"].includes(data.dragMode)) {
|
|
2095
|
+
throw new Error('page_drag: "dragMode" must be one of auto, mouse, touch');
|
|
2096
|
+
}
|
|
2097
|
+
const durationMs = data.durationMs ?? PAGE_DRAG_DEFAULT_DURATION_MS;
|
|
2098
|
+
if (!Number.isFinite(durationMs) || durationMs < 0 || durationMs > PAGE_DRAG_MAX_DURATION_MS) {
|
|
2099
|
+
throw new Error(`page_drag: "durationMs" must be between 0 and ${PAGE_DRAG_MAX_DURATION_MS}.`);
|
|
2100
|
+
}
|
|
2101
|
+
return {
|
|
2102
|
+
from: resolveEndpoint(data.from, "from"),
|
|
2103
|
+
to: resolveEndpoint(data.to, "to"),
|
|
2104
|
+
dragMode: data.dragMode ?? "auto",
|
|
2105
|
+
durationMs: Math.round(durationMs)
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
function buildPageDragPath(from, to, durationMs) {
|
|
2109
|
+
const steps = Math.max(PAGE_DRAG_MIN_STEPS, Math.min(PAGE_DRAG_MAX_STEPS, Math.ceil(durationMs / PAGE_DRAG_STEP_INTERVAL_MS)));
|
|
2110
|
+
const points = Array.from({ length: steps }, (_, index) => {
|
|
2111
|
+
const ratio = (index + 1) / steps;
|
|
2112
|
+
return {
|
|
2113
|
+
x: from.x + (to.x - from.x) * ratio,
|
|
2114
|
+
y: from.y + (to.y - from.y) * ratio
|
|
2115
|
+
};
|
|
2116
|
+
});
|
|
2117
|
+
return { points, intervalMs: durationMs / steps };
|
|
2118
|
+
}
|
|
2119
|
+
function getDragCoordsFromNode() {
|
|
2120
|
+
const el = this.nodeType === Node.TEXT_NODE ? this.parentElement : this;
|
|
2121
|
+
if (!el || typeof el.getBoundingClientRect !== "function")
|
|
2122
|
+
return null;
|
|
2123
|
+
const rect = el.getBoundingClientRect();
|
|
2124
|
+
const viewportWidth = window.visualViewport?.width ?? window.innerWidth;
|
|
2125
|
+
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
|
2126
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
2127
|
+
return { x: 0, y: 0, tag: "", text: "", error: "Element has no visible bounds" };
|
|
2128
|
+
}
|
|
2129
|
+
if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= viewportWidth || rect.top >= viewportHeight) {
|
|
2130
|
+
return {
|
|
2131
|
+
x: 0,
|
|
2132
|
+
y: 0,
|
|
2133
|
+
tag: "",
|
|
2134
|
+
text: "",
|
|
2135
|
+
error: "Element is outside the current viewport; scroll it into view before dragging"
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
const x = Math.min(Math.max(rect.left + rect.width / 2, 0), Math.max(viewportWidth - 1, 0));
|
|
2139
|
+
const y = Math.min(Math.max(rect.top + rect.height / 2, 0), Math.max(viewportHeight - 1, 0));
|
|
2140
|
+
return {
|
|
2141
|
+
x,
|
|
2142
|
+
y,
|
|
2143
|
+
tag: el.tagName?.toLowerCase() ?? "",
|
|
2144
|
+
text: (el.textContent ?? "").trim().slice(0, 100)
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2071
2148
|
// ../browser-core/dist/axSnapshot.js
|
|
2072
2149
|
var PAGE_SNAPSHOT_QUERY_MAX_MATCHES = 100;
|
|
2073
2150
|
var PAGE_SNAPSHOT_QUERY_MAX_BYTES = 256 * 1024;
|
|
@@ -3372,6 +3449,7 @@ var SEND_REQUEST_MAX_TOTAL_FILE_BYTES = 40 * 1024 * 1024;
|
|
|
3372
3449
|
var SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES = 7 * 1024 * 1024;
|
|
3373
3450
|
var PAGE_ACT_ACTIONS = [
|
|
3374
3451
|
"page_click",
|
|
3452
|
+
"page_drag",
|
|
3375
3453
|
"page_type",
|
|
3376
3454
|
"page_hover",
|
|
3377
3455
|
"page_scroll",
|
|
@@ -3402,6 +3480,7 @@ var BROWSER_COMMAND_ACTIONS = {
|
|
|
3402
3480
|
tab_close: true,
|
|
3403
3481
|
tab_list: true,
|
|
3404
3482
|
page_click: true,
|
|
3483
|
+
page_drag: true,
|
|
3405
3484
|
page_type: true,
|
|
3406
3485
|
page_scroll: true,
|
|
3407
3486
|
page_hover: true,
|
|
@@ -3442,6 +3521,7 @@ var EXTENSION_BROWSER_ACTIONS = {
|
|
|
3442
3521
|
tab_close: true,
|
|
3443
3522
|
tab_list: true,
|
|
3444
3523
|
page_click: true,
|
|
3524
|
+
page_drag: true,
|
|
3445
3525
|
page_type: true,
|
|
3446
3526
|
page_scroll: true,
|
|
3447
3527
|
page_hover: true,
|
|
@@ -4339,6 +4419,12 @@ function resolveSetCookieTarget(currentUrl, input) {
|
|
|
4339
4419
|
}
|
|
4340
4420
|
|
|
4341
4421
|
// ../browser-core/dist/controlledFavicon.js
|
|
4422
|
+
function normalizeControlSource(source) {
|
|
4423
|
+
return source === "remote" || source === "cloud" ? "remote" : "local";
|
|
4424
|
+
}
|
|
4425
|
+
function toControlSourceWireValue(source) {
|
|
4426
|
+
return source === "remote" ? "cloud" : "local";
|
|
4427
|
+
}
|
|
4342
4428
|
var BASE_ICON = `
|
|
4343
4429
|
<rect width="128" height="128" rx="28" fill="#3273E8"/>
|
|
4344
4430
|
<path d="M36 90V40L64 68L92 40V90" stroke="#FFFFFF" stroke-width="14"
|
|
@@ -4348,7 +4434,7 @@ var SOURCE_BADGES = {
|
|
|
4348
4434
|
local: `
|
|
4349
4435
|
<circle cx="104" cy="104" r="21" fill="#16A34A" stroke="#FFFFFF" stroke-width="6"/>
|
|
4350
4436
|
`,
|
|
4351
|
-
|
|
4437
|
+
remote: `
|
|
4352
4438
|
<circle cx="104" cy="104" r="21" fill="#FF7A00" stroke="#FFFFFF" stroke-width="6"/>
|
|
4353
4439
|
`
|
|
4354
4440
|
};
|
|
@@ -5949,6 +6035,85 @@ async function dispatchCdpClick(session, x, y, pointerType) {
|
|
|
5949
6035
|
clickCount: 1
|
|
5950
6036
|
});
|
|
5951
6037
|
}
|
|
6038
|
+
async function dispatchCdpDrag(session, from, to, pointerType, durationMs) {
|
|
6039
|
+
const [start, end] = await Promise.all([
|
|
6040
|
+
toCdpInputPoint(session, from.x, from.y),
|
|
6041
|
+
toCdpInputPoint(session, to.x, to.y)
|
|
6042
|
+
]);
|
|
6043
|
+
const path15 = buildPageDragPath(start, end, durationMs);
|
|
6044
|
+
const waitForNextPoint = async () => {
|
|
6045
|
+
if (path15.intervalMs <= 0) return;
|
|
6046
|
+
await new Promise((resolve2) => setTimeout(resolve2, path15.intervalMs));
|
|
6047
|
+
};
|
|
6048
|
+
if (pointerType === "touch") {
|
|
6049
|
+
await sendCdpInputCommand(session, "Input.dispatchTouchEvent", {
|
|
6050
|
+
type: "touchStart",
|
|
6051
|
+
touchPoints: [start]
|
|
6052
|
+
});
|
|
6053
|
+
let primaryError2;
|
|
6054
|
+
try {
|
|
6055
|
+
for (const point of path15.points) {
|
|
6056
|
+
await waitForNextPoint();
|
|
6057
|
+
await sendCdpInputCommand(session, "Input.dispatchTouchEvent", {
|
|
6058
|
+
type: "touchMove",
|
|
6059
|
+
touchPoints: [point]
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
} catch (error48) {
|
|
6063
|
+
primaryError2 = error48;
|
|
6064
|
+
throw error48;
|
|
6065
|
+
} finally {
|
|
6066
|
+
await sendCdpInputCommand(session, "Input.dispatchTouchEvent", {
|
|
6067
|
+
type: "touchEnd",
|
|
6068
|
+
touchPoints: []
|
|
6069
|
+
}).catch((error48) => {
|
|
6070
|
+
if (primaryError2 === void 0) throw error48;
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
6073
|
+
return { steps: path15.points.length };
|
|
6074
|
+
}
|
|
6075
|
+
await sendCdpInputCommand(session, "Input.dispatchMouseEvent", {
|
|
6076
|
+
type: "mouseMoved",
|
|
6077
|
+
x: start.x,
|
|
6078
|
+
y: start.y
|
|
6079
|
+
});
|
|
6080
|
+
await sendCdpInputCommand(session, "Input.dispatchMouseEvent", {
|
|
6081
|
+
type: "mousePressed",
|
|
6082
|
+
x: start.x,
|
|
6083
|
+
y: start.y,
|
|
6084
|
+
button: "left",
|
|
6085
|
+
buttons: 1,
|
|
6086
|
+
clickCount: 1
|
|
6087
|
+
});
|
|
6088
|
+
let primaryError;
|
|
6089
|
+
try {
|
|
6090
|
+
for (const point of path15.points) {
|
|
6091
|
+
await waitForNextPoint();
|
|
6092
|
+
await sendCdpInputCommand(session, "Input.dispatchMouseEvent", {
|
|
6093
|
+
type: "mouseMoved",
|
|
6094
|
+
x: point.x,
|
|
6095
|
+
y: point.y,
|
|
6096
|
+
button: "left",
|
|
6097
|
+
buttons: 1
|
|
6098
|
+
});
|
|
6099
|
+
}
|
|
6100
|
+
} catch (error48) {
|
|
6101
|
+
primaryError = error48;
|
|
6102
|
+
throw error48;
|
|
6103
|
+
} finally {
|
|
6104
|
+
await sendCdpInputCommand(session, "Input.dispatchMouseEvent", {
|
|
6105
|
+
type: "mouseReleased",
|
|
6106
|
+
x: end.x,
|
|
6107
|
+
y: end.y,
|
|
6108
|
+
button: "left",
|
|
6109
|
+
buttons: 0,
|
|
6110
|
+
clickCount: 1
|
|
6111
|
+
}).catch((error48) => {
|
|
6112
|
+
if (primaryError === void 0) throw error48;
|
|
6113
|
+
});
|
|
6114
|
+
}
|
|
6115
|
+
return { steps: path15.points.length };
|
|
6116
|
+
}
|
|
5952
6117
|
async function sendCdpInputCommand(session, method, params) {
|
|
5953
6118
|
try {
|
|
5954
6119
|
await session.client.sendCommand(method, params, PAGE_CLICK_INPUT_TIMEOUT_MS);
|
|
@@ -7940,6 +8105,7 @@ registerActions([
|
|
|
7940
8105
|
]);
|
|
7941
8106
|
|
|
7942
8107
|
// src/cdp-backend/interaction-handlers.ts
|
|
8108
|
+
var GET_DRAG_COORDS_FROM_NODE_SRC = getDragCoordsFromNode.toString();
|
|
7943
8109
|
function resolveSnapshotRef(session, ref) {
|
|
7944
8110
|
const entry = resolvePageRef(session, ref);
|
|
7945
8111
|
if (!entry) {
|
|
@@ -8251,6 +8417,102 @@ ${candidates}`
|
|
|
8251
8417
|
)
|
|
8252
8418
|
);
|
|
8253
8419
|
}
|
|
8420
|
+
async function scrollDragSourceIntoView(session, endpoint) {
|
|
8421
|
+
if (typeof endpoint !== "string") return;
|
|
8422
|
+
validatePageElementSelector(endpoint, "page_drag from");
|
|
8423
|
+
if (endpoint.startsWith("@e")) {
|
|
8424
|
+
const entry = resolveSnapshotRef(session, endpoint);
|
|
8425
|
+
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
8426
|
+
await session.client.sendCommand("Runtime.callFunctionOn", {
|
|
8427
|
+
objectId,
|
|
8428
|
+
functionDeclaration: `function() {
|
|
8429
|
+
const el = this.nodeType === Node.TEXT_NODE ? this.parentElement : this;
|
|
8430
|
+
if (!el) return false;
|
|
8431
|
+
el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
|
|
8432
|
+
return true;
|
|
8433
|
+
}`,
|
|
8434
|
+
returnByValue: true
|
|
8435
|
+
});
|
|
8436
|
+
return;
|
|
8437
|
+
}
|
|
8438
|
+
const result = await session.client.sendCommand("Runtime.evaluate", {
|
|
8439
|
+
expression: `(() => {
|
|
8440
|
+
const selector = ${JSON.stringify(endpoint)};
|
|
8441
|
+
const el = document.querySelector(selector);
|
|
8442
|
+
if (!el) return { error: 'Element not found: ' + selector };
|
|
8443
|
+
el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
|
|
8444
|
+
return { scrolled: true };
|
|
8445
|
+
})()`,
|
|
8446
|
+
returnByValue: true
|
|
8447
|
+
});
|
|
8448
|
+
const value = result.result?.value;
|
|
8449
|
+
if (value?.error) throw new Error(`page_drag: ${value.error}`);
|
|
8450
|
+
}
|
|
8451
|
+
function unwrapDragElementCoords(value, endpoint) {
|
|
8452
|
+
if (!value) throw new Error(`page_drag: cannot resolve element coordinates for "${endpoint}"`);
|
|
8453
|
+
if (value.error) throw new Error(`page_drag: ${value.error}: "${endpoint}"`);
|
|
8454
|
+
return value;
|
|
8455
|
+
}
|
|
8456
|
+
async function resolveDragEndpoint(session, endpoint) {
|
|
8457
|
+
if (typeof endpoint !== "string") {
|
|
8458
|
+
return { requested: endpoint, point: endpoint };
|
|
8459
|
+
}
|
|
8460
|
+
validatePageElementSelector(endpoint, "page_drag endpoint");
|
|
8461
|
+
let coords;
|
|
8462
|
+
if (endpoint.startsWith("@e")) {
|
|
8463
|
+
const entry = resolveSnapshotRef(session, endpoint);
|
|
8464
|
+
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
8465
|
+
const result = await session.client.sendCommand("Runtime.callFunctionOn", {
|
|
8466
|
+
objectId,
|
|
8467
|
+
functionDeclaration: GET_DRAG_COORDS_FROM_NODE_SRC,
|
|
8468
|
+
returnByValue: true
|
|
8469
|
+
});
|
|
8470
|
+
coords = unwrapDragElementCoords(result.result?.value, endpoint);
|
|
8471
|
+
} else {
|
|
8472
|
+
const result = await session.client.sendCommand("Runtime.evaluate", {
|
|
8473
|
+
expression: `(() => {
|
|
8474
|
+
const selector = ${JSON.stringify(endpoint)};
|
|
8475
|
+
const el = document.querySelector(selector);
|
|
8476
|
+
if (!el) return { error: 'Element not found: ' + selector };
|
|
8477
|
+
return (${GET_DRAG_COORDS_FROM_NODE_SRC}).call(el);
|
|
8478
|
+
})()`,
|
|
8479
|
+
returnByValue: true
|
|
8480
|
+
});
|
|
8481
|
+
coords = unwrapDragElementCoords(result.result?.value, endpoint);
|
|
8482
|
+
}
|
|
8483
|
+
return {
|
|
8484
|
+
requested: endpoint,
|
|
8485
|
+
point: { x: coords.x, y: coords.y },
|
|
8486
|
+
tag: coords.tag,
|
|
8487
|
+
text: coords.text
|
|
8488
|
+
};
|
|
8489
|
+
}
|
|
8490
|
+
async function cdpPageDrag(session, data) {
|
|
8491
|
+
const input = resolvePageDragInput(data);
|
|
8492
|
+
await scrollDragSourceIntoView(session, input.from);
|
|
8493
|
+
const from = await resolveDragEndpoint(session, input.from);
|
|
8494
|
+
const to = await resolveDragEndpoint(session, input.to);
|
|
8495
|
+
if (Math.hypot(to.point.x - from.point.x, to.point.y - from.point.y) < 1) {
|
|
8496
|
+
throw new Error("page_drag: resolved endpoints must be at least 1 viewport pixel apart");
|
|
8497
|
+
}
|
|
8498
|
+
const pointerType = input.dragMode === "auto" ? await prefersTouchInput(session) ? "touch" : "mouse" : input.dragMode;
|
|
8499
|
+
const { steps } = await dispatchCdpDrag(
|
|
8500
|
+
session,
|
|
8501
|
+
from.point,
|
|
8502
|
+
to.point,
|
|
8503
|
+
pointerType,
|
|
8504
|
+
input.durationMs
|
|
8505
|
+
);
|
|
8506
|
+
return {
|
|
8507
|
+
dragged: true,
|
|
8508
|
+
from,
|
|
8509
|
+
to,
|
|
8510
|
+
dispatchMode: "trusted-input",
|
|
8511
|
+
pointerType,
|
|
8512
|
+
durationMs: input.durationMs,
|
|
8513
|
+
steps
|
|
8514
|
+
};
|
|
8515
|
+
}
|
|
8254
8516
|
async function cdpPageType(session, data) {
|
|
8255
8517
|
const { selector, text, clearFirst = true } = data;
|
|
8256
8518
|
if (!selector || typeof selector !== "string") {
|
|
@@ -8571,6 +8833,7 @@ async function cdpPageHover(session, data) {
|
|
|
8571
8833
|
}
|
|
8572
8834
|
registerActions([
|
|
8573
8835
|
{ name: "page_click", session: "page", handler: cdpPageClick },
|
|
8836
|
+
{ name: "page_drag", session: "page", handler: cdpPageDrag },
|
|
8574
8837
|
{ name: "page_type", session: "page", handler: cdpPageType },
|
|
8575
8838
|
{ name: "page_hover", session: "page", handler: cdpPageHover },
|
|
8576
8839
|
{ name: "page_scroll", session: "page", handler: cdpPageScroll },
|
|
@@ -9373,6 +9636,12 @@ function acquireLaunchLock(browserId) {
|
|
|
9373
9636
|
};
|
|
9374
9637
|
}
|
|
9375
9638
|
async function launchManagedBrowser(input, deps) {
|
|
9639
|
+
if (input.reuse !== void 0 && input.reuse !== "never" && input.reuse !== "prefer" && input.reuse !== "require") {
|
|
9640
|
+
throw new Error('reuse must be "never", "prefer", or "require"');
|
|
9641
|
+
}
|
|
9642
|
+
if (input.reuse === "prefer" || input.reuse === "require") {
|
|
9643
|
+
throw new Error("reuse is currently supported only by the AgentBay browser provider");
|
|
9644
|
+
}
|
|
9376
9645
|
if (input.imageId !== void 0) {
|
|
9377
9646
|
throw new Error("imageId is only supported by the AgentBay browser provider");
|
|
9378
9647
|
}
|
|
@@ -9477,7 +9746,11 @@ async function launchManagedBrowser(input, deps) {
|
|
|
9477
9746
|
recordWritten = true;
|
|
9478
9747
|
await waitForBrowserRegistration(browserId, hostPid);
|
|
9479
9748
|
deps.log(`managed browser launched: ${browserId}, chrome=${chromePid}, host=${hostPid}`);
|
|
9480
|
-
return
|
|
9749
|
+
return {
|
|
9750
|
+
...browserSummary(record2, true),
|
|
9751
|
+
created: true,
|
|
9752
|
+
reused: false
|
|
9753
|
+
};
|
|
9481
9754
|
} catch (error48) {
|
|
9482
9755
|
session?.close();
|
|
9483
9756
|
if (hostPid) await stopProcess(hostPid, `--browser-id ${browserId}`);
|
|
@@ -9513,6 +9786,127 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
9513
9786
|
import { homedir as homedir2 } from "node:os";
|
|
9514
9787
|
import { dirname as dirname3, join as join4, parse, resolve } from "node:path";
|
|
9515
9788
|
import { fileURLToPath } from "node:url";
|
|
9789
|
+
|
|
9790
|
+
// src/agentbay-lifecycle.ts
|
|
9791
|
+
var MIN_AGENTBAY_IDLE_RELEASE_MINUTES = 3;
|
|
9792
|
+
var DEFAULT_AGENTBAY_IDLE_RELEASE_MINUTES = 30;
|
|
9793
|
+
var DEFAULT_AGENTBAY_MAX_RUNTIME_MINUTES = 120;
|
|
9794
|
+
var DEFAULT_AGENTBAY_LIFECYCLE_CHECK_INTERVAL_MS = 6e4;
|
|
9795
|
+
function positiveIntegerMinutes(value, name, fallback) {
|
|
9796
|
+
if (value === void 0 || !value.trim()) return fallback;
|
|
9797
|
+
const minutes = Number(value);
|
|
9798
|
+
if (!Number.isInteger(minutes) || minutes <= 0) {
|
|
9799
|
+
throw new Error(`${name} must be a positive integer in minutes`);
|
|
9800
|
+
}
|
|
9801
|
+
return minutes;
|
|
9802
|
+
}
|
|
9803
|
+
function resolveAgentBayLifecycleConfig(env = process.env) {
|
|
9804
|
+
const idleReleaseTimeout = positiveIntegerMinutes(
|
|
9805
|
+
env.MEARL_AGENTBAY_IDLE_RELEASE_MINUTES,
|
|
9806
|
+
"MEARL_AGENTBAY_IDLE_RELEASE_MINUTES",
|
|
9807
|
+
DEFAULT_AGENTBAY_IDLE_RELEASE_MINUTES
|
|
9808
|
+
);
|
|
9809
|
+
const maxRuntime = positiveIntegerMinutes(
|
|
9810
|
+
env.MEARL_AGENTBAY_MAX_RUNTIME_MINUTES,
|
|
9811
|
+
"MEARL_AGENTBAY_MAX_RUNTIME_MINUTES",
|
|
9812
|
+
DEFAULT_AGENTBAY_MAX_RUNTIME_MINUTES
|
|
9813
|
+
);
|
|
9814
|
+
if (idleReleaseTimeout < MIN_AGENTBAY_IDLE_RELEASE_MINUTES) {
|
|
9815
|
+
throw new Error(
|
|
9816
|
+
`MEARL_AGENTBAY_IDLE_RELEASE_MINUTES must be at least ${MIN_AGENTBAY_IDLE_RELEASE_MINUTES}`
|
|
9817
|
+
);
|
|
9818
|
+
}
|
|
9819
|
+
if (maxRuntime < idleReleaseTimeout) {
|
|
9820
|
+
throw new Error(
|
|
9821
|
+
"MEARL_AGENTBAY_MAX_RUNTIME_MINUTES must be greater than or equal to MEARL_AGENTBAY_IDLE_RELEASE_MINUTES"
|
|
9822
|
+
);
|
|
9823
|
+
}
|
|
9824
|
+
return { idleReleaseTimeout, maxRuntime };
|
|
9825
|
+
}
|
|
9826
|
+
var AgentBaySessionWatchdog = class {
|
|
9827
|
+
idleTimeoutMs;
|
|
9828
|
+
checkIntervalMs;
|
|
9829
|
+
now;
|
|
9830
|
+
keepAlive;
|
|
9831
|
+
close;
|
|
9832
|
+
log;
|
|
9833
|
+
activeRequests = 0;
|
|
9834
|
+
lastActivityAt;
|
|
9835
|
+
timer = null;
|
|
9836
|
+
checking = false;
|
|
9837
|
+
closing = false;
|
|
9838
|
+
stopped = false;
|
|
9839
|
+
constructor(options) {
|
|
9840
|
+
if (!Number.isFinite(options.idleTimeoutMs) || options.idleTimeoutMs <= 0) {
|
|
9841
|
+
throw new Error("AgentBay idle timeout must be positive");
|
|
9842
|
+
}
|
|
9843
|
+
this.idleTimeoutMs = options.idleTimeoutMs;
|
|
9844
|
+
this.checkIntervalMs = options.checkIntervalMs ?? DEFAULT_AGENTBAY_LIFECYCLE_CHECK_INTERVAL_MS;
|
|
9845
|
+
if (!Number.isFinite(this.checkIntervalMs) || this.checkIntervalMs <= 0) {
|
|
9846
|
+
throw new Error("AgentBay lifecycle check interval must be positive");
|
|
9847
|
+
}
|
|
9848
|
+
this.now = options.now ?? Date.now;
|
|
9849
|
+
this.keepAlive = options.keepAlive;
|
|
9850
|
+
this.close = options.close;
|
|
9851
|
+
this.log = options.log;
|
|
9852
|
+
this.lastActivityAt = this.now();
|
|
9853
|
+
}
|
|
9854
|
+
start() {
|
|
9855
|
+
if (this.timer || this.stopped) return;
|
|
9856
|
+
this.timer = setInterval(() => void this.checkNow(), this.checkIntervalMs);
|
|
9857
|
+
this.timer.unref();
|
|
9858
|
+
}
|
|
9859
|
+
stop() {
|
|
9860
|
+
this.stopped = true;
|
|
9861
|
+
if (this.timer) {
|
|
9862
|
+
clearInterval(this.timer);
|
|
9863
|
+
this.timer = null;
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
beginActivity() {
|
|
9867
|
+
if (this.stopped || this.closing) return false;
|
|
9868
|
+
this.activeRequests += 1;
|
|
9869
|
+
this.lastActivityAt = this.now();
|
|
9870
|
+
return true;
|
|
9871
|
+
}
|
|
9872
|
+
endActivity() {
|
|
9873
|
+
if (this.activeRequests > 0) this.activeRequests -= 1;
|
|
9874
|
+
this.lastActivityAt = this.now();
|
|
9875
|
+
}
|
|
9876
|
+
async checkNow() {
|
|
9877
|
+
if (this.stopped || this.checking || this.closing) return;
|
|
9878
|
+
this.checking = true;
|
|
9879
|
+
try {
|
|
9880
|
+
const idleForMs = this.now() - this.lastActivityAt;
|
|
9881
|
+
if (this.activeRequests > 0 || idleForMs < this.idleTimeoutMs) {
|
|
9882
|
+
try {
|
|
9883
|
+
await this.keepAlive();
|
|
9884
|
+
} catch (error48) {
|
|
9885
|
+
this.log(
|
|
9886
|
+
"AgentBay keepalive failed:",
|
|
9887
|
+
error48 instanceof Error ? error48.message : String(error48)
|
|
9888
|
+
);
|
|
9889
|
+
}
|
|
9890
|
+
return;
|
|
9891
|
+
}
|
|
9892
|
+
this.closing = true;
|
|
9893
|
+
this.log(`AgentBay browser reached the idle timeout after ${idleForMs}ms; closing`);
|
|
9894
|
+
try {
|
|
9895
|
+
await this.close();
|
|
9896
|
+
} catch (error48) {
|
|
9897
|
+
this.closing = false;
|
|
9898
|
+
this.log(
|
|
9899
|
+
"AgentBay idle close failed:",
|
|
9900
|
+
error48 instanceof Error ? error48.message : String(error48)
|
|
9901
|
+
);
|
|
9902
|
+
}
|
|
9903
|
+
} finally {
|
|
9904
|
+
this.checking = false;
|
|
9905
|
+
}
|
|
9906
|
+
}
|
|
9907
|
+
};
|
|
9908
|
+
|
|
9909
|
+
// src/agentbay-browser.ts
|
|
9516
9910
|
var AGENTBAY_BROWSERS_SUBDIR = "agentbay-browsers";
|
|
9517
9911
|
var DEFAULT_AGENTBAY_IMAGE_ID = "imgc-0aae4rgl8kvagdv1j";
|
|
9518
9912
|
var sdkPromise = null;
|
|
@@ -9589,6 +9983,24 @@ function validateAgentBayInput(input) {
|
|
|
9589
9983
|
throw new Error("TDBank login parameters are only supported by the local browser provider");
|
|
9590
9984
|
}
|
|
9591
9985
|
}
|
|
9986
|
+
function resolveReuseMode(value) {
|
|
9987
|
+
if (value === void 0) return "never";
|
|
9988
|
+
if (value === "never" || value === "prefer" || value === "require") return value;
|
|
9989
|
+
throw new Error('reuse must be "never", "prefer", or "require"');
|
|
9990
|
+
}
|
|
9991
|
+
function validateReusableAgentBayConfiguration(input, record2) {
|
|
9992
|
+
if (input.imageId !== void 0 && resolveAgentBayImageId(input.imageId) !== record2.imageId) {
|
|
9993
|
+
throw new Error(`Cannot change imageId while reusing AgentBay browser: ${record2.browserId}`);
|
|
9994
|
+
}
|
|
9995
|
+
if (input.persistent !== void 0 && input.persistent !== (record2.persistent === true)) {
|
|
9996
|
+
throw new Error(`Cannot change persistent while reusing AgentBay browser: ${record2.browserId}`);
|
|
9997
|
+
}
|
|
9998
|
+
if (input.userAgentMode !== void 0 && record2.userAgentMode !== void 0 && input.userAgentMode !== record2.userAgentMode) {
|
|
9999
|
+
throw new Error(
|
|
10000
|
+
`Cannot change userAgentMode while reusing AgentBay browser: ${record2.browserId}`
|
|
10001
|
+
);
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
9592
10004
|
function resolveAgentBayImageId(value) {
|
|
9593
10005
|
if (value === void 0) return DEFAULT_AGENTBAY_IMAGE_ID;
|
|
9594
10006
|
const imageId = typeof value === "string" ? value.trim() : "";
|
|
@@ -9647,11 +10059,14 @@ function listRecords2() {
|
|
|
9647
10059
|
}
|
|
9648
10060
|
return records;
|
|
9649
10061
|
}
|
|
9650
|
-
function
|
|
10062
|
+
function findRecordWithStatus(selector) {
|
|
9651
10063
|
const normalized = selector.trim().toLowerCase();
|
|
9652
10064
|
return listRecords2().find(
|
|
9653
10065
|
({ record: record2 }) => record2.browserId.toLowerCase() === normalized || record2.name.toLowerCase() === normalized
|
|
9654
|
-
)
|
|
10066
|
+
) ?? null;
|
|
10067
|
+
}
|
|
10068
|
+
function findRecord(selector) {
|
|
10069
|
+
return findRecordWithStatus(selector)?.record ?? null;
|
|
9655
10070
|
}
|
|
9656
10071
|
function hasAgentBayBrowser(selector) {
|
|
9657
10072
|
return findRecord(selector) !== null;
|
|
@@ -9723,13 +10138,9 @@ async function keepAgentBaySessionAlive(sessionId) {
|
|
|
9723
10138
|
const result = await session.keepAlive();
|
|
9724
10139
|
if (result.success === false) throw new Error(result.errorMessage || "AgentBay keepalive failed");
|
|
9725
10140
|
}
|
|
9726
|
-
async function
|
|
9727
|
-
const initialized = await session.browser.initializeAsync(
|
|
9728
|
-
options.userAgent ? { userAgent: options.userAgent } : {}
|
|
9729
|
-
);
|
|
9730
|
-
if (!initialized) throw new Error("AgentBay browser initialization failed");
|
|
10141
|
+
async function configureAgentBayBrowserSession(session, input, cookies, connectCdp = (url2) => CdpClient.connect(url2)) {
|
|
9731
10142
|
const endpoint = await session.browser.getEndpointUrl();
|
|
9732
|
-
const cdp = await
|
|
10143
|
+
const cdp = await connectCdp(endpoint);
|
|
9733
10144
|
try {
|
|
9734
10145
|
await cdp.sendCommand("Browser.getVersion", {}, 1e4);
|
|
9735
10146
|
const copiedCookieCount = await cdpImportCookiesWithClient(cdp, cookies);
|
|
@@ -9741,32 +10152,76 @@ async function initializeAgentBayBrowserSession(session, input, cookies, options
|
|
|
9741
10152
|
cdp.close();
|
|
9742
10153
|
}
|
|
9743
10154
|
}
|
|
10155
|
+
async function initializeAgentBayBrowserSession(session, input, cookies, options = {}) {
|
|
10156
|
+
const initialized = await session.browser.initializeAsync(
|
|
10157
|
+
options.userAgent ? { userAgent: options.userAgent } : {}
|
|
10158
|
+
);
|
|
10159
|
+
if (!initialized) throw new Error("AgentBay browser initialization failed");
|
|
10160
|
+
return configureAgentBayBrowserSession(session, input, cookies, options.connectCdp);
|
|
10161
|
+
}
|
|
10162
|
+
async function reuseAgentBayBrowserSession(session, input, cookies, options = {}) {
|
|
10163
|
+
return configureAgentBayBrowserSession(session, input, cookies, options.connectCdp);
|
|
10164
|
+
}
|
|
9744
10165
|
async function launchAgentBayBrowser(input, deps) {
|
|
9745
10166
|
const name = validateName(input.name);
|
|
9746
10167
|
const browserId = agentBayBrowserId(name);
|
|
9747
10168
|
validateAgentBayInput(input);
|
|
10169
|
+
const reuseMode = resolveReuseMode(input.reuse);
|
|
9748
10170
|
const imageId = resolveAgentBayImageId(input.imageId);
|
|
9749
10171
|
const persistent = input.persistent === true;
|
|
9750
10172
|
const userAgentMode = resolveUserAgentMode(input.userAgentMode);
|
|
9751
10173
|
const copyCookieDomains = input.copyCookieDomains === void 0 ? [] : normalizeCookieDomains(input.copyCookieDomains);
|
|
9752
10174
|
const apiKey = (deps.resolveApiKey ?? resolveAgentBayApiKey)();
|
|
10175
|
+
const lifecycle = resolveAgentBayLifecycleConfig();
|
|
9753
10176
|
const releaseLock = acquireLaunchLock(browserId);
|
|
9754
10177
|
const persistBrowserRecord = deps.persistBrowserRecord ?? ((record2) => writeRecord(keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, record2.browserId), record2));
|
|
10178
|
+
const findBrowserRecord = deps.findBrowserRecord ?? findRecordWithStatus;
|
|
9755
10179
|
let session = null;
|
|
9756
10180
|
let context;
|
|
9757
10181
|
let hostPid = 0;
|
|
9758
10182
|
let recordWritten = false;
|
|
9759
10183
|
try {
|
|
9760
|
-
|
|
10184
|
+
const existing = findBrowserRecord(browserId);
|
|
10185
|
+
if (existing) {
|
|
10186
|
+
if (reuseMode === "never") {
|
|
10187
|
+
throw new Error(`AgentBay browser already exists: ${browserId}`);
|
|
10188
|
+
}
|
|
10189
|
+
if (!existing.running) {
|
|
10190
|
+
throw new Error(`AgentBay browser is not running and cannot be reused: ${browserId}`);
|
|
10191
|
+
}
|
|
10192
|
+
validateReusableAgentBayConfiguration(input, existing.record);
|
|
10193
|
+
const cookies2 = copyCookieDomains.length > 0 ? await deps.exportCookies(copyCookieDomains) : [];
|
|
10194
|
+
const agentBay2 = await (deps.createAgentBayClient ?? createClient)(apiKey);
|
|
10195
|
+
const existingSession = await requireSessionFromClient(agentBay2, existing.record.sessionId);
|
|
10196
|
+
const copiedCookieCount2 = await reuseAgentBayBrowserSession(existingSession, input, cookies2, {
|
|
10197
|
+
connectCdp: deps.connectCdp
|
|
10198
|
+
});
|
|
10199
|
+
const record3 = {
|
|
10200
|
+
...existing.record,
|
|
10201
|
+
...copyCookieDomains.length > 0 ? { copiedCookies: { domains: copyCookieDomains, count: copiedCookieCount2 } } : {}
|
|
10202
|
+
};
|
|
10203
|
+
if (copyCookieDomains.length > 0) persistBrowserRecord(record3);
|
|
10204
|
+
deps.log(`AgentBay browser reused: ${browserId}, host=${record3.pid}`);
|
|
10205
|
+
return {
|
|
10206
|
+
...browserSummary2(record3, true),
|
|
10207
|
+
created: false,
|
|
10208
|
+
reused: true
|
|
10209
|
+
};
|
|
10210
|
+
}
|
|
10211
|
+
if (reuseMode === "require") {
|
|
10212
|
+
throw new Error(`AgentBay browser is not running and reuse=require: ${browserId}`);
|
|
10213
|
+
}
|
|
9761
10214
|
const [cookies, userAgent] = await Promise.all([
|
|
9762
10215
|
copyCookieDomains.length > 0 ? deps.exportCookies(copyCookieDomains) : Promise.resolve([]),
|
|
9763
10216
|
userAgentMode === "desktop" ? deps.getBrowserUserAgent().then(normalizeDesktopUserAgent) : Promise.resolve(void 0)
|
|
9764
10217
|
]);
|
|
9765
10218
|
const agentBay = await (deps.createAgentBayClient ?? createClient)(apiKey);
|
|
9766
10219
|
context = persistent ? await resolvePersistentContext(agentBay, browserId) : void 0;
|
|
10220
|
+
const { LifecyclePolicy } = await loadSdk();
|
|
9767
10221
|
const created = await agentBay.create({
|
|
9768
10222
|
imageId,
|
|
9769
10223
|
framework: "mearl",
|
|
10224
|
+
lifecyclePolicy: new LifecyclePolicy(lifecycle),
|
|
9770
10225
|
...context ? {
|
|
9771
10226
|
browserContext: {
|
|
9772
10227
|
contextId: context.id,
|
|
@@ -9780,7 +10235,8 @@ async function launchAgentBayBrowser(input, deps) {
|
|
|
9780
10235
|
}
|
|
9781
10236
|
session = created.session;
|
|
9782
10237
|
const copiedCookieCount = await initializeAgentBayBrowserSession(session, input, cookies, {
|
|
9783
|
-
userAgent
|
|
10238
|
+
userAgent,
|
|
10239
|
+
connectCdp: deps.connectCdp
|
|
9784
10240
|
});
|
|
9785
10241
|
const host = spawn4(
|
|
9786
10242
|
process.execPath,
|
|
@@ -9792,7 +10248,9 @@ async function launchAgentBayBrowser(input, deps) {
|
|
|
9792
10248
|
"--browser-id",
|
|
9793
10249
|
browserId,
|
|
9794
10250
|
"--browser-name",
|
|
9795
|
-
name
|
|
10251
|
+
name,
|
|
10252
|
+
"--agentbay-idle-release-minutes",
|
|
10253
|
+
String(lifecycle.idleReleaseTimeout)
|
|
9796
10254
|
],
|
|
9797
10255
|
{
|
|
9798
10256
|
detached: true,
|
|
@@ -9813,6 +10271,7 @@ async function launchAgentBayBrowser(input, deps) {
|
|
|
9813
10271
|
name,
|
|
9814
10272
|
sessionId: session.sessionId,
|
|
9815
10273
|
imageId,
|
|
10274
|
+
userAgentMode,
|
|
9816
10275
|
persistent,
|
|
9817
10276
|
...context ? { contextId: context.id, contextName: context.name } : {},
|
|
9818
10277
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9822,7 +10281,11 @@ async function launchAgentBayBrowser(input, deps) {
|
|
|
9822
10281
|
recordWritten = true;
|
|
9823
10282
|
await waitForBrowserRegistration(browserId, hostPid);
|
|
9824
10283
|
deps.log(`AgentBay browser launched: ${browserId}, host=${hostPid}`);
|
|
9825
|
-
return
|
|
10284
|
+
return {
|
|
10285
|
+
...browserSummary2(record2, true),
|
|
10286
|
+
created: true,
|
|
10287
|
+
reused: false
|
|
10288
|
+
};
|
|
9826
10289
|
} catch (error48) {
|
|
9827
10290
|
if (hostPid) await stopProcess(hostPid, `--browser-id ${browserId}`);
|
|
9828
10291
|
if (recordWritten) {
|
|
@@ -9846,6 +10309,7 @@ async function launchAgentBayBrowser(input, deps) {
|
|
|
9846
10309
|
name,
|
|
9847
10310
|
sessionId: session.sessionId,
|
|
9848
10311
|
imageId,
|
|
10312
|
+
userAgentMode,
|
|
9849
10313
|
persistent,
|
|
9850
10314
|
...context ? { contextId: context.id, contextName: context.name } : {},
|
|
9851
10315
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -9940,31 +10404,38 @@ async function releaseAgentBayBrowserResources(record2, agentBay, options = {})
|
|
|
9940
10404
|
};
|
|
9941
10405
|
}
|
|
9942
10406
|
async function closeAgentBayBrowser(selector, options = {}) {
|
|
9943
|
-
|
|
10407
|
+
let record2 = findRecord(selector);
|
|
9944
10408
|
if (!record2) throw new Error(`AgentBay browser not found: ${selector}`);
|
|
9945
10409
|
const agentBay = await createClient(resolveAgentBayApiKey());
|
|
9946
|
-
const
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
10410
|
+
const releaseLock = acquireLaunchLock(record2.browserId);
|
|
10411
|
+
try {
|
|
10412
|
+
record2 = findRecord(record2.browserId);
|
|
10413
|
+
if (!record2) throw new Error(`AgentBay browser not found: ${selector}`);
|
|
10414
|
+
const released = await releaseAgentBayBrowserResources(record2, agentBay, {
|
|
10415
|
+
...options,
|
|
10416
|
+
persistRecord: (updatedRecord) => writeRecord(
|
|
10417
|
+
keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, updatedRecord.browserId),
|
|
10418
|
+
updatedRecord
|
|
10419
|
+
)
|
|
10420
|
+
});
|
|
10421
|
+
const closingOwnHost = record2.pid === process.pid;
|
|
10422
|
+
if (!closingOwnHost) await stopProcess(record2.pid, `--browser-id ${record2.browserId}`);
|
|
10423
|
+
removeRecord(keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, record2.browserId));
|
|
10424
|
+
removeRecord(keyedConfigFile("browsers", record2.browserId));
|
|
10425
|
+
if (closingOwnHost) {
|
|
10426
|
+
const timer = setTimeout(() => process.kill(process.pid, "SIGTERM"), 250);
|
|
10427
|
+
timer.unref();
|
|
10428
|
+
}
|
|
10429
|
+
return {
|
|
10430
|
+
browserId: record2.browserId,
|
|
10431
|
+
provider: "agentbay",
|
|
10432
|
+
closed: true,
|
|
10433
|
+
...released,
|
|
10434
|
+
...record2.persistent === true ? { profileDeleted: options.deleteProfile === true } : { profileDeleted: true }
|
|
10435
|
+
};
|
|
10436
|
+
} finally {
|
|
10437
|
+
releaseLock();
|
|
9960
10438
|
}
|
|
9961
|
-
return {
|
|
9962
|
-
browserId: record2.browserId,
|
|
9963
|
-
provider: "agentbay",
|
|
9964
|
-
closed: true,
|
|
9965
|
-
...released,
|
|
9966
|
-
...record2.persistent === true ? { profileDeleted: options.deleteProfile === true } : { profileDeleted: true }
|
|
9967
|
-
};
|
|
9968
10439
|
}
|
|
9969
10440
|
|
|
9970
10441
|
// src/browser-registry.ts
|
|
@@ -10163,7 +10634,7 @@ import os2 from "node:os";
|
|
|
10163
10634
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10164
10635
|
|
|
10165
10636
|
// src/version.ts
|
|
10166
|
-
var NATIVE_HOST_VERSION = true ? "2.
|
|
10637
|
+
var NATIVE_HOST_VERSION = true ? "2.7.0" : "unknown";
|
|
10167
10638
|
|
|
10168
10639
|
// src/analytics.ts
|
|
10169
10640
|
var MEASUREMENT_ID = "G-W84LJDZ6PH";
|
|
@@ -30153,6 +30624,7 @@ var AgentSkillManager = class {
|
|
|
30153
30624
|
"--agent",
|
|
30154
30625
|
providerId,
|
|
30155
30626
|
scope === "global" ? "--global" : "--project",
|
|
30627
|
+
"--full-depth",
|
|
30156
30628
|
"--yes"
|
|
30157
30629
|
];
|
|
30158
30630
|
const output = await this.run(executable, args, cwd);
|
|
@@ -30229,7 +30701,7 @@ function createExtensionRequestMessage(id, action, data, trustedInternal = false
|
|
|
30229
30701
|
id,
|
|
30230
30702
|
action,
|
|
30231
30703
|
data,
|
|
30232
|
-
controlSource,
|
|
30704
|
+
controlSource: toControlSourceWireValue(controlSource),
|
|
30233
30705
|
...trustedInternal ? { trustedInternal: true } : {}
|
|
30234
30706
|
};
|
|
30235
30707
|
}
|
|
@@ -30654,9 +31126,22 @@ function positivePort(value, fallback) {
|
|
|
30654
31126
|
}
|
|
30655
31127
|
return port;
|
|
30656
31128
|
}
|
|
31129
|
+
function agentBayIdleReleaseMinutes(value) {
|
|
31130
|
+
if (value === void 0) return void 0;
|
|
31131
|
+
const minutes = Number(value);
|
|
31132
|
+
if (!Number.isInteger(minutes) || minutes < MIN_AGENTBAY_IDLE_RELEASE_MINUTES) {
|
|
31133
|
+
throw new Error(
|
|
31134
|
+
`Invalid --agentbay-idle-release-minutes: ${value}. Expected an integer of at least ${MIN_AGENTBAY_IDLE_RELEASE_MINUTES}.`
|
|
31135
|
+
);
|
|
31136
|
+
}
|
|
31137
|
+
return minutes;
|
|
31138
|
+
}
|
|
30657
31139
|
var CDP_PORT = positivePort(cliOption("--cdp-port"), 9222);
|
|
30658
31140
|
var CDP_USER_DATA_DIR = cliOption("--cdp-user-data-dir");
|
|
30659
31141
|
var AGENTBAY_SESSION_ID = cliOption("--agentbay-session-id");
|
|
31142
|
+
var AGENTBAY_IDLE_RELEASE_MINUTES = agentBayIdleReleaseMinutes(
|
|
31143
|
+
cliOption("--agentbay-idle-release-minutes")
|
|
31144
|
+
);
|
|
30660
31145
|
var CDP_BROWSER_ID = cliOption("--browser-id") ?? "cdp";
|
|
30661
31146
|
var CDP_BROWSER_NAME = cliOption("--browser-name") ?? "CDP";
|
|
30662
31147
|
if (cliArg === "--init" || cliArg === "--uninstall") {
|
|
@@ -30684,6 +31169,8 @@ Options:
|
|
|
30684
31169
|
--cdp-port <port> CDP port for daemon mode (default 9222)
|
|
30685
31170
|
--cdp-user-data-dir <dir> Read DevToolsActivePort from this Chrome profile
|
|
30686
31171
|
--agentbay-session-id <id> Connect to an AgentBay browser session over CDP
|
|
31172
|
+
--agentbay-idle-release-minutes <minutes>
|
|
31173
|
+
Close an idle AgentBay browser (minimum 3 minutes)
|
|
30687
31174
|
--browser-id <id> Registry id for this CDP browser
|
|
30688
31175
|
--browser-name <name> Human-readable registry name
|
|
30689
31176
|
--version, -v Show version
|
|
@@ -30818,6 +31305,7 @@ var socketServer = null;
|
|
|
30818
31305
|
var legacyServer = null;
|
|
30819
31306
|
var inventoryExitTimer = null;
|
|
30820
31307
|
var cdpRegistrationTimer = null;
|
|
31308
|
+
var agentBaySessionWatchdog = null;
|
|
30821
31309
|
function scheduleInventoryExit() {
|
|
30822
31310
|
if (inventoryExitTimer) clearTimeout(inventoryExitTimer);
|
|
30823
31311
|
inventoryExitTimer = setTimeout(() => cleanupAndExit(0), INVENTORY_IDLE_EXIT_MS);
|
|
@@ -31085,7 +31573,7 @@ function toTimeoutMs(action, data) {
|
|
|
31085
31573
|
}
|
|
31086
31574
|
return DEFAULT_TIMEOUT_MS;
|
|
31087
31575
|
}
|
|
31088
|
-
async function
|
|
31576
|
+
async function handleSocketRequest(socket, line) {
|
|
31089
31577
|
let request;
|
|
31090
31578
|
try {
|
|
31091
31579
|
request = JSON.parse(line);
|
|
@@ -31096,7 +31584,7 @@ async function handleSocketLine(socket, line) {
|
|
|
31096
31584
|
return;
|
|
31097
31585
|
}
|
|
31098
31586
|
const { id = "", action, data = {}, version: clientVersion } = request;
|
|
31099
|
-
const controlSource = request.controlSource
|
|
31587
|
+
const controlSource = normalizeControlSource(request.controlSource);
|
|
31100
31588
|
if (action === "start_cloud_connector") {
|
|
31101
31589
|
const serverUrl = data?.serverUrl;
|
|
31102
31590
|
if (!serverUrl || typeof serverUrl !== "string") {
|
|
@@ -31386,6 +31874,33 @@ async function handleSocketLine(socket, line) {
|
|
|
31386
31874
|
socket.write(JSON.stringify({ id, success: false, error: message }) + "\n");
|
|
31387
31875
|
}
|
|
31388
31876
|
}
|
|
31877
|
+
async function handleSocketLine(socket, line) {
|
|
31878
|
+
const watchdog = agentBaySessionWatchdog;
|
|
31879
|
+
if (!watchdog) {
|
|
31880
|
+
await handleSocketRequest(socket, line);
|
|
31881
|
+
return;
|
|
31882
|
+
}
|
|
31883
|
+
if (!watchdog.beginActivity()) {
|
|
31884
|
+
let id = "";
|
|
31885
|
+
try {
|
|
31886
|
+
id = JSON.parse(line)?.id ?? "";
|
|
31887
|
+
} catch {
|
|
31888
|
+
}
|
|
31889
|
+
socket.write(
|
|
31890
|
+
JSON.stringify({
|
|
31891
|
+
id,
|
|
31892
|
+
success: false,
|
|
31893
|
+
error: "AgentBay browser is closing after reaching its idle timeout"
|
|
31894
|
+
}) + "\n"
|
|
31895
|
+
);
|
|
31896
|
+
return;
|
|
31897
|
+
}
|
|
31898
|
+
try {
|
|
31899
|
+
await handleSocketRequest(socket, line);
|
|
31900
|
+
} finally {
|
|
31901
|
+
watchdog.endActivity();
|
|
31902
|
+
}
|
|
31903
|
+
}
|
|
31389
31904
|
async function handleViaCdp(action, data, _timeoutMs, controlSource) {
|
|
31390
31905
|
if (action === "set_rule" || action === "get_rules") {
|
|
31391
31906
|
throw new Error(
|
|
@@ -31544,6 +32059,8 @@ function cleanupAndExit(code) {
|
|
|
31544
32059
|
clearInterval(cdpRegistrationTimer);
|
|
31545
32060
|
cdpRegistrationTimer = null;
|
|
31546
32061
|
}
|
|
32062
|
+
agentBaySessionWatchdog?.stop();
|
|
32063
|
+
agentBaySessionWatchdog = null;
|
|
31547
32064
|
cdpSession?.close();
|
|
31548
32065
|
agentChatManager?.dispose();
|
|
31549
32066
|
agentChatManager = null;
|
|
@@ -31630,15 +32147,17 @@ async function main() {
|
|
|
31630
32147
|
await monitorDaemonBrowser();
|
|
31631
32148
|
}
|
|
31632
32149
|
if (AGENTBAY_SESSION_ID) {
|
|
31633
|
-
const
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
);
|
|
31639
|
-
}
|
|
31640
|
-
|
|
31641
|
-
|
|
32150
|
+
const idleReleaseMinutes = AGENTBAY_IDLE_RELEASE_MINUTES ?? resolveAgentBayLifecycleConfig().idleReleaseTimeout;
|
|
32151
|
+
agentBaySessionWatchdog = new AgentBaySessionWatchdog({
|
|
32152
|
+
idleTimeoutMs: idleReleaseMinutes * 6e4,
|
|
32153
|
+
keepAlive: () => keepAgentBaySessionAlive(AGENTBAY_SESSION_ID),
|
|
32154
|
+
close: async () => {
|
|
32155
|
+
await closeAgentBayBrowser(CDP_BROWSER_ID);
|
|
32156
|
+
},
|
|
32157
|
+
log
|
|
32158
|
+
});
|
|
32159
|
+
agentBaySessionWatchdog.start();
|
|
32160
|
+
log(`AgentBay idle close enabled for ${AGENTBAY_SESSION_ID}: ${idleReleaseMinutes} minutes`);
|
|
31642
32161
|
}
|
|
31643
32162
|
process.on("SIGINT", () => cleanupAndExit(0));
|
|
31644
32163
|
process.on("SIGTERM", () => cleanupAndExit(0));
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type ControlSource, type ControlSourceWireValue } from '@mearl/browser-core';
|
|
1
2
|
export declare function isInternalExtensionAction(action: unknown): boolean;
|
|
2
|
-
export declare function createExtensionRequestMessage(id: string, action: string, data: any, trustedInternal?: boolean, controlSource?:
|
|
3
|
+
export declare function createExtensionRequestMessage(id: string, action: string, data: any, trustedInternal?: boolean, controlSource?: ControlSource): {
|
|
3
4
|
id: string;
|
|
4
5
|
action: string;
|
|
5
6
|
data: any;
|
|
6
|
-
controlSource:
|
|
7
|
+
controlSource: ControlSourceWireValue;
|
|
7
8
|
trustedInternal?: true;
|
|
8
9
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/native-host",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Native Messaging Host for Mearl — bridges Chrome Extension and local socket server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/install.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"@types/ws": "^8.18.1",
|
|
38
38
|
"esbuild": "^0.25.2",
|
|
39
39
|
"typescript": "^5.8.3",
|
|
40
|
-
"@mearl/browser-core": "2.
|
|
41
|
-
"@mearl/
|
|
42
|
-
"@mearl/
|
|
40
|
+
"@mearl/browser-core": "2.7.0",
|
|
41
|
+
"@mearl/daemon-core": "2.7.0",
|
|
42
|
+
"@mearl/setup": "2.7.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "rm -rf dist/ && node build.mjs",
|