@angelky7/dsh-browser-control 0.0.0-stage → 1.2.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/LICENSE +662 -0
- package/README.en.md +318 -0
- package/README.md +311 -2
- package/cordis.patch.yml +14 -0
- package/lib/index.js +2677 -0
- package/lib/invariant.js +25 -0
- package/lib/types/errors.d.ts +64 -0
- package/lib/types/errors.js +161 -0
- package/lib/types/index.d.ts +62 -0
- package/lib/types/index.js +1343 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +23 -0
- package/lib/types/policy.d.ts +62 -0
- package/lib/types/policy.js +87 -0
- package/lib/types/server.d.ts +158 -0
- package/lib/types/server.js +678 -0
- package/lib/types/ws.d.ts +35 -0
- package/lib/types/ws.js +106 -0
- package/llms.txt +44 -0
- package/package.json +83 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-browser-bridge`.
|
|
3
|
+
* @module @deepseek-ai/dsh-browser-bridge/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "browser-bridge-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-browser-bridge`.
|
|
3
|
+
* @module @deepseek-ai/dsh-browser-bridge/invariant
|
|
4
|
+
*/
|
|
5
|
+
const PACKAGE_NAME = '@deepseek-ai/dsh-browser-bridge';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export const name = 'browser-bridge-invariant';
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export const inject = ['invariants'];
|
|
10
|
+
/**
|
|
11
|
+
* No runtime invariant: the bridge owns no durable session events or shared
|
|
12
|
+
* mutable state; its socket lifecycle relations (dispose releases the port,
|
|
13
|
+
* pending commands reject on link loss) are asserted by unit tests against
|
|
14
|
+
* the private server instance.
|
|
15
|
+
*/
|
|
16
|
+
const install = () => { };
|
|
17
|
+
/**
|
|
18
|
+
* Register this package's invariant companion.
|
|
19
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
20
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
21
|
+
*/
|
|
22
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
23
|
+
/* jscpd:ignore-end */
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本机策略闸门:只读档(`readOnly`)与脚本开关(`allowScript`)。
|
|
3
|
+
*
|
|
4
|
+
* 这两张表与 `errors.ts` 里的 `WRITE_COMMANDS` **故意不是同一张**,别合并:
|
|
5
|
+
*
|
|
6
|
+
* - `WRITE_COMMANDS` 回答「超时或掉线之后重试,会不会多留一次副作用」,那是
|
|
7
|
+
* `effectState` 的语义。所以 `tabs.group` / `tabs.ungroup` 在那里**刻意算只读**:
|
|
8
|
+
* 重复把标签归进同一个组是空操作,重试安全。
|
|
9
|
+
* - 这里回答的是「这条命令会不会改变页面、文件或浏览器状态」,用户开只读档时想要的
|
|
10
|
+
* 正是这个。所以 `tabs.group` / `tabs.ungroup` / `tabs.activate` / `emulate` /
|
|
11
|
+
* `dialog` / `network.clear` 都不在白名单里。
|
|
12
|
+
*
|
|
13
|
+
* 白名单是**默认拒绝**:扩展以后新增一条命令,它自动被只读档拦下,等人判断完再放进来。
|
|
14
|
+
* `verify/verify_policy.mjs` 会把扩展 `COMMANDS` 表逐条对账,让这个判断无法被跳过。
|
|
15
|
+
*
|
|
16
|
+
* 第三张表 `LOCAL_DESTRUCTIVE_COMMANDS` 是给**不经过扩展**的插件本地命令的:闸门如果只长在
|
|
17
|
+
* `execute()` 上,这类调用就整个绕开只读档(`browser_cleanup` 合并进来时正是这样)。
|
|
18
|
+
*/
|
|
19
|
+
/** 只读档允许的命令:读页面、读扩展自己的缓冲、或者把页面**内容**落到本机文件。 */
|
|
20
|
+
export declare const READ_ONLY_ALLOWED: ReadonlySet<string>;
|
|
21
|
+
/**
|
|
22
|
+
* 会执行**任意页面脚本**的扩展命令。这条与只读档分开管,因为用户可能想「可以读、
|
|
23
|
+
* 也可以点,但别在我页面里跑任意 JS」——`browser_evaluate` 能绕开其他所有工具做的事。
|
|
24
|
+
*/
|
|
25
|
+
export declare const SCRIPT_COMMANDS: ReadonlySet<string>;
|
|
26
|
+
/**
|
|
27
|
+
* 不经过扩展、直接在插件进程里改动本机文件的命令名。
|
|
28
|
+
*
|
|
29
|
+
* `browser_cleanup` 走 `BridgeController.cleanup()`,**不经过 `execute()`**,所以它天然
|
|
30
|
+
* 绕开了上面两张表管的那条路。只读档下仍然能删掉用户的整个截图目录,这正是「只读」
|
|
31
|
+
* 最不该允许的动作,所以单独列一张表,并且由 `cleanup()` 自己调闸门。
|
|
32
|
+
*/
|
|
33
|
+
export declare const LOCAL_DESTRUCTIVE_COMMANDS: ReadonlySet<string>;
|
|
34
|
+
/**
|
|
35
|
+
* 明确登记「会改页面 DOM」的命令 —— 只读档里必须拒,理由写在名字旁边。
|
|
36
|
+
*
|
|
37
|
+
* 现在只有 `hold`(接管浮层):它往页面 `<html>` 下插一个宿主元素。默认拒绝其实已经
|
|
38
|
+
* 盖住它(不在 `READ_ONLY_ALLOWED` 里),但「默认拒绝是对的」不等于「不需要解释」:
|
|
39
|
+
* 这一条要的不是安全性,是**拒绝理由**——告诉模型为什么一个「只是提示用户」的动作
|
|
40
|
+
* 在只读档里也算写操作。
|
|
41
|
+
*
|
|
42
|
+
* `hold` 是一条命令带 `action`(on/off/status):on/off 改 DOM,**status 只读**
|
|
43
|
+
* (它读 session 里的条目 + 探一下页面上还有没有那层宿主),所以只读档放行 status。
|
|
44
|
+
* 这也是 `policyRefusal` 唯一需要看参数的地方——判据是「这个动作改不改东西」,
|
|
45
|
+
* 不是「这个命令名字在不在表里」。
|
|
46
|
+
*/
|
|
47
|
+
export declare const PAGE_MUTATING_COMMANDS: ReadonlySet<string>;
|
|
48
|
+
/** 两个闸门的当前值。取自插件配置,热更新即生效(改完重启 dsh 也一样)。 */
|
|
49
|
+
export interface PolicyFlags {
|
|
50
|
+
/** 只读档:true 时拒绝一切会改变页面/浏览器状态的命令。 */
|
|
51
|
+
readOnly: boolean;
|
|
52
|
+
/** 允许在页面里执行任意脚本(`browser_evaluate`)。 */
|
|
53
|
+
allowScript: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 判断一条命令是否被本机策略拦下。
|
|
57
|
+
* @param command - 扩展命令名(`click` / `eval` / `tabs.close` …),或 `LOCAL_DESTRUCTIVE_COMMANDS` 里的插件本地命令名。
|
|
58
|
+
* @param flags - 当前的两个闸门值。
|
|
59
|
+
* @param params - 这条命令的参数;只有带动作的命令(目前是 `hold`)需要它来分辨「这个动作改不改东西」。
|
|
60
|
+
* @returns 拒绝时写给 agent 的整句理由;放行时 `undefined`。
|
|
61
|
+
*/
|
|
62
|
+
export declare function policyRefusal(command: string, flags: PolicyFlags, params?: Record<string, unknown>): string | undefined;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本机策略闸门:只读档(`readOnly`)与脚本开关(`allowScript`)。
|
|
3
|
+
*
|
|
4
|
+
* 这两张表与 `errors.ts` 里的 `WRITE_COMMANDS` **故意不是同一张**,别合并:
|
|
5
|
+
*
|
|
6
|
+
* - `WRITE_COMMANDS` 回答「超时或掉线之后重试,会不会多留一次副作用」,那是
|
|
7
|
+
* `effectState` 的语义。所以 `tabs.group` / `tabs.ungroup` 在那里**刻意算只读**:
|
|
8
|
+
* 重复把标签归进同一个组是空操作,重试安全。
|
|
9
|
+
* - 这里回答的是「这条命令会不会改变页面、文件或浏览器状态」,用户开只读档时想要的
|
|
10
|
+
* 正是这个。所以 `tabs.group` / `tabs.ungroup` / `tabs.activate` / `emulate` /
|
|
11
|
+
* `dialog` / `network.clear` 都不在白名单里。
|
|
12
|
+
*
|
|
13
|
+
* 白名单是**默认拒绝**:扩展以后新增一条命令,它自动被只读档拦下,等人判断完再放进来。
|
|
14
|
+
* `verify/verify_policy.mjs` 会把扩展 `COMMANDS` 表逐条对账,让这个判断无法被跳过。
|
|
15
|
+
*
|
|
16
|
+
* 第三张表 `LOCAL_DESTRUCTIVE_COMMANDS` 是给**不经过扩展**的插件本地命令的:闸门如果只长在
|
|
17
|
+
* `execute()` 上,这类调用就整个绕开只读档(`browser_cleanup` 合并进来时正是这样)。
|
|
18
|
+
*/
|
|
19
|
+
/** 只读档允许的命令:读页面、读扩展自己的缓冲、或者把页面**内容**落到本机文件。 */
|
|
20
|
+
export const READ_ONLY_ALLOWED = new Set([
|
|
21
|
+
// 身份与链路探测
|
|
22
|
+
'ping', 'browser.info',
|
|
23
|
+
// 读页面
|
|
24
|
+
'content', 'find', 'snapshot', 'screenshot', 'pdf', 'wait',
|
|
25
|
+
// 读扩展采集到的缓冲
|
|
26
|
+
'tabs.list', 'console.log', 'network.log',
|
|
27
|
+
// 读浏览器自己的状态(cookie 是凭据,但 get 不改任何东西;set/remove 不在此列)
|
|
28
|
+
'cookies.get',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* 会执行**任意页面脚本**的扩展命令。这条与只读档分开管,因为用户可能想「可以读、
|
|
32
|
+
* 也可以点,但别在我页面里跑任意 JS」——`browser_evaluate` 能绕开其他所有工具做的事。
|
|
33
|
+
*/
|
|
34
|
+
export const SCRIPT_COMMANDS = new Set(['eval']);
|
|
35
|
+
/**
|
|
36
|
+
* 不经过扩展、直接在插件进程里改动本机文件的命令名。
|
|
37
|
+
*
|
|
38
|
+
* `browser_cleanup` 走 `BridgeController.cleanup()`,**不经过 `execute()`**,所以它天然
|
|
39
|
+
* 绕开了上面两张表管的那条路。只读档下仍然能删掉用户的整个截图目录,这正是「只读」
|
|
40
|
+
* 最不该允许的动作,所以单独列一张表,并且由 `cleanup()` 自己调闸门。
|
|
41
|
+
*/
|
|
42
|
+
export const LOCAL_DESTRUCTIVE_COMMANDS = new Set(['cleanup']);
|
|
43
|
+
/**
|
|
44
|
+
* 明确登记「会改页面 DOM」的命令 —— 只读档里必须拒,理由写在名字旁边。
|
|
45
|
+
*
|
|
46
|
+
* 现在只有 `hold`(接管浮层):它往页面 `<html>` 下插一个宿主元素。默认拒绝其实已经
|
|
47
|
+
* 盖住它(不在 `READ_ONLY_ALLOWED` 里),但「默认拒绝是对的」不等于「不需要解释」:
|
|
48
|
+
* 这一条要的不是安全性,是**拒绝理由**——告诉模型为什么一个「只是提示用户」的动作
|
|
49
|
+
* 在只读档里也算写操作。
|
|
50
|
+
*
|
|
51
|
+
* `hold` 是一条命令带 `action`(on/off/status):on/off 改 DOM,**status 只读**
|
|
52
|
+
* (它读 session 里的条目 + 探一下页面上还有没有那层宿主),所以只读档放行 status。
|
|
53
|
+
* 这也是 `policyRefusal` 唯一需要看参数的地方——判据是「这个动作改不改东西」,
|
|
54
|
+
* 不是「这个命令名字在不在表里」。
|
|
55
|
+
*/
|
|
56
|
+
export const PAGE_MUTATING_COMMANDS = new Set(['hold']);
|
|
57
|
+
const CONFIG_HINT = '开关在 profile 的 cordis.patch.yml 里(browser-bridge 的 config),改完重启 dsh;'
|
|
58
|
+
+ 'dsh 0.1.7+ 的宿主也会在「设置 → 插件」里直接列出这两项';
|
|
59
|
+
/**
|
|
60
|
+
* 判断一条命令是否被本机策略拦下。
|
|
61
|
+
* @param command - 扩展命令名(`click` / `eval` / `tabs.close` …),或 `LOCAL_DESTRUCTIVE_COMMANDS` 里的插件本地命令名。
|
|
62
|
+
* @param flags - 当前的两个闸门值。
|
|
63
|
+
* @param params - 这条命令的参数;只有带动作的命令(目前是 `hold`)需要它来分辨「这个动作改不改东西」。
|
|
64
|
+
* @returns 拒绝时写给 agent 的整句理由;放行时 `undefined`。
|
|
65
|
+
*/
|
|
66
|
+
export function policyRefusal(command, flags, params) {
|
|
67
|
+
if (!flags.allowScript && SCRIPT_COMMANDS.has(command)) {
|
|
68
|
+
return `在页面里执行任意脚本已被关闭(allowScript: false):命令 ${command} 被拒绝,没有向浏览器发出任何操作。`
|
|
69
|
+
+ `要读页面内容请改用 browser_read / browser_snapshot / browser_find;确实需要执行脚本时,${CONFIG_HINT}之后再说。`;
|
|
70
|
+
}
|
|
71
|
+
if (flags.readOnly && LOCAL_DESTRUCTIVE_COMMANDS.has(command)) {
|
|
72
|
+
return `只读模式已开启(readOnly: true):命令 ${command} 会删除本机保存的截图与临时文件,删除不可逆,所以已被拒绝,没有任何文件被删除。`
|
|
73
|
+
+ `只读档的含义是「什么都不要改」,清理属于改动;要释放磁盘请由用户自己关掉 readOnly,${CONFIG_HINT}。不要原地重试本命令。`;
|
|
74
|
+
}
|
|
75
|
+
// `hold` 带动作:on/off 改页面 DOM,status 只读(读 session 条目 + 探页面上那层还在不在),
|
|
76
|
+
// 所以只读档放行 status——只读档不该连「看一眼浮层现在是什么状态」都不让。
|
|
77
|
+
const isReadOnlyAction = command === 'hold' && params?.action === 'status';
|
|
78
|
+
if (flags.readOnly && PAGE_MUTATING_COMMANDS.has(command) && !isReadOnlyAction) {
|
|
79
|
+
return `只读模式已开启(readOnly: true):命令 ${command} 会往页面里插入一层接管浮层(改页面 DOM),已被拒绝,没有向浏览器发出任何操作。`
|
|
80
|
+
+ `浮层是给用户看的提示层,不是安全边界;只读档里连这种「只是提示」的改动也一并拒绝。要开浮层请由用户关掉 readOnly,${CONFIG_HINT}不要原地重试本命令。`;
|
|
81
|
+
}
|
|
82
|
+
if (flags.readOnly && !READ_ONLY_ALLOWED.has(command) && !isReadOnlyAction) {
|
|
83
|
+
return `只读模式已开启(readOnly: true):命令 ${command} 会改变页面或浏览器状态,已被拒绝,没有向浏览器发出任何操作。`
|
|
84
|
+
+ `只读模式下可用的工具是读页面与读日志那几类;要放开写操作,${CONFIG_HINT}不要原地重试本命令。`;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local WebSocket+HTTP bridge between one browser extension and dsh. The
|
|
3
|
+
* extension connects OUT to `ws://127.0.0.1:<port>/ws?token=...`, so no
|
|
4
|
+
* native-messaging host or registry setup exists; dsh drives commands through
|
|
5
|
+
* {@link BridgeServer.execute} and reads link state through {@link BridgeServer.status}.
|
|
6
|
+
* A small HTTP face (`/api/status`, `/api/command`, `/api/cleanup`, `/`) keeps
|
|
7
|
+
* the bridge usable from curl while the server runs. `/api/command` and
|
|
8
|
+
* `/api/cleanup` demand the same token as the WebSocket upgrade; `/` and
|
|
9
|
+
* `/api/status` stay readable because the status page needs them and they only
|
|
10
|
+
* report link state. No response carries `Access-Control-Allow-Origin`, and the
|
|
11
|
+
* upgrade checks `Host` plus `Origin` so neither a web page nor a rebound
|
|
12
|
+
* hostname can attach.
|
|
13
|
+
*
|
|
14
|
+
* Wire protocol (text frames, one JSON object each):
|
|
15
|
+
* - extension → server: `{type:'hello',…}`, `{type:'pong',t}`, `{type:'result',id,ok,result?|error}`
|
|
16
|
+
* - server → extension: `{type:'ping',t}`, `{type:'command',id,command,params}`
|
|
17
|
+
*
|
|
18
|
+
* Command names (sent in `command`) include: `ping`, `browser.info`, `tabs.list/open/close/activate`,
|
|
19
|
+
* `nav`, `eval`, `content`, `find`, `click`, `input`, `press`, `scroll`, `snapshot`, `screenshot`,
|
|
20
|
+
* `wait`, `dialog`, `console.log`, `network.log`, `network.clear`, `pdf`, `emulate`.
|
|
21
|
+
* @module @deepseek-ai/dsh-browser-bridge/server
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Wire protocol revision this bridge speaks. Kept separate from the package
|
|
25
|
+
* version: an extension that is one patch newer still works, an extension that
|
|
26
|
+
* speaks a different frame contract does not, and only the second case should
|
|
27
|
+
* light up.
|
|
28
|
+
*/
|
|
29
|
+
export declare const BRIDGE_PROTOCOL_VERSION = 1;
|
|
30
|
+
/** Per-command ceiling when the caller passes none. */
|
|
31
|
+
export declare const DEFAULT_COMMAND_TIMEOUT_MS = 60000;
|
|
32
|
+
/** Hard ceiling accepted from callers; longer waits cannot be expressed. */
|
|
33
|
+
export declare const MAX_COMMAND_TIMEOUT_MS = 300000;
|
|
34
|
+
/** Identity the extension announces right after the handshake completes. */
|
|
35
|
+
export interface BridgeHello {
|
|
36
|
+
readonly client: string;
|
|
37
|
+
readonly version: string;
|
|
38
|
+
/** Frame-contract revision the extension declared; 0 when it predates versioning. */
|
|
39
|
+
readonly protocol: number;
|
|
40
|
+
readonly browser: {
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly version: string;
|
|
43
|
+
readonly ua: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Snapshot of the link and in-flight work, served verbatim on `/api/status`. */
|
|
47
|
+
export interface BridgeServerStatus {
|
|
48
|
+
readonly listening: boolean;
|
|
49
|
+
readonly port: number | undefined;
|
|
50
|
+
readonly extensionConnected: boolean;
|
|
51
|
+
readonly connectedAt: string | undefined;
|
|
52
|
+
readonly hello: BridgeHello | undefined;
|
|
53
|
+
/** Protocol revision this bridge speaks. */
|
|
54
|
+
readonly bridgeProtocol: number;
|
|
55
|
+
/** Protocol revision the extension declared; absent/0 when it predates versioning. */
|
|
56
|
+
readonly extensionProtocol: number;
|
|
57
|
+
/** False when the two sides cannot agree on the frame contract, with the reason. */
|
|
58
|
+
readonly protocolCompatible: boolean;
|
|
59
|
+
readonly protocolNote: string | null;
|
|
60
|
+
readonly pendingCommands: number;
|
|
61
|
+
readonly lastError: string | undefined;
|
|
62
|
+
/** True when the active token is the published default or too short to be a secret. */
|
|
63
|
+
readonly insecureToken: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Caller controls for one {@link BridgeServer.execute} round-trip. */
|
|
66
|
+
export interface BridgeExecuteOptions {
|
|
67
|
+
/** Fail the command after this many milliseconds; defaults to 60s, capped at 300s. */
|
|
68
|
+
readonly timeoutMs?: number;
|
|
69
|
+
/** Cancellation from the tool execution; aborting fails the in-flight command. */
|
|
70
|
+
readonly signal?: AbortSignal;
|
|
71
|
+
}
|
|
72
|
+
/** What {@link BridgeServer.cleanup} removed. */
|
|
73
|
+
export interface CleanupResult {
|
|
74
|
+
/** Files deleted directly inside the screenshots directory. */
|
|
75
|
+
readonly shotsRemoved: number;
|
|
76
|
+
/** Scratch files deleted (agent temp scripts/artifacts matched by convention). */
|
|
77
|
+
readonly scratchRemoved: readonly string[];
|
|
78
|
+
}
|
|
79
|
+
export interface BridgeServerOptions {
|
|
80
|
+
readonly port: number;
|
|
81
|
+
readonly token: string;
|
|
82
|
+
/** Directory whose direct contents `cleanup()` clears; created on demand. */
|
|
83
|
+
readonly shotsDir: string;
|
|
84
|
+
/** Directory scanned for agent scratch files by `cleanup()`; defaults to the process working directory. */
|
|
85
|
+
readonly scratchDir?: string;
|
|
86
|
+
/** Optional line logger for lifecycle diagnostics. */
|
|
87
|
+
readonly log?: (line: string) => void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clear the screenshots directory and delete agent scratch files. Only the
|
|
91
|
+
* top level of each location is touched; nested trees stay untouched.
|
|
92
|
+
* @param options - `shotsDir` is cleared of direct file children; `scratchDir`
|
|
93
|
+
* defaults to the process working directory and loses only scratch-named files.
|
|
94
|
+
* @returns counts and names of what was removed.
|
|
95
|
+
*/
|
|
96
|
+
export declare function cleanupArtifacts(options: {
|
|
97
|
+
shotsDir: string;
|
|
98
|
+
scratchDir?: string;
|
|
99
|
+
}): Promise<CleanupResult>;
|
|
100
|
+
/**
|
|
101
|
+
* One live bridge endpoint. Start/stop may cycle repeatedly on one instance;
|
|
102
|
+
* a fresh listener is built per start so a changed config reuses the class
|
|
103
|
+
* without re-allocation concerns. Exactly one extension link is held at a
|
|
104
|
+
* time; a newer WebSocket replaces the older one.
|
|
105
|
+
*/
|
|
106
|
+
export declare class BridgeServer {
|
|
107
|
+
private readonly options;
|
|
108
|
+
private httpServer;
|
|
109
|
+
private clientSocket;
|
|
110
|
+
private hello;
|
|
111
|
+
private connectedAt;
|
|
112
|
+
private readonly pending;
|
|
113
|
+
private lastError;
|
|
114
|
+
private continuationRemainder;
|
|
115
|
+
private continuationOpen;
|
|
116
|
+
constructor(options: BridgeServerOptions);
|
|
117
|
+
/** Current link state, also served verbatim as `/api/status`. */
|
|
118
|
+
get status(): BridgeServerStatus;
|
|
119
|
+
private get insecureToken();
|
|
120
|
+
/** Bind `127.0.0.1:<port>` and start accepting the extension plus HTTP calls. */
|
|
121
|
+
start(): Promise<void>;
|
|
122
|
+
/** Close the extension link, fail every pending command, and release the port. Idempotent. */
|
|
123
|
+
stop(): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Send one command to the connected extension and await its result.
|
|
126
|
+
* Throws while no extension is linked; the rejection message names the fix.
|
|
127
|
+
*/
|
|
128
|
+
execute(command: string, params: Record<string, unknown>, options?: BridgeExecuteOptions): Promise<unknown>;
|
|
129
|
+
/**
|
|
130
|
+
* Delete generated screenshots and agent scratch files via
|
|
131
|
+
* {@link cleanupArtifacts} using this server's configured directories.
|
|
132
|
+
*/
|
|
133
|
+
cleanup(): Promise<CleanupResult>;
|
|
134
|
+
private log;
|
|
135
|
+
/**
|
|
136
|
+
* Mismatch description, or null when the two sides can talk. An older
|
|
137
|
+
* extension that declares nothing is treated as v1-compatible: it still
|
|
138
|
+
* answers every command this bridge has ever sent, so failing the link would
|
|
139
|
+
* break working installs for a cosmetic gap.
|
|
140
|
+
*/
|
|
141
|
+
private get protocolNote();
|
|
142
|
+
/**
|
|
143
|
+
* Whether a request carries the bridge token, the same credential the WebSocket
|
|
144
|
+
* upgrade checks. Accepted forms: `Authorization: Bearer <token>`, `X-Token: <token>`
|
|
145
|
+
* and `?token=<token>` (the query form exists for curl and for the status page).
|
|
146
|
+
*/
|
|
147
|
+
private authorized;
|
|
148
|
+
private rejectUnauthenticated;
|
|
149
|
+
private handleHttp;
|
|
150
|
+
private respondJson;
|
|
151
|
+
private handleCommandRequest;
|
|
152
|
+
private handleUpgrade;
|
|
153
|
+
private handleFrame;
|
|
154
|
+
private handleText;
|
|
155
|
+
private closeClientSocket;
|
|
156
|
+
private failAllPending;
|
|
157
|
+
private logVerbose;
|
|
158
|
+
}
|