fix(app): show one /exit row with /quit and /q as aliases

This commit is contained in:
Mohamed Boudra
2026-05-22 18:22:19 +07:00
parent 8e0ebfcaaa
commit f5f1ae7fa9
4 changed files with 46 additions and 46 deletions
@@ -181,7 +181,7 @@ test.describe("Client slash commands", () => {
page, page,
{ title: "Slash quit autocomplete e2e" }, { title: "Slash quit autocomplete e2e" },
async ({ agent, title }) => { async ({ agent, title }) => {
await selectClientSlashCommand(page, "/qu", "/quit"); await selectClientSlashCommand(page, "/qu", "/exit");
await expectWorkspaceTabHidden(page, agent.id); await expectWorkspaceTabHidden(page, agent.id);
await expectAgentArchivedInSessions(page, title); await expectAgentArchivedInSessions(page, title);
}, },
@@ -57,33 +57,41 @@ function createAgent(overrides: Partial<Agent> = {}): Agent {
} }
describe("resolveClientSlashCommand", () => { describe("resolveClientSlashCommand", () => {
it("declares the exact client commands that execute immediately", () => { it("declares the exact canonical client commands with their aliases", () => {
expect(CLIENT_SLASH_COMMANDS.map((command) => [command.name, command.execution])).toEqual([ expect(
["quit", "immediate"], CLIENT_SLASH_COMMANDS.map((command) => [
["exit", "immediate"], command.name,
["q", "immediate"], [...command.aliases],
["clear", "immediate"], command.execution,
["new", "immediate"], ]),
).toEqual([
["exit", ["quit", "q"], "immediate"],
["clear", ["new"], "immediate"],
]); ]);
}); });
it("resolves exact client commands after trimming", () => { it("resolves canonical names and aliases after trimming", () => {
expect(resolveClientSlashCommand({ text: " /quit ", hasAttachments: false })).toMatchObject({ expect(resolveClientSlashCommand({ text: " /quit ", hasAttachments: false })).toMatchObject({
name: "exit",
kind: "archive-agent", kind: "archive-agent",
execution: "immediate", execution: "immediate",
}); });
expect(resolveClientSlashCommand({ text: "/exit", hasAttachments: false })?.kind).toBe( expect(resolveClientSlashCommand({ text: "/exit", hasAttachments: false })).toMatchObject({
"archive-agent", name: "exit",
); kind: "archive-agent",
expect(resolveClientSlashCommand({ text: "/q", hasAttachments: false })?.kind).toBe( });
"archive-agent", expect(resolveClientSlashCommand({ text: "/q", hasAttachments: false })).toMatchObject({
); name: "exit",
expect(resolveClientSlashCommand({ text: "/clear", hasAttachments: false })?.kind).toBe( kind: "archive-agent",
"replace-agent-with-draft", });
); expect(resolveClientSlashCommand({ text: "/clear", hasAttachments: false })).toMatchObject({
expect(resolveClientSlashCommand({ text: "/new", hasAttachments: false })?.kind).toBe( name: "clear",
"replace-agent-with-draft", kind: "replace-agent-with-draft",
); });
expect(resolveClientSlashCommand({ text: "/new", hasAttachments: false })).toMatchObject({
name: "clear",
kind: "replace-agent-with-draft",
});
}); });
it("leaves provider commands, arguments, ordinary messages, and attachment submits alone", () => { it("leaves provider commands, arguments, ordinary messages, and attachment submits alone", () => {
+10 -22
View File
@@ -6,6 +6,7 @@ export type ClientSlashCommandExecution = "immediate" | "insert";
export interface ClientSlashCommand { export interface ClientSlashCommand {
name: string; name: string;
aliases: readonly string[];
description: string; description: string;
argumentHint: string; argumentHint: string;
kind: ClientSlashCommandKind; kind: ClientSlashCommandKind;
@@ -13,22 +14,9 @@ export interface ClientSlashCommand {
} }
export const CLIENT_SLASH_COMMANDS: readonly ClientSlashCommand[] = [ export const CLIENT_SLASH_COMMANDS: readonly ClientSlashCommand[] = [
{
name: "quit",
description: "Archive the current agent",
argumentHint: "",
kind: "archive-agent",
execution: "immediate",
},
{ {
name: "exit", name: "exit",
description: "Archive the current agent", aliases: ["quit", "q"],
argumentHint: "",
kind: "archive-agent",
execution: "immediate",
},
{
name: "q",
description: "Archive the current agent", description: "Archive the current agent",
argumentHint: "", argumentHint: "",
kind: "archive-agent", kind: "archive-agent",
@@ -36,13 +24,7 @@ export const CLIENT_SLASH_COMMANDS: readonly ClientSlashCommand[] = [
}, },
{ {
name: "clear", name: "clear",
description: "Archive this agent and start a fresh draft", aliases: ["new"],
argumentHint: "",
kind: "replace-agent-with-draft",
execution: "immediate",
},
{
name: "new",
description: "Archive this agent and start a fresh draft", description: "Archive this agent and start a fresh draft",
argumentHint: "", argumentHint: "",
kind: "replace-agent-with-draft", kind: "replace-agent-with-draft",
@@ -50,7 +32,13 @@ export const CLIENT_SLASH_COMMANDS: readonly ClientSlashCommand[] = [
}, },
]; ];
const COMMAND_BY_NAME = new Map(CLIENT_SLASH_COMMANDS.map((command) => [command.name, command])); const COMMAND_BY_NAME = new Map<string, ClientSlashCommand>();
for (const command of CLIENT_SLASH_COMMANDS) {
COMMAND_BY_NAME.set(command.name, command);
for (const alias of command.aliases) {
COMMAND_BY_NAME.set(alias, command);
}
}
export function resolveClientSlashCommand(input: { export function resolveClientSlashCommand(input: {
text: string; text: string;
@@ -324,9 +324,13 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
), ),
...providerCommands, ...providerCommands,
]; ];
const matches = availableCommands.filter((entry) => const matches = availableCommands.filter((entry) => {
entry.command.name.toLowerCase().includes(filterLower), if (entry.source === "provider") {
); return entry.command.name.toLowerCase().includes(filterLower);
}
const candidates = [entry.command.name, ...entry.command.aliases];
return candidates.some((candidate) => candidate.toLowerCase().includes(filterLower));
});
const orderedMatches = orderAutocompleteOptions(matches); const orderedMatches = orderAutocompleteOptions(matches);
return orderedMatches.map(mapCommandToOption); return orderedMatches.map(mapCommandToOption);
} }