fix: connect resumed native terminals reliably (#107688)

* fix: render resumed paired-node terminals

* fix(control-ui): connect native catalog terminals reliably

* chore: keep release notes in PR body

* fix(control-ui): require terminal reconnect cancellation

* fix(gateway): cancel timed-out terminal opens safely

* fix(control-ui): recover stalled terminal opens

* chore(ui): refresh i18n fallback baseline

* fix: preserve login shell path for resumed terminals

* refactor(plugin-sdk): avoid extra resolver export

* fix(gateway): normalize terminal open failures

* test(control-ui): split terminal readiness coverage

* docs: explain catalog terminal PATH restoration

* fix(control-ui): complete terminal readiness release gates

* fix(ci): remove unused shell resolver exports

* chore(plugin-sdk): refresh API baseline after rebase
This commit is contained in:
Peter Steinberger
2026-07-14 19:18:19 -07:00
committed by GitHub
parent 576c209110
commit 57d926b4fd
97 changed files with 1931 additions and 474 deletions
@@ -1,6 +1,7 @@
import { accessSync, constants, statSync } from "node:fs";
import path from "node:path";
import process from "node:process";
import { resolveExecutableFromPathEnv } from "openclaw/plugin-sdk/node-host";
import type {
OpenClawPluginApi,
OpenClawPluginNodeHostCommand,
@@ -317,7 +318,12 @@ async function listOpenCodeHosts(
const requested = query.hostIds ? new Set(query.hostIds) : undefined;
const searchTerm = query.search?.trim().slice(0, MAX_SEARCH_LENGTH) || undefined;
const hosts: SessionCatalogHost[] = [];
if ((!requested || requested.has(LOCAL_HOST_ID)) && executableOnPath("opencode", process.env)) {
if (
(!requested || requested.has(LOCAL_HOST_ID)) &&
resolveExecutableFromPathEnv("opencode", process.env.PATH ?? "", process.env, {
fallbackToLoginShell: true,
})
) {
try {
hosts.push({
hostId: LOCAL_HOST_ID,
@@ -112,14 +112,20 @@ export async function openOpenCodeCatalogTerminal(
const title = `opencode --session ${params.threadId.slice(0, 12)}`;
if (params.hostId === OPENCODE_LOCAL_SESSION_HOST_ID) {
const record = await requireLocalOpenCodeSession(params.threadId);
const executable = resolveExecutableFromPathEnv("opencode", process.env.PATH ?? "");
if (!executable) {
const resolution = resolveExecutableFromPathEnv(
"opencode",
process.env.PATH ?? "",
process.env,
{ fallbackToLoginShell: true, withPathEnv: true },
);
if (!resolution) {
throw new Error("OpenCode CLI is unavailable");
}
return {
kind: "local",
argv: [executable, "--session", params.threadId],
argv: [resolution.executable, "--session", params.threadId],
...(record.cwd ? { cwd: record.cwd } : {}),
...(resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {}),
title,
};
}
+13 -1
View File
@@ -10,7 +10,19 @@ const nodeHostMocks = vi.hoisted(() => ({
vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/node-host")>();
return { ...actual, runNodePtyCommand: nodeHostMocks.runNodePtyCommand };
return {
...actual,
runNodePtyCommand: nodeHostMocks.runNodePtyCommand,
resolveExecutableFromPathEnv: (
command: string,
pathEnv: string,
env?: NodeJS.ProcessEnv,
options?: { withPathEnv?: boolean },
) =>
options?.withPathEnv
? actual.resolveExecutableFromPathEnv(command, pathEnv, env, { withPathEnv: true })
: actual.resolveExecutableFromPathEnv(command, pathEnv, env),
};
});
import {