fix(tui): preserve slash command semantics

This commit is contained in:
Xubin Ren
2026-08-17 20:56:10 +08:00
parent 2b666934ba
commit b3c3a82075
9 changed files with 349 additions and 57 deletions
+44 -1
View File
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, test } from "bun:test"
import { createTestRenderer, type TestRendererSetup } from "@opentui/core/testing"
import { CommandMenu } from "./command-menu"
import { CommandMenu, resolveSlashCommandLifecycle } from "./command-menu"
import type { SlashCommand } from "./protocol"
const commands: SlashCommand[] = [
@@ -10,6 +10,7 @@ const commands: SlashCommand[] = [
title: "Help",
description: "Show available commands",
argHint: "",
lifecycle: "side_channel",
acceptsArgs: false,
},
{
@@ -17,6 +18,7 @@ const commands: SlashCommand[] = [
title: "History",
description: "Show recent messages",
argHint: "[n]",
lifecycle: "side_channel",
acceptsArgs: true,
},
]
@@ -64,4 +66,45 @@ describe("CommandMenu", () => {
menu.update("/history 5")
expect(menu.visible).toBe(false)
})
test("keeps gateway commands authoritative over local actions", async () => {
setup = await createTestRenderer({ width: 60, height: 12, screenMode: "alternate-screen" })
const menu = new CommandMenu(setup.renderer, {
text: "#FFFFFF",
muted: "#999999",
border: "#555555",
})
menu.setCommands(commands, [{
command: "/help",
title: "Local help",
description: "Must not replace nanobot help",
action: "sessions",
}, {
command: "/sessions",
title: "Sessions",
description: "Switch conversations",
action: "sessions",
}])
expect(menu.resolve("/help")?.source).toBe("gateway")
expect(menu.resolve("/sessions")).toMatchObject({
source: "tui",
command: { action: "sessions" },
})
})
test("resolves argument-sensitive command lifecycles", () => {
const goal: SlashCommand = {
command: "/goal",
title: "Goal",
description: "Start a long-running goal",
argHint: "<goal>",
lifecycle: "agent_turn_with_args",
acceptsArgs: true,
}
expect(resolveSlashCommandLifecycle("/goal", goal)).toBe("side_channel")
expect(resolveSlashCommandLifecycle("/goal ship it", goal)).toBe("agent_turn")
expect(resolveSlashCommandLifecycle("/help extra", commands[0]!)).toBeNull()
})
})