Files
openclaw/src/plugins/types.node-host.ts
Peter Steinberger d8d2f83cc1 feat(terminal): open Codex/Claude catalog sessions in a terminal on their owning host
Catalog session rows (sidebar context menu + click), the built-in viewer
header, and a new "Open Codex/Claude sessions in" preference can launch the
native CLI (codex resume / claude --resume) in the operator terminal on the
machine that owns the session.

- Gateway-local sessions spawn through the existing terminal launch policy
  (sandbox/enabled gates preserved) with the resume command in the session cwd.
- Paired-node sessions run through a new seq-ordered node PTY relay: a
  duplex node-host command streams PTY output via node.invoke.progress and
  receives keystrokes/resize via a new node.invoke.input event, behind the
  unchanged terminal.* client protocol (TerminalSessionManager gains a backend
  abstraction; node relay reuses the streaming-invoke controller).
- Owner boundary: each plugin owns its resume command and builds argv from a
  validated thread id; the gateway routes node opens through the node command
  allowlist and plugin invoke policy (no advertisement-only trust), and nodes
  re-verify session eligibility before spawning.
- UI setting catalogOpenTarget + canOpenTerminal capability gate every entry
  point; capability requires the owning host to actually have the CLI.

Node PATH is normalized before command-availability probes, Windows .cmd/.bat
shims spawn via ComSpec, and catalog terminal opens reattach persisted tabs
before opening the new tab.
2026-07-13 22:20:50 -07:00

40 lines
1.6 KiB
TypeScript

// Node-host plugin command contracts, including the opt-in duplex transport.
import type { OpenClawConfig } from "../config/types.openclaw.js";
export type OpenClawPluginNodeHostCommandAvailabilityContext = {
/** Node-local configuration used to build this host's Gateway declaration. */
config: OpenClawConfig;
/** Node-host process environment. */
env: NodeJS.ProcessEnv;
};
export type OpenClawPluginNodeHostCommandIo = {
emitChunk(chunk: string): Promise<void>;
onInput(callback: (payloadJSON: string) => void): void;
signal: AbortSignal;
};
type OpenClawPluginNodeHostCommandBase = {
command: string;
cap?: string;
dangerous?: boolean;
/** Return false to omit this command and capability from the node declaration. */
isAvailable?: (context: OpenClawPluginNodeHostCommandAvailabilityContext) => boolean;
agentTool?: {
name: string;
description: string;
parameters?: Record<string, unknown>;
/** Platforms where this tool is allowlisted by default; omit for explicit config only. */
defaultPlatforms?: Array<"ios" | "android" | "macos" | "windows" | "linux" | "unknown">;
mcp?: { server: string; tool: string };
};
};
export type OpenClawPluginNodeHostCommand = OpenClawPluginNodeHostCommandBase & {
// Not a discriminated handle signature: a union of different arities makes
// plain `command.handle(params)` uncallable for consumers holding the union.
// The node host enforces io presence for duplex commands at runtime.
duplex?: boolean;
handle: (paramsJSON?: string | null, io?: OpenClawPluginNodeHostCommandIo) => Promise<string>;
};