Files
Mohamed Boudra 91dca5702b Connect saved hosts and delegate agents across daemons (#7370)
* feat(hosts): connect to saved hosts through a daemon

* fix(hosts): validate safe targets and verify encrypted relay routes

* test(hosts): include relay coverage in existing CI contracts

* fix(cli): preserve validated addresses in daemon command hints

* fix(hosts): share daemon links across isolated routed sessions

Multiplex logical sessions over the monitored daemon connection and wrap original binary bytes in routing headers. Admit channels under their authenticated parent and current grants; preserve scoped delivery and physical send completion.

* fix(hosts): bound encrypted overload and initial acquisition

Check encrypted wire capacity before the async channel converts a resource limit into a physical connection error. Bound initial routed acquisition with the existing client timeout, then enable reconnect after admission.

* fix(hosts): keep compact row actions clear of host labels

Give compact connected-host rows separate text and action widths so long names and producer labels cannot compete with primary controls.

* refactor(hosts): remove management settings UI from phase one

Keep connected-host management in the CLI and shared SDK while preserving app saved connections and plugin access.

* test(hosts): control physical write backpressure across platforms

Hold real TCP writes until assertions finish instead of assuming a paused receiver exhausts every platform receive buffer. Preserve physical send settlement, byte identity, capacity and sibling checks.

* refactor(hosts): use the ordinary client contract over channel transports

Keep queuing, permission codes and reconnection independent of transport selection. Track submitted request outcomes generically while retaining connection error codes and structured causes.

* test(e2e): keep transient developer toasts from stalling checks

Concurrent locator handlers can wait for a dismiss button another handler removed. Dismiss existing toast DOM at document lifetime, including its open shadow root, without intercepting app actions.

* Create, prompt, and monitor agents across connected hosts (#7507)

* feat(hosts): delegate agents across connected hosts

Resolve MCP host operations through in-process commands for self and the existing routed client transport for other destinations. Keep ordinary parent labels, app sessions and idle finish notifications.

* test(app): normalize subagent fixtures through snapshots

* docs(protocol): clarify same-host parent labels

* fix(hosts): resolve subagents through destination sessions

A child address identifies its destination regardless of the connection used. Reuse the existing host candidate selection for discovered routes and direct connections, and keep navigation and row actions independent of the parent transport.

* fix(hosts): keep routed destinations distinct during discovery

A routed candidate names only the origin daemon. Match it within the named destination profile so two destinations discovered through the same origin retain separate identities and connections, including existing saved routes.

* feat(hosts): prompt and query agents on selected hosts

Extend the existing MCP host operation boundary with prompt, status, activity and list operations. Local calls retain their handlers and notification owner; selected destinations use ordinary client RPCs and the existing idle finish wait.

* test(e2e): preserve toast coverage with CSS hiding

* docs(hosts): translate connected host references
2026-10-21 16:10:47 +07:00

26 KiB
Raw Permalink Blame History

title, description, nav, order, category
title description nav order category
SDK API reference Public configuration, methods, handles, results, defaults, and lifecycle behavior for @getpaseo/client. API reference 58 TypeScript SDK

SDK API reference

Import every supported runtime value and TypeScript type from @getpaseo/client.

createPaseoClient(config)

Creates a client without opening the connection.

Required configuration:

Field Type Meaning
url string Daemon WebSocket endpoint, including /ws.

Common optional configuration:

Field Type Default Meaning
clientId string Generated Stable identifier for logs and subscriptions.
password string Unset Daemon password.
authHeader string Unset Complete authorization-header value for a proxy.
connectTimeoutMs number Client default Connection deadline.
reconnect.enabled boolean Client default Reconnect after an unexpected disconnect.
reconnect.baseDelayMs number Client default Initial reconnect delay.
reconnect.maxDelayMs number Client default Maximum reconnect delay.
logger PaseoLogger Unset Debug, info, warning, and error sink.

Relay E2EE clients can also pass e2ee.enabled and e2ee.daemonPublicKeyB64. appVersion, runtimeGeneration, and runtime-metrics options exist for Paseo client surfaces; ordinary integrations can omit them.

Client lifecycle

Method Result Behavior
connect() Promise<void> Resolves after the daemon sends its server information.
close() Promise<void> Closes the connection and disposes this client.
ensureConnected() void Throws unless the client is connected.
getConnectionState() ConnectionState Returns idle, connecting, connected, disconnected, or disposed.

Create a new client after close().

client.agents

Method Result Behavior
list(options?) PaseoAgentListResult Lists a page of agents. scope, filter, sort, page, and subscribe match the daemon directory query.
create(options) PaseoAgentHandle Creates an agent and a fresh workspace for cwd. Requires config.
ref(agentOrId) PaseoAgentHandle Creates a local handle without fetching.
subscribe(handler) Unsubscribe function Local listener for this API instance. Requires an owned list({ subscribe: {} }) observation.

list({ subscribe: {} }) also returns a subscriptionId and an owned subscription. Its subscribe({ snapshot, update, error? }) callbacks receive the snapshot before scoped wire updates; release() ends that observation. Capable daemons assign the ID and keep observations independent. Older daemons use local IDs and their established shared delivery behavior. Plain lists create no observation. The same contract applies to workspace lists. See events.

Creation options include config, cwd, parent, title, prompt, env, outputSchema, images, attachments, git, worktree, autoArchive, background, and labels.

parent accepts a host-local agent ID, an AgentAddress ({ serverId, agentId }), or an agent handle. A handle’s address keeps its host identity after disconnect. When creating through hosts.client(serverId), pass the parent handle or complete address; the SDK carries parent metadata to the destination.

background is workspace creation intent. It cannot accompany an existing workspaceId. See background workspaces for caller inheritance and inclusive discovery.

config accepts:

Field Type Meaning
provider string Required provider/model selection.
modeId string Provider operating or permission mode.
thinkingOptionId string Provider reasoning level.
featureValues Record<string, unknown> Values for features discovered through providers.listFeatures.
options JSON object Provider-native settings, strictly validated. See Provider options.
systemPrompt string Additional system or developer instructions.
mcpServers MCP server map Session-scoped MCP servers.
toolPolicy MCP tool policy Exact preapproval rules for MCP tools.

Agent handle

Member Result Behavior
id string Stable daemon agent ID.
address AgentAddress Complete { serverId, agentId } identity; requires a connected host to establish it.
workspaceId string | null Current workspace placement.
cwd string | null Current working directory.
status Agent status or null Current lifecycle status.
capabilities Capability flags or null What the provider session supports.
availableModes Agent modes or null Modes the session can switch to.
pendingPermissions Permission requests or null Requests waiting on an answer.
activeTurn Active turn or null The turn in flight, with turnId and startedAt.
lastUsage Usage or null Token counts, cost, and context-window use from the last turn.
lastError string | null Last error the daemon recorded for the agent.
features Agent features or null Provider feature toggles and selects with their current values.
runtimeInfo Runtime info or null Live provider, session ID, model, thinking option, and mode.
archivedAt string | null Archive timestamp; null while the agent is active.
current() PaseoAgent | null Current detailed value observed by this handle; never fetches.
refresh(requestId?) PaseoAgentRefetchResult | null Fetches the current agent and project placement.
send(text, options?) Promise<void> Resolves when the daemon accepts the prompt.
respondToPermission(options) Promise<void> Answers a pending permission by requestId with an allow or deny response.
run(text, options?) PaseoAgentRunResult Sends a prompt and waits for that turn. timeoutMs controls the wait; it defaults to 10 minutes.
waitForFinish(timeoutMs?) PaseoAgentRunResult Waits for the active turn, including an initial prompt. Default timeout: 10 minutes.
commands(options?) PaseoAgentCommandsResult Asks the live session for its slash commands and skills, including built-in ones. Options: requestId.
subscribe(handler) Unsubscribe function Filters agent-directory updates to this ID and refreshes the handle properties.
archive() { archivedAt } Soft-deletes the agent and closes its runtime.
detach() Promise<void> Removes the parent relationship without stopping the agent.

workspaceId through archivedAt mirror the last snapshot the handle observed. A handle from ref() reads null for all of them until refresh(), run(), waitForFinish(), a timeline refetch, or subscribe() delivers a snapshot. Optional values in an observed snapshot also read as null. Call current() when you need the whole snapshot or need to distinguish those states.

PaseoAgentRunResult contains status, final, error, and lastMessage. final refreshes the handle when present.

PaseoAgentCommandsResult contains agentId, commands, and error. Each command has name, description, argumentHint, and an optional kind of "command" or "skill". A provider that cannot answer reports it in error rather than rejecting; providers that expose no command list at all return an empty array.

Timeline handle

agent.timeline.refetch(options?) fetches a page. Options are direction, cursor, limit, projection, and requestId.

agent.timeline.subscribe(handler) establishes network demand for this agent and restores it after reconnect. Its unsubscribe function releases that demand; await unsubscribe.ready for initial daemon acknowledgement before starting work. Delivery is live-only. Reconnect emits a local subscription_restored event; request missed history explicitly with refetch(). A live replacement invalidates the previous epoch. See the callback shapes, paging and failure behavior in timeline events.

client.projects

Method Result Behavior
list(options?) PaseoProjectListResult Lists every registered project, including projects with no active workspaces.
subscribe(handler) Unsubscribe function Requests future project updates; unsubscribe releases demand. list() supplies initial state.

See events for explicit event observation and cleanup.

client.workspaces

Method Result Behavior
list(options?) PaseoWorkspaceListResult Lists, filters, pages, or subscribes to the workspace directory.
open(cwd) PaseoWorkspaceHandle Reuses the active workspace for a directory or creates one.
create(options) PaseoWorkspaceHandle Always creates a fresh directory-backed or Paseo-worktree workspace. background: true hides it from default discovery.
ref(workspaceOrId) PaseoWorkspaceHandle Creates a local handle.
archive(workspaceOrId) PaseoWorkspaceArchiveResult Archives without first creating a handle.
subscribe(handler) Unsubscribe function Local listener for this API instance. Requires an owned list({ subscribe: {} }) observation.

create({ source, agent, background }) creates a workspace and its initial agent together. agent accepts agent creation options except worktree, git, onEvent, idempotencyKey, and requestId. Set background on the workspace options; agent.background is rejected. See the combined creation example.

A workspace handle exposes id, projectId, directory, name, status, current(), refresh(), setTitle(title), archive(), and subscribe(). Pass null to setTitle to restore the derived workspace name. Use workspace.agents.create(options) to create an agent without repeating the workspace ID or directory.

client.terminals

Terminal operations require a host that supports workspace terminals. An older host receives no terminal request; the SDK throws an update-host error.

Method Result Behavior
create(options) Promise<PaseoTerminalHandle> Creates a terminal owned by the required workspaceId.
list(options?) Promise<PaseoTerminalListResult> Returns { entries, requestId }. Omit filters to list all terminals on this host.
ref(terminalOrId) PaseoTerminalHandle Creates a local handle without fetching or attaching a terminal stream.

Creation options:

Field Meaning
workspaceId Required active workspace ID. Unknown and archived IDs fail.
cwd Optional absolute process working directory. Defaults to the workspace directory; changing it does not change ownership.
name Optional terminal name.
command, args Optional executable and argument array. Omit them to start the default shell.
size Optional initial viewport: { rows, cols }.
requestId Optional request correlation ID.

List options are workspaceId, cwd, and requestId. workspaceId selects ownership, including terminals started outside the workspace directory. When it is present, cwd does not restrict the results. Without an ID, cwd filters by workspace root directory. Each entry contains id, workspaceId, cwd, and name; cwd is the terminal's actual starting directory.

Terminal handles expose:

Method Result Behavior
current() PaseoTerminal | null Last snapshot from creation, ref(snapshot), or refresh. A handle made from an ID starts empty.
refresh(options?) Promise<PaseoTerminal | null> Fetches the terminal snapshot, or null when it no longer exists. Accepts requestId.
write(data) number Sends literal text without interpreting key names. Returns the input's UTF-16 length.
sendKeys(keys) number Expands key tokens and sends the combined input. Returns its UTF-16 length.
capture(options?) Promise<PaseoTerminalCaptureResult> Returns { terminalId, lines, totalLines, requestId }.
kill(requestId?) Promise<void> Waits for terminal teardown. Killing an already-removed terminal succeeds.

sendKeys() recognizes Enter, Tab, Escape, Space, BSpace, C-c, C-d, C-z, C-l, C-a, and C-e. Other strings pass through literally. Input methods send without waiting for command execution or acknowledging that the terminal consumed the input.

Capture accepts optional start, end, stripAnsi, and requestId. Line bounds are zero-based and inclusive across scrollback and the viewport. Negative bounds count from the end; omitted bounds capture all lines. stripAnsi defaults to true. A missing terminal returns empty lines.

Use workspace.terminals.create(options?) and workspace.terminals.list(options?) to supply the workspace ID from a handle. Creation accepts the same options except workspaceId; listing accepts only requestId. Plugins get these methods through usePaseo() and the handler's paseo context.

client.providers

Method Result Behavior
waitForReady(options?) PaseoProviderSnapshotResult Waits until no provider is loading. Default timeout: 60 seconds. Rejects with an update-host error when the daemon cannot correlate workspace snapshots.
snapshot(options?) PaseoProviderSnapshotResult Returns the current catalog immediately.
refresh(options?) Acknowledgement Forces catalog refresh for all or selected providers.
listAvailable() Availability result Reports installed provider availability.
listModels(provider, options?) Models result Discovers models for one provider and directory.
listModes(provider, options?) Modes result Discovers permission or operating modes.
listFeatures(draftConfig) Features result Discovers features for the current draft provider configuration.
diagnostic(provider) Diagnostic result Returns human-readable setup diagnostics.
listUsage(options?) PaseoProviderUsageResult Returns normalized subscription windows, balances, and provider details. Rejects with an update-host error when unsupported. Options: requestId.
subscribe(handler) Unsubscribe function Requests future catalog updates; unsubscribe releases demand.

client.config

config.get(requestId?) returns the daemon's mutable configuration.

config.patch(patch, requestId?) validates, persists, and returns an updated configuration. Use this administrative surface for host configuration, not per-agent choices. A patch affects every client and future agent using that daemon.

client.hosts

Manage hosts connected to the selected daemon. Both the managing daemon and destination must support connected hosts.

const directory = await client.hosts.list();
const registration = await client.hosts.add({
  connection: "ssh://deploy@build-box",
  label: "Build box",
  metadata: { team: "infra" },
  sourceKey: "build-box",
});
await client.hosts.update(registration.registrationId, { enabled: false });
await client.hosts.remove(registration.registrationId);

list() returns { self: { serverId, label }, hosts }. Each host includes its registration and server IDs, label, authenticated source, string metadata, enabled state, connection status, safe target description, last-online/retry timestamps, and an actionable error. Credentials are never returned. A valid but unreachable connection is saved and retried. Invalid syntax fails without saving.

update(registrationId, patch) accepts label, metadata, enabled, connection, and replaceIdentity. Metadata replaces the whole map. Replacing a connection preserves the expected server ID unless replaceIdentity: true explicitly accepts a new destination.

observe() returns an owned subscription. Await ready, subscribe for snapshots and hosts.changed updates, then call release(). Observations restore a current snapshot after reconnect.

const remote = await client.hosts.client("srv_build_box");
try {
  const workspaces = await remote.workspaces.list();
  const providers = await remote.providers.listAvailable();
  await remote.agents.ref("agent_on_build_box").send("Continue the investigation");
} finally {
  await remote.dispose();
}

client(serverId) returns a caller-owned PaseoApi bound to that destination through the selected daemon. The ordinary APIs keep their signatures. Paths and IDs belong to the destination. Selecting the local server ID returns a local scope. Routing supports one hop.

Disposing one scope releases its observations without closing siblings or deleting the saved host. Origin disposal cascades. Observations recover through the ordinary client lifecycle. While connecting, new requests use the existing bounded send queue; disconnected requests fail. Connection loss clears old waiters and queued sends. Ordinary non-recoverable requests are not replayed; existing acknowledged/idempotent creation recovery retains its request identity. A submitted request whose response is lost rejects with DaemonConnectionError (code: "DAEMON_CONNECTION_LOST"), optional outcome: "unknown" and known serverId. Inspect before retrying a mutation.

Registration and host acquisition failures expose a stable code: host_unknown, host_disabled, host_offline, permission_denied, unsupported_capability, authentication_failed, server_id_mismatch, self_host, or duplicate_host. Ordinary RPC permission codes, including access_denied, pass through unchanged. Multiplexing is a transport; consumers use the same client/API operations and lifecycle. Discovery does not grant execution authority.

Errors and cleanup

Connection, validation, rejection, and timeout failures reject their promise. Turn outcomes are returned through PaseoAgentRunResult.status because permission and provider errors are expected agent states.

Always close the client in finally. Closing a client removes its local listeners and network connection; it does not stop agents or archive workspaces.