@alfe.ai/remote-host
v0.0.5
Published
Runtime-agnostic host for the Alfe interactive remote-control relay: the relay/browser/terminal resource graph, browser takeover, and an SDK-free tool catalog any agent runtime can register
Readme
@alfe.ai/remote-host
Runtime-agnostic host for Alfe's interactive remote-control relay. One
outbound WebSocket carries browser co-browse (@alfe.ai/browser) and web
terminal (@alfe.ai/terminal) sessions. This package owns the resource
graph, the human browser-takeover flow, the default-deny SSRF navigation
policy, and an SDK-free tool catalog. It has no agent-runtime SDK dependency.
Adapters own activation, config sourcing, and tool registration:
@alfe.ai/openclaw-remote(OpenClaw plugin) is the first adapter.- Terminal-runtime hosts (claude-code, codex, …): the persistent agent host
owns a
RemoteHost(via@alfe/agent-host-core'screateTerminalRemote) and executes the catalog in async-takeover mode; thealfe-platformMCP server (@alfe.ai/mcp-toolsregisterRemoteTools) forwards calls to it over a local socket.
Host
import { RemoteHost } from "@alfe.ai/remote-host";
// resolveConfig is the caller's own dependency (e.g. `@alfe.ai/config`); any
// () => ({ apiKey, apiUrl, workspacePath }) works.
import { resolveConfig } from "@alfe.ai/config";
const host = new RemoteHost({ logger, resolveCredentials: resolveConfig });
const result = await host.start({ config, ssrfPolicy, workspaceDir }); // "started" | "idle" | "busy"
// …
await host.stop();Each RemoteHost owns its own RemoteHostState (or adopts one passed as
state). Two instances never share resources. stop() invalidates the
generation, stops the relay, closes every session, awaits browser shutdown,
and clears the browser re-check timer.
Tool catalog
import { createRemoteToolCatalog, publicErrorMessage } from "@alfe.ai/remote-host";
for (const tool of createRemoteToolCatalog(host)) {
// tool.name, tool.description, tool.inputSchema (JSON Schema object)
// tool.execute(args, { conversationId }) → { type: "json", value } | { type: "image", data, mimeType, bytes }
// On throw, show the model only publicErrorMessage(error).
}conversationIdcomes from the host's trusted context. Only an adapter whose runtime cannot supply it passes{ modelSuppliedConversationId: true }, which adds it to the takeover schema (a context value still wins).executere-validates every bound at runtime; schemas are advisory to some hosts.- Send
imageoutput as native image content. Never copy its base64 into JSON. - Bound JSON output at the host's own result limit (OpenClaw: 512 KiB).
MCP usage: expose each entry under its catalog name, pass the
JSON Schema as the tool's input schema (the low-level Server list handler
accepts it directly; McpServer needs an equivalent zod shape), map image
output to MCP image content, and return publicErrorMessage(error) with
isError: true on failure. Supply conversationId from the session, not the
model.
Takeover
Blocking mode (OpenClaw): request_browser_takeover blocks until hand-back
or timeout. Async mode (AsyncTakeovers, MCP runtimes): it returns within
45 s and browser_takeover_wait continues; the host keeps the takeover alive
and reports an unclaimed hand-back through onSettled. The takeover
API's 409 (no shared browser) and 503 (presence unconfirmed) map to the
fixed TAKEOVER_NO_BROWSER_MESSAGE / TAKEOVER_PRESENCE_UNAVAILABLE_MESSAGE.
The session the call created is always completed.
