@mearl/native-host 2.0.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.
Files changed (43) hide show
  1. package/README.md +99 -0
  2. package/dist/agent/codex-provider.d.ts +45 -0
  3. package/dist/agent/event-transport.d.ts +3 -0
  4. package/dist/agent/instructions.d.ts +1 -0
  5. package/dist/agent/manager.d.ts +44 -0
  6. package/dist/agent/process-env.d.ts +6 -0
  7. package/dist/agent/provider.d.ts +53 -0
  8. package/dist/agent/qoder-provider.d.ts +49 -0
  9. package/dist/agent/skill-manager.d.ts +28 -0
  10. package/dist/agent/workspace-directory.d.ts +4 -0
  11. package/dist/analytics.d.ts +1 -0
  12. package/dist/browser-inventory.d.ts +33 -0
  13. package/dist/browser-registry.d.ts +78 -0
  14. package/dist/cdp-backend/action-registry.d.ts +35 -0
  15. package/dist/cdp-backend/browser-handlers.d.ts +7 -0
  16. package/dist/cdp-backend/buffers.d.ts +46 -0
  17. package/dist/cdp-backend/cdp-session.d.ts +60 -0
  18. package/dist/cdp-backend/cdp-utils.d.ts +66 -0
  19. package/dist/cdp-backend/client.d.ts +47 -0
  20. package/dist/cdp-backend/composite-handlers.d.ts +19 -0
  21. package/dist/cdp-backend/controlled-indicator.d.ts +17 -0
  22. package/dist/cdp-backend/cookie-transfer.d.ts +25 -0
  23. package/dist/cdp-backend/discover.d.ts +28 -0
  24. package/dist/cdp-backend/domain-manager.d.ts +4 -0
  25. package/dist/cdp-backend/handlers.d.ts +29 -0
  26. package/dist/cdp-backend/http-handlers.d.ts +7 -0
  27. package/dist/cdp-backend/interaction-handlers.d.ts +16 -0
  28. package/dist/cdp-backend/mock-handlers.d.ts +6 -0
  29. package/dist/cdp-backend/navigation-observer.d.ts +7 -0
  30. package/dist/cdp-backend/page-observer.d.ts +5 -0
  31. package/dist/cdp-backend/recording-handlers.d.ts +9 -0
  32. package/dist/cdp-backend/request-handlers.d.ts +7 -0
  33. package/dist/cdp-backend/snapshot-handlers.d.ts +6 -0
  34. package/dist/cdp-backend/tab-handlers.d.ts +7 -0
  35. package/dist/cdp-backend/tdbank-handlers.d.ts +13 -0
  36. package/dist/cdp-backend/timezone-handlers.d.ts +3 -0
  37. package/dist/cloud-connector.d.ts +56 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +26945 -0
  40. package/dist/install.d.ts +36 -0
  41. package/dist/install.js +220 -0
  42. package/dist/managed-browser.d.ts +85 -0
  43. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @mearl/native-host
