Add slash commands for ending and restarting agents (#1034)
* Add slash commands for ending agents * Stabilize slash command e2e submit * Unslop client slash command draft setup * Fix slash commands while agent is running * Fix slash command submit race * Reshape client slash command execution * Fix slash command tab cleanup
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CLIENT_SLASH_COMMANDS,
|
||||
buildDraftAgentSetup,
|
||||
resolveClientSlashCommand,
|
||||
} from "@/client-slash-commands";
|
||||
import type { Agent } from "@/stores/session-store";
|
||||
|
||||
function createAgent(overrides: Partial<Agent> = {}): Agent {
|
||||
const now = new Date("2026-05-15T00:00:00.000Z");
|
||||
return {
|
||||
serverId: "server-1",
|
||||
id: "agent-1",
|
||||
provider: "codex",
|
||||
status: "idle",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastUserMessageAt: now,
|
||||
lastActivityAt: now,
|
||||
capabilities: {
|
||||
supportsStreaming: true,
|
||||
supportsSessionPersistence: true,
|
||||
supportsDynamicModes: true,
|
||||
supportsMcpServers: false,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: true,
|
||||
},
|
||||
currentModeId: "mode-current",
|
||||
availableModes: [],
|
||||
pendingPermissions: [],
|
||||
persistence: null,
|
||||
runtimeInfo: {
|
||||
provider: "codex",
|
||||
sessionId: "session-1",
|
||||
model: "runtime-model",
|
||||
modeId: "runtime-mode",
|
||||
thinkingOptionId: "runtime-thinking",
|
||||
},
|
||||
title: "Agent",
|
||||
cwd: "/repo",
|
||||
model: "agent-model",
|
||||
thinkingOptionId: "think-hard",
|
||||
features: [
|
||||
{ type: "toggle", id: "web-search", label: "Web search", value: true },
|
||||
{
|
||||
type: "select",
|
||||
id: "effort",
|
||||
label: "Effort",
|
||||
value: "high",
|
||||
options: [{ id: "high", label: "High" }],
|
||||
},
|
||||
],
|
||||
parentAgentId: null,
|
||||
labels: {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("resolveClientSlashCommand", () => {
|
||||
it("declares the exact client commands that execute immediately", () => {
|
||||
expect(CLIENT_SLASH_COMMANDS.map((command) => [command.name, command.execution])).toEqual([
|
||||
["quit", "immediate"],
|
||||
["exit", "immediate"],
|
||||
["q", "immediate"],
|
||||
["clear", "immediate"],
|
||||
["new", "immediate"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves exact client commands after trimming", () => {
|
||||
expect(resolveClientSlashCommand({ text: " /quit ", hasAttachments: false })).toMatchObject({
|
||||
kind: "archive-agent",
|
||||
execution: "immediate",
|
||||
});
|
||||
expect(resolveClientSlashCommand({ text: "/exit", hasAttachments: false })?.kind).toBe(
|
||||
"archive-agent",
|
||||
);
|
||||
expect(resolveClientSlashCommand({ text: "/q", hasAttachments: false })?.kind).toBe(
|
||||
"archive-agent",
|
||||
);
|
||||
expect(resolveClientSlashCommand({ text: "/clear", hasAttachments: false })?.kind).toBe(
|
||||
"replace-agent-with-draft",
|
||||
);
|
||||
expect(resolveClientSlashCommand({ text: "/new", hasAttachments: false })?.kind).toBe(
|
||||
"replace-agent-with-draft",
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves provider commands, arguments, ordinary messages, and attachment submits alone", () => {
|
||||
expect(resolveClientSlashCommand({ text: "/clear now", hasAttachments: false })).toBeNull();
|
||||
expect(resolveClientSlashCommand({ text: "/quit now", hasAttachments: false })).toBeNull();
|
||||
expect(
|
||||
resolveClientSlashCommand({ text: "/provider-command", hasAttachments: false }),
|
||||
).toBeNull();
|
||||
expect(resolveClientSlashCommand({ text: "hello /quit", hasAttachments: false })).toBeNull();
|
||||
expect(resolveClientSlashCommand({ text: "/quit", hasAttachments: true })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildDraftAgentSetup", () => {
|
||||
it("builds draft setup from the active agent snapshot", () => {
|
||||
expect(buildDraftAgentSetup(createAgent())).toEqual({
|
||||
provider: "codex",
|
||||
cwd: "/repo",
|
||||
modeId: "mode-current",
|
||||
model: "agent-model",
|
||||
thinkingOptionId: "think-hard",
|
||||
featureValues: {
|
||||
"web-search": true,
|
||||
effort: "high",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to runtime model setup when top-level fields are absent", () => {
|
||||
expect(
|
||||
buildDraftAgentSetup(
|
||||
createAgent({
|
||||
currentModeId: null,
|
||||
model: null,
|
||||
thinkingOptionId: null,
|
||||
}),
|
||||
),
|
||||
).toMatchObject({
|
||||
modeId: "runtime-mode",
|
||||
model: "runtime-model",
|
||||
thinkingOptionId: "runtime-thinking",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user