fix: prevent agent hatch failure after gateway startup (#122210)

* fix(system-agent): attach hatch TUI to running gateway

* test(system-agent): isolate TUI gateway state

---------

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
This commit is contained in:
Sedrak Hovhannisyan
2026-08-12 22:19:17 +04:00
committed by GitHub
parent 16540e2b88
commit 2ce420091e
3 changed files with 59 additions and 12 deletions
+7 -3
View File
@@ -502,15 +502,19 @@ export async function executeSystemAgentOperation(
},
});
case "open-tui": {
const agentId = await resolveTuiAgentId({
const overview = await loadOverviewForOperation(opts.deps);
const agentId = resolveTuiAgentId({
requestedAgentId: operation.agentId,
requestedWorkspace: operation.workspace,
deps: opts.deps,
overview,
});
const session = agentId ? buildAgentMainSessionKey({ agentId }) : undefined;
const runTui = opts.deps?.runTui ?? (await import("../tui/tui.js")).runTui;
// A reachable Gateway owns the state lock, so embedded mode would fail during hatch.
// Keep embedded mode only as the no-Gateway fallback for standalone sessions.
const useEmbeddedTui = !overview.gateway.reachable;
const result = await runTui({
local: true,
local: useEmbeddedTui,
session,
deliver: false,
historyLimit: 200,
@@ -187,12 +187,12 @@ export function createNoExitRuntime(runtime: RuntimeEnv): RuntimeEnv {
};
}
export async function resolveTuiAgentId(params: {
export function resolveTuiAgentId(params: {
requestedAgentId: string | undefined;
requestedWorkspace?: string;
deps?: SystemAgentCommandDeps;
}): Promise<string | undefined> {
const overview = await loadOverviewForOperation(params.deps);
overview: SystemAgentOverview;
}): string | undefined {
const { overview } = params;
const workspace = params.requestedWorkspace
? resolveUserPath(params.requestedWorkspace)
: undefined;
+48 -5
View File
@@ -4,8 +4,35 @@ import path from "node:path";
import { withTempHome } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it, vi } from "vitest";
import { executeSystemAgentOperation, isPersistentSystemAgentOperation } from "./operations.js";
import type { SystemAgentOverview } from "./overview.js";
import { createSystemAgentTestRuntime } from "./system-agent.runtime.test-support.js";
function createOverview(gatewayReachable: boolean): SystemAgentOverview {
return {
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
agents: [
{ id: "main", isDefault: true },
{ id: "work", isDefault: false },
],
defaultAgentId: "main",
tools: {
codex: { command: "codex", found: false },
claude: { command: "claude", found: false },
gemini: { command: "gemini", found: false },
apiKeys: { openai: false, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "test",
reachable: gatewayReachable,
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
};
}
describe("system-agent TUI operations", () => {
it("refuses doctor repairs before any write or audit", async () => {
await withTempHome(async (home) => {
@@ -39,7 +66,7 @@ describe("system-agent TUI operations", () => {
const result = await executeSystemAgentOperation(
{ kind: "open-tui", agentId: "work" },
runtime,
{ deps: { runTui } },
{ deps: { runTui, loadOverview: async () => createOverview(false) } },
);
expect(runTui).toHaveBeenCalledWith({
@@ -58,22 +85,38 @@ describe("system-agent TUI operations", () => {
);
});
it("seeds a fresh hatch into the agent TUI", async () => {
it("connects a fresh hatch to the reachable Gateway", async () => {
const { runtime } = createSystemAgentTestRuntime();
const runTui = vi.fn(async () => ({ exitReason: "exit" as const }));
await executeSystemAgentOperation(
{ kind: "open-tui", agentId: "work", agentDraft: "hatch" },
runtime,
{ deps: { runTui } },
{ deps: { runTui, loadOverview: async () => createOverview(true) } },
);
expect(runTui).toHaveBeenCalledWith({
local: false,
session: "agent:work:main",
deliver: false,
historyLimit: 200,
message: "Wake up, my friend!",
});
});
it("keeps the embedded TUI fallback when the Gateway is unreachable", async () => {
const { runtime } = createSystemAgentTestRuntime();
const runTui = vi.fn(async () => ({ exitReason: "exit" as const }));
await executeSystemAgentOperation({ kind: "open-tui", agentId: "work" }, runtime, {
deps: { runTui, loadOverview: async () => createOverview(false) },
});
expect(runTui).toHaveBeenCalledWith({
local: true,
session: "agent:work:main",
deliver: false,
historyLimit: 200,
message: "Wake up, my friend!",
});
});
@@ -84,7 +127,7 @@ describe("system-agent TUI operations", () => {
}));
const result = await executeSystemAgentOperation({ kind: "open-tui" }, runtime, {
deps: { runTui },
deps: { runTui, loadOverview: async () => createOverview(false) },
});
expect(result).toMatchObject({