2
+
3
+ Mearl 的 Native Messaging Host —— 桥接 Chrome 扩展与本地 socket 服务。
4
+
5
+ 浏览器扩展通过 `chrome.runtime.connectNative()` 拉起本进程;进程对内通过 CDP(Chrome DevTools Protocol)与浏览器交互,对外通过 **Unix Socket** 暴露给 `@mearl/client` 等本地调用方,并可作为云端链路的本地落点。
6
+
7
+ ## 安装与注册
8
+
9
+ 注册 Native Messaging Host manifest(让浏览器能够拉起本进程):
10
+
11
+ ```bash
12
+ # 同时注册 Skills 与 MCP 两种集成模式(默认)
13
+ mearl-native-host --init
14
+
15
+ # 仅注册某一种模式
16
+ mearl-native-host --init --mode skills
17
+ mearl-native-host --init --mode mcp
18
+
19
+ # 指定自定义扩展 ID(开发版)
20
+ mearl-native-host --init --extension-id <your-extension-id>
21
+ ```
22
+
23
+ > Skills 与 MCP 两个 host 运行同一份二进制、共享同一个 Unix socket,因此可同时启用。
24
+
25
+ 也可在代码中调用安装入口:
26
+
27
+ ```typescript
28
+ import { installNativeHost } from '@mearl/native-host';
29
+
30
+ await installNativeHost(); // 默认注册两种模式
31
+ ```
32
+
33
+ 运行时入口(被浏览器拉起、或手动启动)通过子路径导出:
34
+
35
+ ```typescript
36
+ import '@mearl/native-host/runtime';
37
+ ```
38
+
39
+ ## CLI 选项
40
+
41
+ ```bash
42
+ mearl-native-host [options]
43
+
44
+ --init [--mode <skills|mcp|both>] [--extension-id <id>] 注册 host manifest
45
+ --daemon 独立 CDP daemon
46
+ --cdp-port <port> 指定 CDP 端口
47
+ --cdp-user-data-dir <dir> 指定 Chrome Profile
48
+ --browser-id <id> 指定浏览器注册 ID
49
+ --browser-name <name> 指定浏览器显示名称
50
+ --version, -v 查看版本
51
+ --help, -h 查看帮助
52
+ ```
53
+
54
+ ## 架构
55
+
56
+ ```
57
+ Chrome Extension
58
+ ↓ (Native Messaging)
59
+ @mearl/native-host ← CDP → Chrome
60
+ ↓ (Unix Socket)
61
+ @mearl/client / @mearl/cloud-connector
62
+ ```
63
+
64
+ `browser_launch` 会为每个托管 Chrome 启动一个带独立 CDP 配置的 native-host 子进程,使多个 headless/Profile/TDBank 账号实例可以同时被现有浏览器注册表寻址。设计细节见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
65
+
66
+ ## 侧边栏 Agent Chat
67
+
68
+ 扩展侧边栏通过 Native Messaging 调用 native-host,再按用户选择启动持久的 Codex app-server 或 Qoder ACP 进程。两种 Provider 都支持流式消息、思考与工具进度、中断和审批,并复用各自 CLI 已保存的本地登录态。
69
+
70
+ 需要先安装并登录支持 `app-server` 的 Codex CLI。若事件中的模型错误提示需要更新版本,应先升级本机 Codex CLI。native-host 会依次从安装时的 Node 可执行文件目录、常见全局安装目录和 `PATH` 查找 `codex`;自定义路径可通过环境变量指定:
71
+
72
+ ```bash
73
+ MEARL_CODEX_PATH=/absolute/path/to/codex
74
+ ```
75
+
76
+ Qoder 使用官方 ACP 接口,需要先安装 Qoder CLI 并完成登录:
77
+
78
+ ```bash
79
+ # macOS / Linux
80
+ curl -fsSL https://qoder.com/install | bash
81
+ qodercli
82
+ # 进入交互界面后输入 /login
83
+
84
+ # 可选:指定非标准安装位置
85
+ MEARL_QODER_PATH=/absolute/path/to/qodercli
86
+ ```
87
+
88
+ 侧边栏中未选择本地目录时,Codex 对话使用只读沙箱且不允许提权,Qoder 对话通过首条提示词约束为只读模式;选择后才使用该目录作为可写工作区。`MEARL_AGENT_CWD` 仅用于 Codex 只读对话的默认浏览目录,不会开启写权限;Qoder 未选目录时从用户主目录开始浏览。Agent 接入由统一 Provider 接口承载,后续可新增 Claude 等实现而无需修改扩展与 native-host 之间的 Chat 协议。只安装 ChatGPT 等桌面聊天客户端不能替代 Provider CLI;侧边栏会在 CLI 缺失时展示安装与登录指引。
89
+
90
+ ## 构建
91
+
92
+ ```bash
93
+ pnpm build # esbuild 打包到 dist/(内联 browser-core / daemon-core)
94
+ pnpm typecheck # 仅类型检查
95
+ ```
96
+
97
+ ## License
98
+
99
+ ISC
@@ -0,0 +1,45 @@
1
+ import type { AgentChatImageInput, AgentChatProviderDescriptor, AgentInstalledSkill } from '@mearl/browser-core';
2
+ import type { AgentProvider, AgentProviderEvent, AgentProviderListSessionsOptions, AgentProviderListSessionsResult, AgentProviderResumeResult, AgentProviderSessionOptions } from './provider.js';
3
+ export declare function codexThreadUrl(providerSessionId: string): string;
4
+ export declare function externalOpenCommand(url: string, platform?: NodeJS.Platform): {
5
+ command: string;
6
+ args: string[];
7
+ };
8
+ export declare class CodexAgentProvider implements AgentProvider {
9
+ private readonly log;
10
+ private readonly clientVersion;
11
+ readonly id = "codex";
12
+ private process;
13
+ private initialized;
14
+ private startPromise;
15
+ private nextRequestId;
16
+ private pendingRequests;
17
+ private listeners;
18
+ private knownThreadIds;
19
+ constructor(log: (...args: any[]) => void, clientVersion: string);
20
+ getDescriptor(): Promise<AgentChatProviderDescriptor>;
21
+ onEvent(listener: (event: AgentProviderEvent) => void): () => void;
22
+ listSessions(options: AgentProviderListSessionsOptions): Promise<AgentProviderListSessionsResult>;
23
+ listSkills(cwd?: string): Promise<AgentInstalledSkill[]>;
24
+ startSession(options: AgentProviderSessionOptions): Promise<string>;
25
+ resumeSession(providerSessionId: string, options: AgentProviderSessionOptions): Promise<AgentProviderResumeResult>;
26
+ sendMessage(providerSessionId: string, message: string, images?: AgentChatImageInput[]): Promise<{
27
+ turnId: string;
28
+ }>;
29
+ interrupt(providerSessionId: string, turnId: string): Promise<void>;
30
+ closeSession(providerSessionId: string): Promise<void>;
31
+ openSession(providerSessionId: string): Promise<void>;
32
+ respondToRequest(requestId: string | number, result: unknown): void;
33
+ dispose(): void;
34
+ private ensureStarted;
35
+ private start;
36
+ private handleLine;
37
+ private request;
38
+ private write;
39
+ private handleExit;
40
+ private rejectPending;
41
+ private emit;
42
+ private extractThreadId;
43
+ private probeExecutable;
44
+ private resolveExecutable;
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentChatEvent } from '@mearl/browser-core';
2
+ export declare const MAX_AGENT_EVENT_BYTES: number;
3
+ export declare function sanitizeAgentChatEvent(event: AgentChatEvent): AgentChatEvent;
@@ -0,0 +1 @@
1
+ export declare function createBrowserDeveloperInstructions(tabId: number): string;
@@ -0,0 +1,44 @@
1
+ import type { AgentChatEvent, AgentChatCloseOptions, AgentChatEventsOptions, AgentChatEventsResult, AgentChatInterruptOptions, AgentChatListSessionsOptions, AgentChatListSessionsResult, AgentChatOpenOptions, AgentChatRefreshResult, AgentChatRespondOptions, AgentChatSendOptions, AgentChatSession, AgentChatStartOptions, AgentChatStatus, AgentInstalledSkill, AgentSkillReadResult } from '@mearl/browser-core';
2
+ import type { AgentProvider } from './provider.js';
3
+ export declare class AgentChatManager {
4
+ private readonly emit;
5
+ private readonly providers;
6
+ private readonly sessions;
7
+ private readonly sessionByProviderThread;
8
+ private readonly pendingRequests;
9
+ private readonly completedTurns;
10
+ private readonly recentEvents;
11
+ private readonly recentEventBytes;
12
+ private readonly unsubscribe;
13
+ constructor(providers: AgentProvider[], emit: (event: AgentChatEvent) => void);
14
+ getStatus(): Promise<AgentChatStatus>;
15
+ listSkills(options: {
16
+ providerId?: unknown;
17
+ cwd?: unknown;
18
+ }): Promise<AgentInstalledSkill[]>;
19
+ readSkill(options: {
20
+ providerId?: unknown;
21
+ path?: unknown;
22
+ cwd?: unknown;
23
+ }): Promise<AgentSkillReadResult>;
24
+ listSessions(options: AgentChatListSessionsOptions): Promise<AgentChatListSessionsResult>;
25
+ startSession(options: AgentChatStartOptions): Promise<AgentChatSession>;
26
+ sendMessage(options: AgentChatSendOptions): Promise<{
27
+ turnId: string;
28
+ }>;
29
+ interrupt(options: AgentChatInterruptOptions): Promise<void>;
30
+ openSession(options: AgentChatOpenOptions): Promise<void>;
31
+ closeSession(options: AgentChatCloseOptions): Promise<void>;
32
+ getEvents(options: AgentChatEventsOptions): AgentChatEventsResult;
33
+ refreshSession(options: AgentChatEventsOptions): Promise<AgentChatRefreshResult>;
34
+ respondToRequest(options: AgentChatRespondOptions): void;
35
+ dispose(): void;
36
+ private handleProviderEvent;
37
+ private emitEvent;
38
+ private recordEvent;
39
+ private getProvider;
40
+ private getSession;
41
+ private providerThreadKey;
42
+ private providerRequestKey;
43
+ private sessionTurnKey;
44
+ }
@@ -0,0 +1,6 @@
1
+ export interface AgentSpawnCommand {
2
+ command: string;
3
+ args: string[];
4
+ }
5
+ export declare function agentSpawnCommand(executable: string, args: string[], platform?: NodeJS.Platform, commandInterpreter?: string | undefined): AgentSpawnCommand;
6
+ export declare function createAgentChildEnv(sourceEnv?: NodeJS.ProcessEnv, nodeExecutable?: string): NodeJS.ProcessEnv;
@@ -0,0 +1,53 @@
1
+ import type { AgentChatEventKind, AgentChatImageInput, AgentChatProviderDescriptor, AgentInstalledSkill } from '@mearl/browser-core';
2
+ export interface AgentProviderEvent {
3
+ providerSessionId?: string;
4
+ kind: AgentChatEventKind;
5
+ method: string;
6
+ params: unknown;
7
+ timestamp?: number;
8
+ requestId?: string | number;
9
+ }
10
+ export interface AgentProviderSessionOptions {
11
+ cwd?: string;
12
+ developerInstructions?: string;
13
+ ephemeral?: boolean;
14
+ }
15
+ export interface AgentProviderSessionSummary {
16
+ providerSessionId: string;
17
+ title?: string;
18
+ cwd?: string;
19
+ updatedAt?: number;
20
+ ephemeral?: boolean;
21
+ }
22
+ export interface AgentProviderListSessionsOptions {
23
+ cwd?: string;
24
+ cursor?: string;
25
+ limit: number;
26
+ }
27
+ export interface AgentProviderListSessionsResult {
28
+ sessions: AgentProviderSessionSummary[];
29
+ nextCursor?: string;
30
+ }
31
+ export interface AgentProviderResumeResult {
32
+ providerSessionId: string;
33
+ history?: AgentProviderEvent[];
34
+ historyComplete?: boolean;
35
+ }
36
+ export interface AgentProvider {
37
+ readonly id: string;
38
+ getDescriptor(): Promise<AgentChatProviderDescriptor>;
39
+ onEvent(listener: (event: AgentProviderEvent) => void): () => void;
40
+ listSessions?(options: AgentProviderListSessionsOptions): Promise<AgentProviderListSessionsResult>;
41
+ listSkills?(cwd?: string): Promise<AgentInstalledSkill[]>;
42
+ startSession(options: AgentProviderSessionOptions): Promise<string>;
43
+ resumeSession(providerSessionId: string, options: AgentProviderSessionOptions): Promise<AgentProviderResumeResult>;
44
+ validateMessageInput?(images: AgentChatImageInput[]): Promise<void>;
45
+ sendMessage(providerSessionId: string, message: string, images?: AgentChatImageInput[]): Promise<{
46
+ turnId: string;
47
+ }>;
48
+ interrupt(providerSessionId: string, turnId: string): Promise<void>;
49
+ closeSession(providerSessionId: string): Promise<void>;
50
+ openSession?(providerSessionId: string): Promise<void>;
51
+ respondToRequest(requestId: string | number, result: unknown): void;
52
+ dispose(): void;
53
+ }
@@ -0,0 +1,49 @@
1
+ import type { AgentChatImageInput, AgentChatProviderDescriptor, AgentInstalledSkill } from '@mearl/browser-core';
2
+ import type { AgentProvider, AgentProviderEvent, AgentProviderListSessionsOptions, AgentProviderListSessionsResult, AgentProviderResumeResult, AgentProviderSessionOptions } from './provider.js';
3
+ export declare function parseQoderSkillsList(output: string, cwd: string, home?: string): AgentInstalledSkill[];
4
+ export declare function qoderInstallCommand(platform?: NodeJS.Platform): string;
5
+ export declare class QoderAgentProvider implements AgentProvider {
6
+ private readonly log;
7
+ private readonly clientVersion;
8
+ readonly id = "qoder";
9
+ private process;
10
+ private connection;
11
+ private context;
12
+ private initializeResponse;
13
+ private startPromise;
14
+ private listeners;
15
+ private sessions;
16
+ private pendingPermissions;
17
+ private historyCaptures;
18
+ private nextTurnId;
19
+ constructor(log: (...args: any[]) => void, clientVersion: string);
20
+ getDescriptor(): Promise<AgentChatProviderDescriptor>;
21
+ onEvent(listener: (event: AgentProviderEvent) => void): () => void;
22
+ listSessions(options: AgentProviderListSessionsOptions): Promise<AgentProviderListSessionsResult>;
23
+ listSkills(cwd?: string): Promise<AgentInstalledSkill[]>;
24
+ startSession(options: AgentProviderSessionOptions): Promise<string>;
25
+ resumeSession(providerSessionId: string, options: AgentProviderSessionOptions): Promise<AgentProviderResumeResult>;
26
+ validateMessageInput(images: AgentChatImageInput[]): Promise<void>;
27
+ sendMessage(providerSessionId: string, message: string, images?: AgentChatImageInput[]): Promise<{
28
+ turnId: string;
29
+ }>;
30
+ interrupt(providerSessionId: string, _turnId: string): Promise<void>;
31
+ closeSession(providerSessionId: string): Promise<void>;
32
+ respondToRequest(requestId: string | number, result: unknown): void;
33
+ dispose(): void;
34
+ private ensureStarted;
35
+ private start;
36
+ private runPrompt;
37
+ private handleSessionUpdate;
38
+ private emitToolUpdate;
39
+ private handlePermissionRequest;
40
+ private cancelPermissions;
41
+ private handleExit;
42
+ private getContext;
43
+ private assertImageInputSupported;
44
+ private withTimeout;
45
+ private probeExecutable;
46
+ private runSkillsList;
47
+ private resolveExecutable;
48
+ private emit;
49
+ }
@@ -0,0 +1,28 @@
1
+ import type { AgentSkillCheckUpdatesResult, AgentSkillCliStatus, AgentSkillInstallOptions, AgentSkillMutationOptions, AgentSkillMutationResult, AgentSkillScope, AgentSkillUpdate } from '@mearl/browser-core';
2
+ interface SkillCliOutput {
3
+ stdout: string;
4
+ stderr: string;
5
+ }
6
+ export type SkillCliRunner = (executable: string, args: string[], cwd?: string) => Promise<SkillCliOutput>;
7
+ export interface AgentSkillManagerOptions {
8
+ resolveExecutable?: () => string | null;
9
+ run?: SkillCliRunner;
10
+ }
11
+ export declare function resolveAliSkillsExecutable(): string | null;
12
+ export declare const runSkillCli: SkillCliRunner;
13
+ export declare function parseSkillUpdates(stdout: string, scope: AgentSkillScope): AgentSkillUpdate[];
14
+ export declare class AgentSkillManager {
15
+ private readonly executableResolver;
16
+ private readonly run;
17
+ constructor(options?: AgentSkillManagerOptions);
18
+ getStatus(): Promise<AgentSkillCliStatus>;
19
+ install(data: AgentSkillInstallOptions): Promise<AgentSkillMutationResult>;
20
+ checkUpdates(data: {
21
+ providerId?: unknown;
22
+ cwd?: unknown;
23
+ }): Promise<AgentSkillCheckUpdatesResult>;
24
+ update(data: AgentSkillMutationOptions): Promise<AgentSkillMutationResult>;
25
+ uninstall(data: AgentSkillMutationOptions): Promise<AgentSkillMutationResult>;
26
+ private mutationContext;
27
+ }
28
+ export {};
@@ -0,0 +1,4 @@
1
+ export type DirectoryPickerRunner = (command: string, args: string[]) => Promise<string | null>;
2
+ export declare const runDirectoryPickerCommand: DirectoryPickerRunner;
3
+ export declare function pickWorkspaceDirectory(platform?: NodeJS.Platform, run?: DirectoryPickerRunner): Promise<string | null>;
4
+ export declare function resolveWorkspaceDirectory(directory: unknown): string;
@@ -0,0 +1 @@
1
+ export declare function trackAction(action: string, params?: Record<string, string | number | boolean>): void;
@@ -0,0 +1,33 @@
1
+ import { type BrowserRecord } from './browser-registry.js';
2
+ import { type ManagedBrowserSummary } from './managed-browser.js';
3
+ export type BrowserStatus = 'connected' | 'running_disconnected' | 'stopped';
4
+ interface BrowserInventoryBase {
5
+ browserId: string;
6
+ name: string;
7
+ status: BrowserStatus;
8
+ extensionVersion?: string;
9
+ nativeHostVersion?: string;
10
+ transport?: 'extension' | 'cdp';
11
+ }
12
+ export interface RegularBrowserInventoryItem extends BrowserInventoryBase {
13
+ type: 'regular';
14
+ status: 'connected';
15
+ }
16
+ export interface ManagedBrowserInventoryItem extends BrowserInventoryBase {
17
+ type: 'managed';
18
+ headless: boolean;
19
+ persistent: boolean;
20
+ cdpPort: number;
21
+ createdAt: string;
22
+ account?: ManagedBrowserSummary['account'];
23
+ copiedCookies?: ManagedBrowserSummary['copiedCookies'];
24
+ }
25
+ export type BrowserInventoryItem = RegularBrowserInventoryItem | ManagedBrowserInventoryItem;
26
+ export interface BrowserInventoryResult {
27
+ count: number;
28
+ connectedCount: number;
29
+ browsers: BrowserInventoryItem[];
30
+ }
31
+ export declare function mergeBrowserInventory(connected: BrowserRecord[], managed: ManagedBrowserSummary[]): BrowserInventoryResult;
32
+ export declare function listBrowserInventory(): BrowserInventoryResult;
33
+ export {};
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Browser registry — advertises this native-host's connection in a shared,
3
+ * on-disk registry so clients can discover and address multiple browsers at
4
+ * once (multiple Chrome profiles / Chromium-based browsers, each with the
5
+ * extension, each launching its own native-host).
6
+ *
7
+ * Why a registry instead of the old single well-known socket
8
+ * ----------------------------------------------------------
9
+ * Historically every native-host fought over one fixed socket; the last one to
10
+ * start kicked the others off, so at most one browser was reachable and the two
11
+ * flapped on every reconnect. Here each native-host listens on its own unique
12
+ * per-process socket and publishes a record; the client reads the registry and
13
+ * connects to the socket of whichever browser it wants (default: most-recently
14
+ * focused). No contention, N browsers reachable simultaneously.
15
+ *
16
+ * Ownership
17
+ * ---------
18
+ * The record is keyed by the browser's stable installation id, so a browser
19
+ * that reconnects (extension reload → new native-host pid → new socket) simply
20
+ * overwrites its own record. To avoid a briefly-overlapping predecessor wiping
21
+ * the successor's record during a reload, every mutation first checks that the
22
+ * on-disk record still belongs to this process (or is stale) before writing.
23
+ */
24
+ import { type DaemonRecord } from '@mearl/daemon-core';
25
+ export interface BrowserRecord extends DaemonRecord {
26
+ /** native-host pid — used for liveness pruning. */
27
+ pid: number;
28
+ /** Stable per-installation id reported by the extension (or 'cdp' for daemon mode). */
29
+ browserId: string;
30
+ /** Absolute path of the per-process socket this native-host listens on. */
31
+ socketPath: string;
32
+ /** Human-readable browser name, e.g. "Chrome", "Edge". */
33
+ browserName: string;
34
+ extensionVersion?: string;
35
+ nativeHostVersion: string;
36
+ transport: 'extension' | 'cdp';
37
+ /** Epoch ms when this browser window last gained focus; drives default target selection. */
38
+ lastFocusedAt?: number;
39
+ startedAt: string;
40
+ /** Epoch ms of the last record write; latest-wins tiebreak. */
41
+ updatedAt: number;
42
+ }
43
+ export declare function initBrowserRegistry(deps: {
44
+ log: (...args: any[]) => void;
45
+ }): void;
46
+ /**
47
+ * Publish (or refresh) this native-host's registry record. Called once the
48
+ * browser identity is known — from the extension handshake, or immediately with
49
+ * a synthetic identity in CDP daemon mode.
50
+ */
51
+ export declare function registerBrowser(info: {
52
+ browserId: string;
53
+ socketPath: string;
54
+ browserName: string;
55
+ nativeHostVersion: string;
56
+ transport: 'extension' | 'cdp';
57
+ extensionVersion?: string;
58
+ /** Initial focus rank. Managed headless browsers use 0 so they do not steal the default target. */
59
+ lastFocusedAt?: number;
60
+ }): void;
61
+ /**
62
+ * Mark this browser as most-recently focused, so it becomes the default target.
63
+ * Focus can change in rapid bursts (fast window switching), so writes are
64
+ * throttled: the first change in an interval writes immediately, further changes
65
+ * schedule a single trailing write rather than one write each.
66
+ */
67
+ export declare function touchFocus(): void;
68
+ /** Remove our record on shutdown — but only if it is still ours. */
69
+ export declare function unregisterBrowser(): void;
70
+ /**
71
+ * Reap registry records left behind by native-hosts that exited without
72
+ * cleanup (crashes). Live records are kept; a dead record is kept until it is
73
+ * older than STALE_RECORD_TTL_MS so a quickly-reconnecting browser can still
74
+ * inherit its own last-focus time. Meant to run once at startup.
75
+ */
76
+ export declare function pruneDeadBrowsers(): void;
77
+ /** Live browser connections, ordered by the same focus rank used for default routing. */
78
+ export declare function listRegisteredBrowsers(): BrowserRecord[];
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CDP action registry.
3
+ *
4
+ * Each handler sub-module registers its actions here. The registry replaces
5
+ * the giant switch/case in handleViaCdp with a data-driven lookup.
6
+ *
7
+ * Two session levels:
8
+ * - `browser`: handler receives CdpSession (for tab management, tdbank, etc.)
9
+ * - `page`: handler receives PageSession (for most page-level actions)
10
+ */
11
+ import type { CdpSession, PageSession } from './cdp-session.js';
12
+ export interface BrowserAction {
13
+ name: string;
14
+ session: 'browser';
15
+ handler: (session: CdpSession, data: any) => Promise<any>;
16
+ }
17
+ export interface PageAction {
18
+ name: string;
19
+ session: 'page';
20
+ handler: (session: PageSession, data: any) => Promise<any>;
21
+ }
22
+ export type CdpAction = BrowserAction | PageAction;
23
+ /** Register one or more CDP actions. Called by each handler sub-module. */
24
+ export declare function registerActions(actions: CdpAction[]): void;
25
+ /** Look up a registered action by name. Returns undefined if not found. */
26
+ export declare function getAction(name: string): CdpAction | undefined;
27
+ /** Get all registered action names (useful for diagnostics). */
28
+ export declare function getRegisteredActionNames(): string[];
29
+ /**
30
+ * Dispatch an action by name.
31
+ *
32
+ * Resolves the appropriate session level and calls the handler.
33
+ * Throws if the action is unknown or CDP is not available.
34
+ */
35
+ export declare function dispatchCdpAction(cdpSession: CdpSession, action: string, data: any): Promise<any>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CDP browser-level handlers: screenshot, selected element, user info.
3
+ */
4
+ import type { PageSession } from './cdp-session.js';
5
+ export declare function cdpPageScreenshot(session: PageSession, data: any): Promise<any>;
6
+ export declare function cdpPageSelectedElement(session: PageSession, _data: any): Promise<any>;
7
+ export declare function cdpGetUserInfo(session: PageSession, data: any): Promise<any>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * In-memory circular buffers for CDP network requests, console logs, and
3
+ * RUM/tracking events. A single CdpSession subscribes to CDP events and
4
+ * fills these buffers; callers read from them without CDP interaction.
5
+ */
6
+ import type { MtopRequest, RumEvent, ConsoleLog } from '@mearl/browser-core';
7
+ import type { CdpClient, CdpChildSession } from './client.js';
8
+ interface PartialRequest {
9
+ id: number;
10
+ requestId: string;
11
+ timestamp: string;
12
+ timestampMs: number;
13
+ method: string;
14
+ url: string;
15
+ apiName: string;
16
+ headers: Array<{
17
+ name: string;
18
+ value: string;
19
+ }>;
20
+ postData: any;
21
+ queryString: Array<{
22
+ name: string;
23
+ value: string;
24
+ }>;
25
+ status: number;
26
+ statusText: string;
27
+ responseHeaders: Array<{
28
+ name: string;
29
+ value: string;
30
+ }>;
31
+ responseBody: any;
32
+ isProxy: boolean;
33
+ proxyRequestUrl: string | null;
34
+ proxyData: any;
35
+ startTs: number;
36
+ }
37
+ export interface CdpBuffers {
38
+ requests: MtopRequest[];
39
+ logs: ConsoleLog[];
40
+ events: RumEvent[];
41
+ pending: Map<string, PartialRequest>;
42
+ requestCounter: number;
43
+ }
44
+ export declare function createBuffers(): CdpBuffers;
45
+ export declare function setupBufferListeners(client: CdpClient | CdpChildSession, buffers: CdpBuffers): void;
46
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * CDP session manager — maintains browser-level and page-level CDP connections.
3
+ *
4
+ * - Browser client: connected to the browser's top-level WebSocket endpoint.
5
+ * Used for Target.* commands (list/open/close tabs).
6
+ * - Page sessions: per-target connections used for page interaction, screenshots,
7
+ * and buffered network/log/event collection.
8
+ */
9
+ import { CdpClient, CdpChildSession } from './client.js';
10
+ import { type CdpBuffers } from './buffers.js';
11
+ export interface PageSession {
12
+ client: CdpClient | CdpChildSession;
13
+ buffers: CdpBuffers;
14
+ targetId: string;
15
+ url: string;
16
+ }
17
+ export declare class CdpSession {
18
+ readonly port: number;
19
+ readonly userDataDir?: string;
20
+ private browserClient;
21
+ private pageSessions;
22
+ /** Virtual tabId counter → CDP targetId */
23
+ private tabIdMap;
24
+ private nextTabId;
25
+ constructor(options?: number | {
26
+ port?: number;
27
+ userDataDir?: string;
28
+ });
29
+ getBrowserClient(): Promise<CdpClient>;
30
+ /**
31
+ * Get (or create) a page session.
32
+ *
33
+ * @param virtualTabId Numeric tabId returned by `tab_open` (optional).
34
+ * If omitted, connects to the first available page target.
35
+ */
36
+ getPageSession(virtualTabId?: number): Promise<PageSession>;
37
+ /** Register a newly opened target as a virtual tabId and return the numeric ID. */
38
+ registerTarget(targetId: string): Promise<number>;
39
+ /** Return the existing virtual tabId for a target, registering it when first discovered. */
40
+ ensureTargetRegistered(targetId: string): Promise<number>;
41
+ private createPageSession;
42
+ private findTargetById;
43
+ private findDefaultPageTarget;
44
+ /** Effective port: from DevToolsActivePort file if present, else this.port */
45
+ private effectivePort;
46
+ /**
47
+ * Resolve the browser-level WebSocket URL.
48
+ * Priority: DevToolsActivePort file → HTTP /json/version
49
+ */
50
+ private resolveBrowserWsUrl;
51
+ /**
52
+ * Build a page-level WebSocket URL from a target ID.
53
+ * Used when HTTP /json is unavailable.
54
+ */
55
+ private pageWsUrl;
56
+ private findTargetByIdViaCdp;
57
+ private findDefaultPageTargetViaCdp;
58
+ private fetchVersionInfo;
59
+ close(): void;
60
+ }