Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-cats-new.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Add `/new` as an alias of `/clear` in the eve dev TUI. It clears model-message history while preserving the current session and its durable resources.
2 changes: 1 addition & 1 deletion docs/guides/dev-tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Each command echoes as an invocation line, asks through a bordered panel that ta
| `/traces` | Opens the full-screen local trace viewer. See [Inspect traces](#inspect-traces). |
| `/reset` | Starts a fresh session. |
| `/cancel` | Cooperatively cancels the running turn while preserving the session and settled context. |
| `/clear` | Clears model-message history while preserving the current session and its durable resources. |
| `/clear` | Clears model-message history while preserving the current session and its durable resources. `/new` is an alias. |
| `/compact` | Queues context compaction for the current session without sending a message. |
| `/exit` | Quits the TUI. |
| `/help` | Lists the commands available for the current local or remote session. |
Expand Down
6 changes: 5 additions & 1 deletion packages/eve/src/cli/dev/tui/prompt-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ describe("parsePromptCommand", () => {
expect(parsePromptCommand("/cancel")).toEqual({ type: "cancel" });
});

it("parses /new as a context clear", () => {
expect(parsePromptCommand("/new")).toEqual({ type: "clear" });
});

it("parses /exit and its /quit alias", () => {
expect(parsePromptCommand("/exit")).toEqual({ type: "exit" });
expect(parsePromptCommand("/quit")).toEqual({ type: "exit" });
Expand Down Expand Up @@ -80,7 +84,6 @@ describe("parsePromptCommand", () => {
});

it("rejects near-misses and ordinary prompts", () => {
expect(parsePromptCommand("/new")).toBeNull();
expect(parsePromptCommand("/models")).toBeNull();
expect(parsePromptCommand("/vercel")).toBeNull();
expect(parsePromptCommand("/vc")).toBeNull();
Expand Down Expand Up @@ -132,6 +135,7 @@ describe("promptCommandsFor", () => {
describe("isPromptControlCommand", () => {
it("is true exactly for recognized commands", () => {
expect(isPromptControlCommand("/reset")).toBe(true);
expect(isPromptControlCommand("/new")).toBe(true);
expect(isPromptControlCommand("/model gpt-5")).toBe(true);
expect(isPromptControlCommand("/unknown")).toBe(false);
expect(isPromptControlCommand("hello")).toBe(false);
Expand Down
10 changes: 5 additions & 5 deletions packages/eve/src/cli/dev/tui/prompt-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const PROMPT_COMMAND_DEFINITIONS = [
},
{
name: "clear",
aliases: [],
aliases: ["new"],
description: "Clear the current session context",
takesArgument: false,
build: () => ({ type: "clear" }),
Expand Down Expand Up @@ -174,10 +174,10 @@ export function isPromptCommandAvailableFor(

/**
* Recognizes the slash commands the prompt accepts. `/reset` clears the
* session and transcript; `/cancel` stops the running turn; `/clear` clears
* context; `/compact` queues context compaction; `/exit` (and `/quit`)
* terminate the TUI like Ctrl+C; extension commands are dispatched outside
* the runner. Anything else — including unknown `/text` — is a normal
* session and transcript; `/cancel` stops the running turn; `/clear` (and
* `/new`) clears context; `/compact` queues context compaction; `/exit` (and
* `/quit`) terminate the TUI like Ctrl+C; extension commands are dispatched
* outside the runner. Anything else — including unknown `/text` — is a normal
* message.
*/
export function parsePromptCommand(prompt: string): PromptCommand | null {
Expand Down
76 changes: 40 additions & 36 deletions packages/eve/src/cli/dev/tui/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,45 +698,48 @@ describe("EveTUIRunner development session continuity", () => {
expect(results).toEqual(["Compaction requested."]);
});

it("clears the active session context without sending a prompt", async () => {
const session = sessionYielding([]);
const clear = vi
.spyOn(session, "clear")
.mockResolvedValue({ sessionId: "session_1", status: "accepted" });
const stream = vi.spyOn(session, "stream").mockReturnValue(
(async function* () {
yield stampTestEvent(
{
data: { continuationToken: "eve:test", wait: "next-user-message" },
type: "session.waiting",
},
0,
);
})(),
);
const results: string[] = [];
const prompts: Array<string | undefined> = ["/clear", undefined];
const runner = new EveTUIRunner({
name: "Weather Agent",
renderer: fakeRenderer({
readPrompt: vi.fn(async () => prompts.shift()),
renderCommandResult: (message) => results.push(message),
renderStream: vi.fn(async (result) => {
for await (const event of result.events) {
void event;
}
it.each(["/clear", "/new"])(
"%s clears the active session context without sending a prompt",
async (command) => {
const session = sessionYielding([]);
const clear = vi
.spyOn(session, "clear")
.mockResolvedValue({ sessionId: "session_1", status: "accepted" });
const stream = vi.spyOn(session, "stream").mockReturnValue(
(async function* () {
yield stampTestEvent(
{
data: { continuationToken: "eve:test", wait: "next-user-message" },
type: "session.waiting",
},
0,
);
})(),
);
const results: string[] = [];
const prompts: Array<string | undefined> = [command, undefined];
const runner = new EveTUIRunner({
name: "Weather Agent",
renderer: fakeRenderer({
readPrompt: vi.fn(async () => prompts.shift()),
renderCommandResult: (message) => results.push(message),
renderStream: vi.fn(async (result) => {
for await (const event of result.events) {
void event;
}
}),
}),
}),
session,
});
session,
});

await runner.run();
await runner.run();

expect(clear).toHaveBeenCalledOnce();
expect(stream).toHaveBeenCalledOnce();
expect(session.send).not.toHaveBeenCalled();
expect(results).toEqual(["Context clear requested."]);
});
expect(clear).toHaveBeenCalledOnce();
expect(stream).toHaveBeenCalledOnce();
expect(session.send).not.toHaveBeenCalled();
expect(results).toEqual(["Context clear requested."]);
},
);

it("cancels an active turn without sending a prompt", async () => {
const session = stubClient().session({
Expand Down Expand Up @@ -1876,6 +1879,7 @@ describe("EveTUIRunner replay guards", () => {
describe("parsePromptCommand", () => {
it.each([
["/reset", { type: "reset" }],
["/new", { type: "clear" }],
["/exit", { type: "exit" }],
["/quit", { type: "exit" }],
["/deploy", { type: "extension", name: "deploy", argument: "" }],
Expand Down
Loading