@alfe.ai/openclaw-remote 0.0.15 → 0.0.16
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 +52 -0
- package/dist/index.cjs +11 -7
- package/dist/index.d.cts +376 -21
- package/dist/index.d.ts +376 -21
- package/dist/index.js +2 -2
- package/dist/plugin.cjs +4 -8
- package/dist/plugin.d.cts +5 -84
- package/dist/plugin.d.ts +5 -84
- package/dist/plugin.js +5 -2
- package/dist/runtime.cjs +842 -0
- package/dist/runtime.js +789 -0
- package/dist/types.d.cts +66 -0
- package/dist/types.d.ts +66 -0
- package/openclaw.plugin.json +9 -4
- package/package.json +9 -8
- package/dist/plugin2.cjs +0 -485
- package/dist/plugin2.d.cts +0 -2
- package/dist/plugin2.d.ts +0 -2
- package/dist/plugin2.js +0 -462
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `@alfe.ai/openclaw-remote`
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin for Alfe's interactive remote-control relay. One outbound
|
|
4
|
+
WebSocket carries browser co-browse and web-terminal sessions; the package also
|
|
5
|
+
registers bounded browser automation and human-takeover tools.
|
|
6
|
+
|
|
7
|
+
OpenClaw loads the default-only `./plugin` entry. The package root exposes named
|
|
8
|
+
runtime factories and types for tests and other library consumers.
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
|
|
12
|
+
The plugin reads its optional entry from
|
|
13
|
+
`plugins.entries["@alfe.ai/openclaw-remote"].config`:
|
|
14
|
+
|
|
15
|
+
| Key | Default | Boundary |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `remoteWsUrl` | Derived from the agent API URL | Credential-free `wss:`; `ws:` only on loopback |
|
|
18
|
+
| `browserExecutablePath` | `/usr/bin/google-chrome-stable` | Absolute path, at most 4096 characters |
|
|
19
|
+
| `browserHeadless` | `true` | Boolean |
|
|
20
|
+
| `browserNoSandbox` | `true` | Boolean |
|
|
21
|
+
| `handoffTimeoutMs` | 600000 | Integer from 1000 through 1800000 |
|
|
22
|
+
|
|
23
|
+
The shared `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` escape hatch is
|
|
24
|
+
off by default. With the default policy, BrowserSession request interception
|
|
25
|
+
blocks credential-bearing URLs and private, loopback, link-local, multicast,
|
|
26
|
+
benchmark, documentation, and reserved literal addresses. DNS rebinding still
|
|
27
|
+
requires a resolving policy upstream.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
- `browser_navigate`
|
|
32
|
+
- `browser_click`
|
|
33
|
+
- `browser_type`
|
|
34
|
+
- `browser_wait_for`
|
|
35
|
+
- `browser_screenshot`
|
|
36
|
+
- `browser_evaluate`
|
|
37
|
+
- `request_browser_takeover`
|
|
38
|
+
|
|
39
|
+
Validation failures deliberately safe for the model are returned as structured
|
|
40
|
+
tool errors. Unexpected browser, relay, and API diagnostics are redacted.
|
|
41
|
+
Screenshots use OpenClaw image content and keep base64 out of JSON details.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm --filter @alfe.ai/openclaw-remote lint
|
|
47
|
+
pnpm --filter @alfe.ai/openclaw-remote typecheck
|
|
48
|
+
pnpm --filter @alfe.ai/openclaw-remote test
|
|
49
|
+
pnpm --filter @alfe.ai/openclaw-remote build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See [DEVELOPING.md](./DEVELOPING.md) for lifecycle and security invariants.
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
Object.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.buildIsNavigationAllowed =
|
|
7
|
-
exports.
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("./runtime.cjs");
|
|
3
|
+
exports.PLUGIN_VERSION = require_runtime.PLUGIN_VERSION;
|
|
4
|
+
exports.REMOTE_ACTIVATION_KEY = require_runtime.REMOTE_ACTIVATION_KEY;
|
|
5
|
+
exports.buildControlUrl = require_runtime.buildControlUrl;
|
|
6
|
+
exports.buildIsNavigationAllowed = require_runtime.buildIsNavigationAllowed;
|
|
7
|
+
exports.createRemotePlugin = require_runtime.createRemotePlugin;
|
|
8
|
+
exports.createRemotePluginRuntimeState = require_runtime.createRemotePluginRuntimeState;
|
|
9
|
+
exports.deriveDashboardBaseUrl = require_runtime.deriveDashboardBaseUrl;
|
|
10
|
+
exports.deriveRemoteWsUrl = require_runtime.deriveRemoteWsUrl;
|
|
11
|
+
exports.parseRemotePluginConfig = require_runtime.parseRemotePluginConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,28 +1,384 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as RemotePluginConfig, t as OpenClawPluginApi } from "./types.cjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../agent-api-client/dist/index.d.ts
|
|
4
|
+
|
|
5
|
+
//#region src/tool-error-capture.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Tool-error capture for Alfe OpenClaw plugins.
|
|
8
|
+
*
|
|
9
|
+
* OpenClaw converts a thrown tool handler into a model-facing `tool_result`
|
|
10
|
+
* WITHOUT logging, and most Alfe plugins catch-and-return an error result the
|
|
11
|
+
* same silent way — so tool failures never appear in the runtime's output and
|
|
12
|
+
* therefore never reach Sentry (the gateway daemon supervises the OpenClaw
|
|
13
|
+
* process and reports error-looking output lines to the `agent-runtime`
|
|
14
|
+
* project — see packages/gateway/src/runtime-output-monitor.ts).
|
|
15
|
+
*
|
|
16
|
+
* `installToolErrorCapture(api, { plugin })` closes that gap at the ONE choke
|
|
17
|
+
* point every plugin already has: it wraps `api.registerTool` so every tool's
|
|
18
|
+
* `execute` emits a deterministic, detector-matched line on failure:
|
|
19
|
+
*
|
|
20
|
+
* [ERROR] alfe-tool plugin=<plugin> tool=<name> <thrown|result-error>: <msg> (at <first-frame>)
|
|
21
|
+
*
|
|
22
|
+
* The `[ERROR]` prefix at line start is exactly what the daemon's
|
|
23
|
+
* `ErrorLineDetector` classifies as an error-log block, so the failure lands
|
|
24
|
+
* in Sentry fingerprinted by its normalized message — no Sentry SDK inside
|
|
25
|
+
* the plugin process, no new dependency. Behavior toward OpenClaw and the
|
|
26
|
+
* model is UNCHANGED: throws are rethrown, results returned as-is.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Minimal shape of the OpenClaw plugin api this helper relies on. Method
|
|
30
|
+
* syntax on purpose — TS checks method signatures bivariantly, so each
|
|
31
|
+
* plugin's own concretely-typed `registerTool(tool: ToolDef): void` is
|
|
32
|
+
* accepted without casts.
|
|
33
|
+
*/
|
|
34
|
+
interface ToolCaptureApi {
|
|
35
|
+
registerTool(...args: never[]): unknown;
|
|
36
|
+
}
|
|
37
|
+
interface InstallToolErrorCaptureOptions {
|
|
38
|
+
/** Plugin package short-name for attribution (e.g. "openclaw-secrets"). */
|
|
39
|
+
plugin: string;
|
|
40
|
+
/**
|
|
41
|
+
* Line sink — defaults to writing `process.stderr` directly (the plugin
|
|
42
|
+
* runs in-process in OpenClaw, so this lands on the runtime's stderr, which
|
|
43
|
+
* the daemon supervises — and a console patch can't reformat it away).
|
|
44
|
+
* Injectable for tests.
|
|
45
|
+
*/
|
|
46
|
+
emit?: (line: string) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wrap `api.registerTool` so every tool registered AFTER this call gets
|
|
50
|
+
* failure capture. Handles both OpenClaw registration signatures:
|
|
51
|
+
* `registerTool(toolDef)` and `registerTool((ctx) => toolDef, opts)`.
|
|
52
|
+
* Call once, first thing in the plugin's `activate`/`register` entry.
|
|
53
|
+
* Never throws.
|
|
54
|
+
*/
|
|
55
|
+
declare function installToolErrorCapture(api: ToolCaptureApi, options: InstallToolErrorCaptureOptions): void;
|
|
56
|
+
//# sourceMappingURL=tool-error-capture.d.ts.map
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/transport.d.ts
|
|
59
|
+
/**
|
|
60
|
+
* Shared HTTP transport for the Agent API client — request core, retry
|
|
61
|
+
* policy, error formatting, and the `ApiBase` class the domain method
|
|
62
|
+
* groups under `./domains/` build on.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region ../browser/dist/index.d.ts
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/types.d.ts
|
|
70
|
+
interface Logger$2 {
|
|
71
|
+
info(msg: string, ...args: unknown[]): void;
|
|
72
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
73
|
+
error(msg: string, ...args: unknown[]): void;
|
|
74
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/turn-controller.d.ts
|
|
78
|
+
/**
|
|
79
|
+
* TurnController — the write-mutex between agent automation and a human during
|
|
80
|
+
* a browser co-browse handoff. The plugin owns the single CDP session, so this
|
|
81
|
+
* lives plugin-side; the relay only forwards the control frames.
|
|
82
|
+
*
|
|
83
|
+
* Only *writes* are gated — screencast reads always flow so the human sees the
|
|
84
|
+
* live page regardless of who holds the token. Agent automation ops call
|
|
85
|
+
* `acquireAgent()` and park while the human is in control; the human's input is
|
|
86
|
+
* injected only while `owner === 'human'`.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/types.d.ts
|
|
92
|
+
interface BrowserSessionOptions {
|
|
93
|
+
/** Path to the Chrome/Chromium binary (from the headless-browser integration). */
|
|
94
|
+
executablePath: string;
|
|
95
|
+
/** Persistent profile dir so cookies/login survive process restarts. */
|
|
96
|
+
userDataDir: string;
|
|
97
|
+
/** Headless mode (default true). */
|
|
98
|
+
headless?: boolean;
|
|
99
|
+
/** Pass --no-sandbox (needed on most managed Linux VMs). */
|
|
100
|
+
noSandbox?: boolean;
|
|
101
|
+
/** Shut Chrome down after this many ms with no holds/activity (default 5 min). */
|
|
102
|
+
idleShutdownMs?: number;
|
|
103
|
+
/** Extra Chrome args. */
|
|
104
|
+
extraArgs?: string[];
|
|
105
|
+
/** Guard applied to every intercepted HTTP(S) page request. */
|
|
106
|
+
isNavigationAllowed?: (url: string) => boolean;
|
|
107
|
+
logger?: Logger$2;
|
|
108
|
+
}
|
|
109
|
+
type BrowserSurfaceOptions = BrowserSessionOptions;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/browser-session.d.ts
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/browser-surface.d.ts
|
|
115
|
+
interface HandoffResult {
|
|
116
|
+
released: boolean;
|
|
117
|
+
timedOut: boolean;
|
|
118
|
+
url: string;
|
|
119
|
+
title: string;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region ../config/dist/index.d.ts
|
|
4
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Resolved runtime config — env vars override config file values.
|
|
126
|
+
*
|
|
127
|
+
* Hierarchy (highest priority first):
|
|
128
|
+
* 1. Environment variables (ALFE_API_KEY, ALFE_API_URL)
|
|
129
|
+
* 2. Config file (~/.alfe/config.toml)
|
|
130
|
+
*
|
|
131
|
+
* Consumers call resolveConfig() and get the resolved values —
|
|
132
|
+
* they don't need to know where the values came from.
|
|
133
|
+
*/
|
|
134
|
+
interface ResolvedConfig {
|
|
135
|
+
apiKey: string;
|
|
136
|
+
apiUrl: string;
|
|
137
|
+
/**
|
|
138
|
+
* Base URL for the voice service's agent-authed one-shot endpoints
|
|
139
|
+
* (`/voice/tts`, `/voice/stt`). Derived from the token via
|
|
140
|
+
* {@link getVoiceServiceUrlFromToken}; currently equals `apiUrl` (the
|
|
141
|
+
* endpoints are co-located on the shared api gateway), but kept as its own
|
|
142
|
+
* field so the voice plugin has a stable seam if voice ever moves hosts.
|
|
143
|
+
*/
|
|
144
|
+
voiceServiceUrl: string;
|
|
145
|
+
socketPath: string;
|
|
146
|
+
workspacePath: string;
|
|
147
|
+
/**
|
|
148
|
+
* The active agent runtime — `"openclaw"` (default) or `"hermes"`. Read from
|
|
149
|
+
* the top-level `runtime` key of the config. Consumers that behave
|
|
150
|
+
* differently per runtime (e.g. AlfeSync's per-runtime default ignore set)
|
|
151
|
+
* read this rather than re-parsing the TOML.
|
|
152
|
+
*/
|
|
153
|
+
runtime: string;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ../remote/dist/index.d.ts
|
|
157
|
+
|
|
158
|
+
declare const RemoteFrameType: {
|
|
159
|
+
/** viewer→plugin: a viewer attached. Payload: SessionOpenPayload. */
|
|
160
|
+
readonly SESSION_OPEN: 1;
|
|
161
|
+
/** either direction: a viewer detached / session torn down. */
|
|
162
|
+
readonly SESSION_CLOSE: 2;
|
|
163
|
+
/** plugin→viewer: current surface state. Payload: SessionStatePayload. */
|
|
164
|
+
readonly SESSION_STATE: 3;
|
|
165
|
+
/** plugin→viewer (browser): the page navigated. Payload: { url }. */
|
|
166
|
+
readonly NAVIGATION: 4;
|
|
167
|
+
/** plugin→viewer: [metaLen:u16BE][meta JSON][raw JPEG]. */
|
|
168
|
+
readonly SCREENCAST_FRAME: 16;
|
|
169
|
+
/** viewer→plugin: { frameSeq } — drives ack-gated backpressure. */
|
|
170
|
+
readonly SCREENCAST_ACK: 17;
|
|
171
|
+
readonly INPUT_MOUSE: 32;
|
|
172
|
+
readonly INPUT_WHEEL: 33;
|
|
173
|
+
readonly INPUT_KEY: 34;
|
|
174
|
+
/** viewer→plugin: { width, height, dpr }. */
|
|
175
|
+
readonly RESIZE: 35;
|
|
176
|
+
readonly TAKEOVER_REQUEST: 48;
|
|
177
|
+
readonly TAKEOVER_GRANTED: 49;
|
|
178
|
+
readonly TAKEOVER_DENIED: 50;
|
|
179
|
+
readonly RELEASE_CONTROL: 51;
|
|
180
|
+
readonly CONTROL_REVOKED: 52;
|
|
181
|
+
/** plugin→viewer: raw PTY output bytes. */
|
|
182
|
+
readonly TERMINAL_DATA: 64;
|
|
183
|
+
/** viewer→plugin: raw keystroke bytes. */
|
|
184
|
+
readonly TERMINAL_INPUT: 65;
|
|
185
|
+
/** viewer→plugin: { cols, rows }. */
|
|
186
|
+
readonly TERMINAL_RESIZE: 66;
|
|
187
|
+
};
|
|
188
|
+
type RemoteFrameType = (typeof RemoteFrameType)[keyof typeof RemoteFrameType];
|
|
189
|
+
type RemoteSurface = "browser" | "terminal";
|
|
190
|
+
interface RemoteFrame {
|
|
191
|
+
type: RemoteFrameType;
|
|
192
|
+
sessionId: number;
|
|
193
|
+
payload: Buffer;
|
|
194
|
+
}
|
|
195
|
+
interface SessionOpenPayload {
|
|
196
|
+
surface: RemoteSurface;
|
|
197
|
+
/** Browser: initial viewport in device pixels. */
|
|
198
|
+
width?: number;
|
|
199
|
+
height?: number;
|
|
200
|
+
dpr?: number;
|
|
201
|
+
/** Terminal: initial PTY dimensions. */
|
|
202
|
+
cols?: number;
|
|
203
|
+
rows?: number;
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/types.d.ts
|
|
207
|
+
interface Logger$1 {
|
|
208
|
+
info(msg: string, ...args: unknown[]): void;
|
|
209
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
210
|
+
error(msg: string, ...args: unknown[]): void;
|
|
211
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
212
|
+
}
|
|
213
|
+
interface RemoteServiceClientOptions {
|
|
214
|
+
/** Relay WebSocket URL (e.g. wss://remote.dev.alfe.ai/ws). */
|
|
215
|
+
wsUrl: string;
|
|
216
|
+
/** Agent API key for Bearer auth on the WS upgrade. */
|
|
217
|
+
apiKey: string;
|
|
218
|
+
/**
|
|
219
|
+
* Handler for every well-formed inbound frame from the relay. The plugin
|
|
220
|
+
* dispatches by `frame.sessionId` to the owning surface handler. Kept on the
|
|
221
|
+
* transport as a single callback so this package stays surface-agnostic.
|
|
222
|
+
*/
|
|
223
|
+
onFrame: (frame: RemoteFrame) => void | Promise<void>;
|
|
224
|
+
/** Called when the WS connection state changes. */
|
|
225
|
+
onConnectionChange?: (connected: boolean) => void | Promise<void>;
|
|
226
|
+
/** Optional logger (defaults to console). */
|
|
227
|
+
logger?: Logger$1;
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/client.d.ts
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region ../terminal/dist/index.d.ts
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/types.d.ts
|
|
236
|
+
interface Logger {
|
|
237
|
+
info(msg: string, ...args: unknown[]): void;
|
|
238
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
239
|
+
error(msg: string, ...args: unknown[]): void;
|
|
240
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/types.d.ts
|
|
244
|
+
interface TerminalSurfaceOptions {
|
|
245
|
+
/** Shell to spawn. Defaults to $SHELL or /bin/bash. */
|
|
246
|
+
shell?: string;
|
|
247
|
+
/** Working directory for the shell (the agent workspace). Defaults to cwd. */
|
|
248
|
+
cwd?: string;
|
|
249
|
+
/** Extra env for the shell. Merged over process.env. */
|
|
250
|
+
env?: Record<string, string>;
|
|
251
|
+
/** Max concurrent terminal sessions (default 8). */
|
|
252
|
+
maxSessions?: number;
|
|
253
|
+
logger?: Logger;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/terminal-surface.d.ts
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/runtime.d.ts
|
|
259
|
+
declare const REMOTE_ACTIVATION_KEY: string;
|
|
260
|
+
declare const PLUGIN_VERSION: string;
|
|
261
|
+
interface RemoteApiClient {
|
|
262
|
+
whoami(): Promise<{
|
|
263
|
+
agentId: string;
|
|
264
|
+
}>;
|
|
265
|
+
requestBrowserTakeover(args: {
|
|
266
|
+
instructions: string;
|
|
267
|
+
url?: string;
|
|
268
|
+
conversationId?: string;
|
|
269
|
+
}): Promise<{
|
|
270
|
+
sessionId: string;
|
|
271
|
+
status: string;
|
|
272
|
+
}>;
|
|
273
|
+
completeRemoteSession(sessionId: string): Promise<unknown>;
|
|
274
|
+
}
|
|
275
|
+
interface BrowserSurfaceLike {
|
|
276
|
+
automation: {
|
|
277
|
+
navigate(url: string): Promise<unknown>;
|
|
278
|
+
click(selector: string): Promise<void>;
|
|
279
|
+
type(selector: string, text: string): Promise<void>;
|
|
280
|
+
waitFor(options: {
|
|
281
|
+
selector?: string;
|
|
282
|
+
urlPattern?: string;
|
|
283
|
+
ms?: number;
|
|
284
|
+
}): Promise<void>;
|
|
285
|
+
screenshot(): Promise<string>;
|
|
286
|
+
evaluate(expression: string): Promise<unknown>;
|
|
287
|
+
};
|
|
288
|
+
openSession(sessionId: number, open: SessionOpenPayload): void | Promise<void>;
|
|
289
|
+
closeSession(sessionId: number): void;
|
|
290
|
+
handleFrame(frame: RemoteFrame): void;
|
|
291
|
+
requestHandoff(timeoutMs: number): Promise<HandoffResult>;
|
|
292
|
+
addHold(): void;
|
|
293
|
+
removeHold(): void;
|
|
294
|
+
shutdown(): Promise<void>;
|
|
295
|
+
}
|
|
296
|
+
interface TerminalSurfaceLike {
|
|
297
|
+
openSession(sessionId: number, open: SessionOpenPayload): void | Promise<void>;
|
|
298
|
+
closeSession(sessionId: number): void;
|
|
299
|
+
handleFrame(frame: RemoteFrame): void;
|
|
300
|
+
shutdown(): void;
|
|
301
|
+
}
|
|
302
|
+
interface RemoteClientLike {
|
|
303
|
+
readonly bufferedAmount?: number;
|
|
304
|
+
start(): void;
|
|
305
|
+
stop(): void;
|
|
306
|
+
sendFrame(frame: Buffer): void;
|
|
307
|
+
}
|
|
308
|
+
interface RemotePluginRuntimeState {
|
|
309
|
+
generation: number;
|
|
310
|
+
remoteClient: RemoteClientLike | null;
|
|
311
|
+
browserSurface: BrowserSurfaceLike | null;
|
|
312
|
+
terminalSurface: TerminalSurfaceLike | null;
|
|
313
|
+
apiClient: RemoteApiClient | null;
|
|
314
|
+
handoffTimeoutMs: number;
|
|
315
|
+
dashboardBaseUrl?: string;
|
|
316
|
+
selfAgentId?: string;
|
|
317
|
+
sessionSurfaces: Map<number, RemoteSurface>;
|
|
318
|
+
stopPromise: Promise<void> | null;
|
|
319
|
+
}
|
|
320
|
+
interface RemotePluginDependencies {
|
|
321
|
+
resolveConfig?: () => ResolvedConfig;
|
|
322
|
+
createApiClient?: (config: {
|
|
323
|
+
apiKey: string;
|
|
324
|
+
apiUrl: string;
|
|
325
|
+
}) => RemoteApiClient;
|
|
326
|
+
createRemoteClient?: (options: RemoteServiceClientOptions) => RemoteClientLike;
|
|
327
|
+
createBrowserSurface?: (options: BrowserSurfaceOptions, sendFrame: (frame: Buffer) => void) => BrowserSurfaceLike;
|
|
328
|
+
createTerminalSurface?: (options: TerminalSurfaceOptions, sendFrame: (frame: Buffer) => void) => TerminalSurfaceLike;
|
|
329
|
+
installErrorCapture?: typeof installToolErrorCapture;
|
|
330
|
+
runtimeState?: RemotePluginRuntimeState;
|
|
331
|
+
}
|
|
332
|
+
declare function createRemotePluginRuntimeState(): RemotePluginRuntimeState;
|
|
333
|
+
/**
|
|
334
|
+
* Derive the relay WebSocket URL from the agent's cloud apiUrl when the
|
|
335
|
+
* manifest doesn't provide one — mirrors @alfe.ai/console-client's
|
|
336
|
+
* `deriveConsoleWsUrl` so the URL is per-stage automatically:
|
|
337
|
+
* https://api.dev.alfe.ai → wss://remote.dev.alfe.ai/ws
|
|
338
|
+
* (matches config.flyDomains.remote per stage). This is why the
|
|
339
|
+
* headless-browser integration manifest carries no hardcoded, prod-pinned
|
|
340
|
+
* `remoteWsUrl`: the plugin resolves it from the agent's own endpoint.
|
|
341
|
+
*/
|
|
342
|
+
declare function deriveRemoteWsUrl(apiUrl: string): string | undefined;
|
|
343
|
+
/**
|
|
344
|
+
* Derive the dashboard (control-plane) base URL from the agent's cloud apiUrl,
|
|
345
|
+
* mirroring `deriveRemoteWsUrl` so it's per-stage automatically:
|
|
346
|
+
* https://api.dev.alfe.ai → https://app.dev.alfe.ai
|
|
347
|
+
* https://api.alfe.ai → https://app.alfe.ai (prod)
|
|
348
|
+
* The `api.` → `app.` swap matches config.*.ts `dashboardDomain` for every
|
|
349
|
+
* stage. Returns undefined for a non-`api.` host (e.g. localhost/dev override)
|
|
350
|
+
* so the caller can fall back to a session-only link.
|
|
351
|
+
*/
|
|
352
|
+
declare function deriveDashboardBaseUrl(apiUrl: string): string | undefined;
|
|
353
|
+
/**
|
|
354
|
+
* Build the dashboard deep-link ("control URL") the human opens to take over.
|
|
355
|
+
* Prefers the full agent-scoped path; if the dashboard host or agentId is
|
|
356
|
+
* unavailable, degrades gracefully (session-only path, then null).
|
|
357
|
+
*/
|
|
358
|
+
declare function buildControlUrl(dashboardBaseUrl: string | undefined, agentId: string | undefined, sessionId: string): string | undefined;
|
|
359
|
+
declare function createRemotePlugin(dependencies?: RemotePluginDependencies): {
|
|
360
|
+
id: string;
|
|
361
|
+
name: string;
|
|
362
|
+
description: string;
|
|
363
|
+
version: string;
|
|
364
|
+
activate(api: OpenClawPluginApi): void;
|
|
365
|
+
deactivate(api: OpenClawPluginApi): Promise<void>;
|
|
366
|
+
};
|
|
367
|
+
declare function parseRemotePluginConfig(value: unknown, browserFallback: unknown): RemotePluginConfig;
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/ssrf.d.ts
|
|
5
370
|
/**
|
|
6
371
|
* SSRF navigation guard for the shared co-browse surface.
|
|
7
372
|
*
|
|
8
|
-
* Builds the `isNavigationAllowed` predicate that `BrowserSurface`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* the integration's SSRF policy explicitly allows the private network
|
|
14
|
-
* (`dangerouslyAllowPrivateNetwork`). The policy is the same one the `alfe`
|
|
15
|
-
* integration exposes under `browser.ssrfPolicy` and threads into runtime
|
|
16
|
-
* config.
|
|
373
|
+
* Builds the `isNavigationAllowed` predicate that `BrowserSurface` applies to
|
|
374
|
+
* explicit agent navigation and to every prepared page's request interception.
|
|
375
|
+
* It blocks non-web schemes, credential-bearing URLs, private, loopback,
|
|
376
|
+
* link-local, multicast, documentation, benchmark, and other reserved literal
|
|
377
|
+
* hosts unless the integration explicitly opts into private-network access.
|
|
17
378
|
*
|
|
18
379
|
* SCOPE — what this guard does and does NOT cover:
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
21
|
-
* - Does NOT gate HUMAN navigation during a takeover: the human types URLs
|
|
22
|
-
* into the live browser and those never pass through this predicate.
|
|
23
|
-
* Blocking that requires CDP-level request interception
|
|
24
|
-
* (`Fetch.enable` / `Network.requestIntercept`) inside the surface —
|
|
25
|
-
* deferred (follow-up).
|
|
380
|
+
* - Gates agent `page.goto`, redirects, page subresources, popups, page-JS
|
|
381
|
+
* fetches, and human navigation after a page is prepared by BrowserSession.
|
|
26
382
|
* - Does NOT defend against DNS rebinding: we deliberately do not resolve
|
|
27
383
|
* hostnames to IPs here (no async DNS in a sync predicate). A public
|
|
28
384
|
* hostname that resolves to a private IP at goto-time is NOT caught in v1
|
|
@@ -35,11 +391,10 @@ interface SsrfPolicy {
|
|
|
35
391
|
/**
|
|
36
392
|
* Build the navigation predicate for a given SSRF policy. When
|
|
37
393
|
* `dangerouslyAllowPrivateNetwork` is true the predicate is allow-all
|
|
38
|
-
*
|
|
39
|
-
* the reserved ranges above and any non-http(s) scheme.
|
|
394
|
+
* otherwise it blocks the reserved ranges above and any non-http(s) scheme.
|
|
40
395
|
*/
|
|
41
396
|
declare function buildIsNavigationAllowed(policy: SsrfPolicy | undefined, log?: {
|
|
42
397
|
warn(msg: string): void;
|
|
43
398
|
}): (url: string) => boolean;
|
|
44
399
|
//#endregion
|
|
45
|
-
export { type RemotePluginConfig, type SsrfPolicy, buildIsNavigationAllowed,
|
|
400
|
+
export { type BrowserSurfaceLike, PLUGIN_VERSION, REMOTE_ACTIVATION_KEY, type RemoteApiClient, type RemoteClientLike, type RemotePluginConfig, type RemotePluginDependencies, type RemotePluginRuntimeState, type SsrfPolicy, type TerminalSurfaceLike, buildControlUrl, buildIsNavigationAllowed, createRemotePlugin, createRemotePluginRuntimeState, deriveDashboardBaseUrl, deriveRemoteWsUrl, parseRemotePluginConfig };
|