From 5138fafac9e09316f26bb998d9fe8744deb4cdad Mon Sep 17 00:00:00 2001 From: qiubinbin <767759678@qq.com> Date: Thu, 23 Jul 2026 18:57:18 +0800 Subject: [PATCH 1/2] fix OpenClaw terminal output latency --- .../terminal/terminalTransport.ts | 35 ++++++++----------- .../terminal/terminalTransportChain.test.mjs | 9 +++++ .../terminal/terminalTransportChain.ts | 19 ++++++++++ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts b/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts index 879b8a34..5c50ebbb 100644 --- a/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts +++ b/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts @@ -3,12 +3,10 @@ import { createTerminalWebSocketUrl, parseTerminalEvent, type CreateWorkbenchTerminalOptions, - type WorkbenchTerminalEvent, } from "./terminalClient"; -import { createGatewayTerminalTransport } from "./terminalGatewayTransport"; import { createSseTerminalTransport } from "./terminalSseTransport"; import { - createTerminalTransportFallbackChain, + resolveTerminalTransportChannel, type TerminalFallbackNotice, type TerminalTransportFailure, } from "./terminalTransportChain"; @@ -33,8 +31,9 @@ export interface WorkbenchTerminalTransportHandlers { /** * Mode-agnostic terminal realtime channel. Standalone mode uses the raw - * `/ws/terminal` WebSocket; OpenClaw gateway single-port mode tunnels the - * session through host gateway RPC (`terminalGatewayTransport.ts`). + * `/ws/terminal` WebSocket; OpenClaw gateway single-port mode uses the + * same-origin HTTP/SSE channel so PTY output is not delayed by host event + * scheduling. */ export interface WorkbenchTerminalTransport { sendInput: (data: string) => void; @@ -120,12 +119,10 @@ function createFailedTerminalTransport( /** * Picks the realtime transport from the injected runtime config: - * `realtimeTransport: "gateway-rpc"` (gateway exposure) tunnels through the - * OpenClaw host gateway, with an automatic fallback to the HTTP/SSE compat - * channel (`terminalSseTransport.ts`) when the host rejects the RPC attach - * (auth/pairing/protocol) — see `terminalTransportChain.ts`; `"raw-ws"` keeps - * the direct WebSocket; `"disabled"` or `features.terminalRealtime === false` - * yields a structured error state. + * `realtimeTransport: "gateway-rpc"` (gateway exposure) uses the HTTP/SSE + * channel because OpenClaw host gateway events are not flushed at interactive + * terminal latency. `"raw-ws"` keeps the direct WebSocket; `"disabled"` or + * `features.terminalRealtime === false` yields a structured error state. */ export function connectWorkbenchTerminal( sid: string, @@ -139,22 +136,18 @@ export function connectWorkbenchTerminal( handlers, ); } - const transportKind = - runtime?.realtimeTransport ?? (isGatewayExposure() ? "gateway-rpc" : "raw-ws"); + const transportKind = resolveTerminalTransportChannel( + runtime?.realtimeTransport, + isGatewayExposure(), + ); if (transportKind === "disabled") { return createFailedTerminalTransport( "服务端已禁用终端实时通道(realtimeTransport=disabled)。", handlers, ); } - if (transportKind === "gateway-rpc") { - return createTerminalTransportFallbackChain({ - createPrimary: (chainHandlers) => - createGatewayTerminalTransport(sid, options, chainHandlers), - createFallback: (chainHandlers) => - createSseTerminalTransport(sid, options, chainHandlers), - handlers, - }); + if (transportKind === "sse") { + return createSseTerminalTransport(sid, options, handlers); } return createRawWebSocketTerminalTransport(sid, options, handlers); } diff --git a/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.test.mjs b/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.test.mjs index ab2cebc4..930a0a39 100644 --- a/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.test.mjs +++ b/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.test.mjs @@ -16,6 +16,7 @@ import { computeReconnectDelayMs, createTerminalTransportFallbackChain, isGatewayFallbackEligible, + resolveTerminalTransportChannel, } from "./terminalTransportChain.ts"; function createMockFactory(name, created) { @@ -186,3 +187,11 @@ test("buildCompatChannelNotice keeps reason and adds actionable guidance", () => const fallback = buildCompatChannelNotice({ kind: "network", message: "" }); assert.match(fallback.detail, /网关通道不可用/, "empty reason gets a sane default"); }); + +test("OpenClaw gateway exposure selects immediate SSE output", () => { + assert.equal(resolveTerminalTransportChannel("gateway-rpc", true), "sse"); + assert.equal(resolveTerminalTransportChannel(undefined, true), "sse"); + assert.equal(resolveTerminalTransportChannel("raw-ws", true), "raw-ws"); + assert.equal(resolveTerminalTransportChannel(undefined, false), "raw-ws"); + assert.equal(resolveTerminalTransportChannel("disabled", true), "disabled"); +}); diff --git a/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.ts b/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.ts index c8ad45f5..0c1bb124 100644 --- a/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.ts +++ b/apps/web/src/features/ide-workbench/terminal/terminalTransportChain.ts @@ -31,6 +31,25 @@ export const TERMINAL_RECONNECT_MAX_ATTEMPTS = 5; export const TERMINAL_RECONNECT_BASE_DELAY_MS = 1_000; export const TERMINAL_RECONNECT_MAX_DELAY_MS = 15_000; +export type TerminalRealtimeTransportSetting = + | "gateway-rpc" + | "raw-ws" + | "disabled"; + +/** + * OpenClaw gateway events are host-scheduled and can delay interactive PTY + * output. Its same-origin HTTP/SSE stream is flushed immediately, so gateway + * exposure uses that channel while standalone mode keeps the raw WebSocket. + */ +export function resolveTerminalTransportChannel( + configured: TerminalRealtimeTransportSetting | undefined, + gatewayExposure: boolean, +): "sse" | "raw-ws" | "disabled" { + const setting = configured ?? (gatewayExposure ? "gateway-rpc" : "raw-ws"); + if (setting === "disabled") return "disabled"; + return setting === "gateway-rpc" ? "sse" : "raw-ws"; +} + /** * Exponential backoff for abnormal-close re-attach: 1s → 2s → 4s → 8s → 15s * (capped). `attemptIndex` is zero-based (first retry waits 1s). From a7bd879fb3c692181afacf00b6b2964162987358 Mon Sep 17 00:00:00 2001 From: qiubinbin <767759678@qq.com> Date: Thu, 23 Jul 2026 19:07:11 +0800 Subject: [PATCH 2/2] release v0.2.5 --- apps/web/package.json | 2 +- .../features/ide-workbench/terminal/terminalTransport.ts | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 21bee1b2..8e71208c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "tracevane-web", - "version": "0.2.4", + "version": "0.2.5", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts b/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts index 5c50ebbb..56f0b0f3 100644 --- a/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts +++ b/apps/web/src/features/ide-workbench/terminal/terminalTransport.ts @@ -3,6 +3,7 @@ import { createTerminalWebSocketUrl, parseTerminalEvent, type CreateWorkbenchTerminalOptions, + type WorkbenchTerminalEvent, } from "./terminalClient"; import { createSseTerminalTransport } from "./terminalSseTransport"; import { diff --git a/package-lock.json b/package-lock.json index e32f4b4c..bfd844e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@binbin/tracevane", - "version": "0.2.4", + "version": "0.2.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@binbin/tracevane", - "version": "0.2.4", + "version": "0.2.5", "license": "MIT", "workspaces": [ "apps/api", @@ -44,11 +44,11 @@ }, "apps/api": { "name": "tracevane-api", - "version": "0.2.4" + "version": "0.2.5" }, "apps/web": { "name": "tracevane-web", - "version": "0.2.4", + "version": "0.2.5", "dependencies": { "@noble/ed25519": "^2.3.0", "@radix-ui/react-dialog": "^1.1.17", diff --git a/package.json b/package.json index c803c459..a283c9ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@binbin/tracevane", - "version": "0.2.4", + "version": "0.2.5", "description": "Tracevane - Local AI Agent Control Workbench", "private": true, "repository": {