fix(tui): align /status with shared session status

This commit is contained in:
Vincent Koc
2026-04-06 14:10:34 +01:00
parent 191b7cb5e6
commit 18ed43cc9e
6 changed files with 59 additions and 5 deletions

View File

@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
- Plugins/Windows: disable native Jiti loading for setup and doctor contract registries on Windows so onboarding and config-doctor plugin probes stop crashing with `ERR_UNSUPPORTED_ESM_URL_SCHEME`. (#61836, #61853)
- Agents/context overflow: combine oversized and aggregate tool-result recovery in one repair pass, and restore a total-context overflow backstop during tool loops so recoverable sessions retry instead of failing early. (#61651) Thanks @Takhoffman.
- Gateway/containers: auto-bind to `0.0.0.0` during container startup for Docker and Podman compatibility, while keeping host-side status and doctor checks on the hardened loopback default when `gateway.bind` is unset. (#61818) Thanks @openperf.
- TUI/status: route `/status` through the shared session-status command and move the old gateway-wide diagnostic summary to `/gateway-status` (`/gwstatus`). Thanks @vincentkoc.
## 2026.4.5
### Breaking

View File

@@ -6,6 +6,10 @@ describe("parseCommand", () => {
expect(parseCommand("/elev full")).toEqual({ name: "elevated", args: "full" });
});
it("normalizes gateway-status aliases", () => {
expect(parseCommand("/gwstatus")).toEqual({ name: "gateway-status", args: "" });
});
it("returns empty name for empty input", () => {
expect(parseCommand(" ")).toEqual({ name: "", args: "" });
});
@@ -24,6 +28,14 @@ describe("getSlashCommands", () => {
{ value: "always", label: "always" },
]);
});
it("keeps session status on the shared command path and exposes gateway status separately", () => {
const commands = getSlashCommands();
const status = commands.find((command) => command.name === "status");
const gatewayStatus = commands.find((command) => command.name === "gateway-status");
expect(status?.description).toBe("Show current status.");
expect(gatewayStatus?.description).toBe("Show gateway status summary");
});
});
describe("helpText", () => {
@@ -31,5 +43,7 @@ describe("helpText", () => {
const output = helpText();
expect(output).toContain("/elevated <on|off|ask|full>");
expect(output).toContain("/elev <on|off|ask|full>");
expect(output).toContain("/gateway-status");
expect(output).toContain("/gwstatus");
});
});

View File

@@ -23,6 +23,7 @@ export type SlashCommandOptions = {
const COMMAND_ALIASES: Record<string, string> = {
elev: "elevated",
gwstatus: "gateway-status",
};
function createLevelCompletion(
@@ -60,7 +61,8 @@ export function getSlashCommands(options: SlashCommandOptions = {}): SlashComman
const activationCompletions = createLevelCompletion(ACTIVATION_LEVELS);
const commands: SlashCommand[] = [
{ name: "help", description: "Show slash command help" },
{ name: "status", description: "Show gateway status summary" },
{ name: "gateway-status", description: "Show gateway status summary" },
{ name: "gwstatus", description: "Alias for /gateway-status" },
{ name: "agent", description: "Switch agent (or open picker)" },
{ name: "agents", description: "Open agent picker" },
{ name: "session", description: "Switch session (or open picker)" },
@@ -145,6 +147,8 @@ export function helpText(options: SlashCommandOptions = {}): string {
"/help",
"/commands",
"/status",
"/gateway-status",
"/gwstatus",
"/agent <id> (or /agents)",
"/session <key> (or /sessions)",
"/model <provider/model> (or /models)",

View File

@@ -251,7 +251,7 @@ export class GatewayChatClient {
});
}
async getStatus() {
async getGatewayStatus() {
return await this.client.request("status");
}

View File

@@ -7,6 +7,7 @@ type SetSessionMock = ReturnType<typeof vi.fn> & ((key: string) => Promise<void>
function createHarness(params?: {
sendChat?: ReturnType<typeof vi.fn>;
getGatewayStatus?: ReturnType<typeof vi.fn>;
patchSession?: ReturnType<typeof vi.fn>;
resetSession?: ReturnType<typeof vi.fn>;
setSession?: SetSessionMock;
@@ -18,6 +19,7 @@ function createHarness(params?: {
activeChatRunId?: string | null;
}) {
const sendChat = params?.sendChat ?? vi.fn().mockResolvedValue({ runId: "r1" });
const getGatewayStatus = params?.getGatewayStatus ?? vi.fn().mockResolvedValue({});
const patchSession = params?.patchSession ?? vi.fn().mockResolvedValue({});
const resetSession = params?.resetSession ?? vi.fn().mockResolvedValue({ ok: true });
const setSession = params?.setSession ?? (vi.fn().mockResolvedValue(undefined) as SetSessionMock);
@@ -40,7 +42,7 @@ function createHarness(params?: {
};
const { handleCommand } = createCommandHandlers({
client: { sendChat, patchSession, resetSession } as never,
client: { sendChat, getGatewayStatus, patchSession, resetSession } as never,
chatLog: { addUser, addSystem } as never,
tui: { requestRender } as never,
opts: {},
@@ -65,6 +67,7 @@ function createHarness(params?: {
return {
handleCommand,
getGatewayStatus,
sendChat,
patchSession,
resetSession,
@@ -127,6 +130,38 @@ describe("tui command handlers", () => {
expect(requestRender).toHaveBeenCalled();
});
it("forwards /status to the shared gateway command path", async () => {
const { handleCommand, sendChat, addUser, addSystem } = createHarness();
await handleCommand("/status");
expect(addSystem).not.toHaveBeenCalled();
expect(addUser).toHaveBeenCalledWith("/status");
expect(sendChat).toHaveBeenCalledWith(
expect.objectContaining({
sessionKey: "agent:main:main",
message: "/status",
}),
);
});
it("keeps gateway diagnostics on /gateway-status", async () => {
const { handleCommand, getGatewayStatus, addSystem, addUser, sendChat } = createHarness({
getGatewayStatus: vi.fn().mockResolvedValue({
runtimeVersion: "1.2.3",
sessions: { count: 2, defaults: { model: "gpt-5.4", contextTokens: 200000 } },
}),
});
await handleCommand("/gateway-status");
expect(getGatewayStatus).toHaveBeenCalledTimes(1);
expect(addUser).not.toHaveBeenCalled();
expect(sendChat).not.toHaveBeenCalled();
expect(addSystem).toHaveBeenCalledWith("Gateway status");
expect(addSystem).toHaveBeenCalledWith("Version: 1.2.3");
});
it("defers local run binding until gateway events provide a real run id", async () => {
const { handleCommand, noteLocalRunId, state } = createHarness();

View File

@@ -255,9 +255,9 @@ export function createCommandHandlers(context: CommandHandlerContext) {
}),
);
break;
case "status":
case "gateway-status":
try {
const status = await client.getStatus();
const status = await client.getGatewayStatus();
if (typeof status === "string") {
chatLog.addSystem(status);
break;