Skip to content

Commit a1adf0c

Browse files
authored
Merge branch 'Kilo-Org:main' into virtual-quota-provider
2 parents 8b8f3d9 + 3673b22 commit a1adf0c

24 files changed

+1150
-265
lines changed

.changeset/forty-laws-argue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@kilocode/cli": patch
3+
---
4+
5+
Improved stability of the approval menu, preventing it from showing when you don't expect it

.changeset/tender-news-sin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@kilocode/cli": patch
3+
---
4+
5+
'Added /clear command'
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* Tests for the /clear command
3+
*/
4+
5+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
6+
import { clearCommand } from "../clear.js"
7+
import type { CommandContext } from "../core/types.js"
8+
9+
describe("clearCommand", () => {
10+
let mockContext: CommandContext
11+
12+
beforeEach(() => {
13+
mockContext = {
14+
input: "/clear",
15+
args: [],
16+
options: {},
17+
sendMessage: vi.fn().mockResolvedValue(undefined),
18+
addMessage: vi.fn(),
19+
clearMessages: vi.fn(),
20+
replaceMessages: vi.fn(),
21+
setMessageCutoffTimestamp: vi.fn(),
22+
clearTask: vi.fn().mockResolvedValue(undefined),
23+
setMode: vi.fn(),
24+
exit: vi.fn(),
25+
routerModels: null,
26+
currentProvider: null,
27+
kilocodeDefaultModel: "",
28+
updateProviderModel: vi.fn().mockResolvedValue(undefined),
29+
refreshRouterModels: vi.fn().mockResolvedValue(undefined),
30+
updateProvider: vi.fn().mockResolvedValue(undefined),
31+
profileData: null,
32+
balanceData: null,
33+
profileLoading: false,
34+
balanceLoading: false,
35+
}
36+
})
37+
38+
describe("command metadata", () => {
39+
it("should have correct name", () => {
40+
expect(clearCommand.name).toBe("clear")
41+
})
42+
43+
it("should have correct aliases", () => {
44+
expect(clearCommand.aliases).toEqual(["c", "cls"])
45+
})
46+
47+
it("should have correct category", () => {
48+
expect(clearCommand.category).toBe("system")
49+
})
50+
51+
it("should have correct priority", () => {
52+
expect(clearCommand.priority).toBe(8)
53+
})
54+
55+
it("should have description", () => {
56+
expect(clearCommand.description).toBeTruthy()
57+
expect(clearCommand.description).toContain("display")
58+
})
59+
60+
it("should have usage examples", () => {
61+
expect(clearCommand.examples).toHaveLength(3)
62+
expect(clearCommand.examples).toContain("/clear")
63+
expect(clearCommand.examples).toContain("/c")
64+
expect(clearCommand.examples).toContain("/cls")
65+
})
66+
})
67+
68+
describe("handler", () => {
69+
it("should set message cutoff timestamp", async () => {
70+
const beforeTime = Date.now()
71+
await clearCommand.handler(mockContext)
72+
const afterTime = Date.now()
73+
74+
expect(mockContext.setMessageCutoffTimestamp).toHaveBeenCalledTimes(1)
75+
const timestamp = (mockContext.setMessageCutoffTimestamp as any).mock.calls[0][0]
76+
expect(timestamp).toBeGreaterThanOrEqual(beforeTime)
77+
expect(timestamp).toBeLessThanOrEqual(afterTime)
78+
})
79+
80+
it("should NOT replace messages", async () => {
81+
await clearCommand.handler(mockContext)
82+
83+
expect(mockContext.replaceMessages).not.toHaveBeenCalled()
84+
})
85+
86+
it("should NOT call clearTask (unlike /new command)", async () => {
87+
await clearCommand.handler(mockContext)
88+
89+
expect(mockContext.clearTask).not.toHaveBeenCalled()
90+
})
91+
92+
it("should NOT call clearMessages", async () => {
93+
await clearCommand.handler(mockContext)
94+
95+
expect(mockContext.clearMessages).not.toHaveBeenCalled()
96+
})
97+
98+
it("should execute without errors", async () => {
99+
await expect(clearCommand.handler(mockContext)).resolves.not.toThrow()
100+
})
101+
})
102+
103+
describe("comparison with /new command", () => {
104+
it("should have different behavior than /new", async () => {
105+
// /clear should:
106+
// 1. Set cutoff timestamp (hide messages)
107+
// 2. NOT replace messages
108+
// 3. NOT clear extension task state
109+
// 4. NOT write escape sequences to stdout (to avoid Ink conflicts)
110+
111+
await clearCommand.handler(mockContext)
112+
113+
// Verify /clear behavior
114+
expect(mockContext.setMessageCutoffTimestamp).toHaveBeenCalled()
115+
expect(mockContext.replaceMessages).not.toHaveBeenCalled()
116+
expect(mockContext.clearTask).not.toHaveBeenCalled()
117+
})
118+
119+
it("should not write directly to stdout to avoid Ink conflicts", async () => {
120+
// Mock stdout.write to verify it's not called
121+
const stdoutWriteSpy = vi.spyOn(process.stdout, "write")
122+
123+
await clearCommand.handler(mockContext)
124+
125+
// Verify stdout.write was NOT called
126+
// This prevents yoga-layout crashes when Ink is managing the terminal
127+
expect(stdoutWriteSpy).not.toHaveBeenCalled()
128+
129+
stdoutWriteSpy.mockRestore()
130+
})
131+
})
132+
})

cli/src/commands/clear.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* /clear command - Clear the display without affecting extension state
3+
*/
4+
5+
import type { Command } from "./core/types.js"
6+
7+
export const clearCommand: Command = {
8+
name: "clear",
9+
aliases: ["c", "cls"],
10+
description: "Clear the display without affecting the current task",
11+
usage: "/clear",
12+
examples: ["/clear", "/c", "/cls"],
13+
category: "system",
14+
priority: 8,
15+
handler: async (context) => {
16+
const { setMessageCutoffTimestamp, addMessage } = context
17+
const now = Date.now()
18+
setMessageCutoffTimestamp(now)
19+
// Add Spacer message
20+
addMessage({
21+
id: `empty-${now + 1}`,
22+
type: "empty",
23+
content: "",
24+
ts: now + 1,
25+
})
26+
},
27+
}

cli/src/commands/core/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface CommandContext {
3636
addMessage: (message: any) => void
3737
clearMessages: () => void
3838
replaceMessages: (messages: any[]) => void
39+
setMessageCutoffTimestamp: (timestamp: number) => void
3940
clearTask: () => Promise<void>
4041
setMode: (mode: string) => void
4142
exit: () => void

cli/src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { commandRegistry } from "./core/registry.js"
99

1010
import { helpCommand } from "./help.js"
1111
import { newCommand } from "./new.js"
12+
import { clearCommand } from "./clear.js"
1213
import { exitCommand } from "./exit.js"
1314
import { modeCommand } from "./mode.js"
1415
import { modelCommand } from "./model.js"
@@ -22,6 +23,7 @@ export function initializeCommands(): void {
2223
// Register all commands
2324
commandRegistry.register(helpCommand)
2425
commandRegistry.register(newCommand)
26+
commandRegistry.register(clearCommand)
2527
commandRegistry.register(exitCommand)
2628
commandRegistry.register(modeCommand)
2729
commandRegistry.register(modelCommand)

0 commit comments

Comments
 (0)