Skip to content

Commit 1c2945d

Browse files
authored
Merge branch 'Kilo-Org:main' into virtual-quota-provider
2 parents ffd7ada + dd48ba4 commit 1c2945d

File tree

76 files changed

+1200
-17559
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1200
-17559
lines changed

.changeset/bright-hornets-bow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": minor
3+
---
4+
5+
Switched autocomplete to showing completions inline

.changeset/green-teams-pull.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/hip-lands-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
Fix broken sign-in links

apps/kilocode-docs/docs/seats/custom-modes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Custom Modes let you create tailored versions of Kilo’s built-in [modes](/basi
88

99
For example, Admins and Owners can extend these by creating **Custom Modes** with specialized roles or personalities (e.g. “Documentation Writer” or “Security Reviewer”).
1010

11-
<img width="900" height="925" alt="Custom Modes Tab" src="https://github.com/user-attachments/assets/183a6d7e-c072-4774-be5b-7653a3fe93fb" />
11+
![Create a new custom mode tab.](/img/teams/custom_modes.png)
1212

1313
## Creating a Custom Mode
1414

250 KB
Loading

apps/playwright-e2e/helpers/webview-helpers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ export async function clickSaveSettingsButton(webviewFrame: FrameLocator): Promi
100100
}
101101
}
102102

103+
/**
104+
* Waits for all tooltips to be dismissed before proceeding.
105+
* This is necessary when tooltips appear after clicking elements and need to animate away
106+
* before taking screenshots to avoid inconsistent visual states.
107+
*/
108+
export async function waitForTooltipsToDismiss(webviewFrame: FrameLocator): Promise<void> {
109+
const tooltipContent = webviewFrame.locator('[data-slot="tooltip-content"]')
110+
await tooltipContent.waitFor({ state: "detached", timeout: 3000 }).catch(() => {
111+
// If timeout, tooltips should be gone by now anyway
112+
})
113+
}
114+
103115
/**
104116
* Freezes all GIFs on the page by converting them to static PNG images.
105117
* Also sets up a MutationObserver to handle dynamically added GIFs.

apps/playwright-e2e/tests/chat.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test.describe("E2E Chat Test", () => {
1717
await waitForWebviewText(page, "Generate, refactor, and debug code with AI assistance")
1818

1919
await (await getChatInput(page)).focus()
20-
await page.waitForTimeout(1000) // Let the page settle to avoid flakes
20+
await page.waitForTimeout(3000) // Let the page settle to avoid flakes
2121
await takeScreenshot("ready-to-chat")
2222

2323
// Don't take any more screenshots after the reponse starts-

apps/playwright-e2e/tests/settings.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { test, expect, type TestFixtures } from "./playwright-base-test"
2-
import { findWebview, upsertApiConfiguration, verifyExtensionInstalled, clickSaveSettingsButton } from "../helpers"
2+
import {
3+
findWebview,
4+
upsertApiConfiguration,
5+
verifyExtensionInstalled,
6+
clickSaveSettingsButton,
7+
waitForTooltipsToDismiss,
8+
} from "../helpers"
39

410
test.describe("Settings", () => {
511
test("screenshots", async ({ workbox: page, takeScreenshot }: TestFixtures) => {
@@ -8,9 +14,10 @@ test.describe("Settings", () => {
814

915
// Open the settings then move the mouse to avoid triggering the tooltip
1016
const webviewFrame = await findWebview(page)
11-
page.locator('[aria-label*="Settings"], [title*="Settings"]').first().click()
12-
await clickSaveSettingsButton(webviewFrame)
17+
await page.locator('[aria-label*="Settings"], [title*="Settings"]').first().click()
18+
await page.mouse.move(0, 0) // Move cursor to top-left corner to avoid triggering hover states
1319

20+
await clickSaveSettingsButton(webviewFrame)
1421
await expect(webviewFrame.locator('[role="tablist"]')).toBeVisible({ timeout: 10000 })
1522
console.log("✅ Settings view loaded")
1623

@@ -35,6 +42,7 @@ test.describe("Settings", () => {
3542
const sectionId = testId?.replace("tab-", "") || `section-${i}`
3643

3744
await clickSaveSettingsButton(webviewFrame) // To avoid flakey screenshots
45+
await waitForTooltipsToDismiss(webviewFrame) // Wait for tooltips to dismiss before screenshot
3846
await takeScreenshot(`${i}-settings-${sectionId}-${tabName.toLowerCase().replace(/\s+/g, "-")}`)
3947
}
4048

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Tests for approval delay functionality
3+
*/
4+
5+
import { describe, it, expect, afterEach } from "vitest"
6+
import { createStore } from "jotai"
7+
import {
8+
setPendingApprovalAtom,
9+
isApprovalPendingAtom,
10+
approvalSetTimestampAtom,
11+
clearPendingApprovalAtom,
12+
} from "../approval.js"
13+
import type { ExtensionChatMessage } from "../../../types/messages.js"
14+
15+
describe("approval delay", () => {
16+
afterEach(() => {
17+
// Cleanup if needed
18+
})
19+
20+
it("should set timestamp when pending approval is set", () => {
21+
const store = createStore()
22+
const message: ExtensionChatMessage = {
23+
ts: Date.now(),
24+
type: "ask",
25+
ask: "tool",
26+
text: '{"tool":"readFile"}',
27+
}
28+
29+
store.set(setPendingApprovalAtom, message)
30+
31+
const timestamp = store.get(approvalSetTimestampAtom)
32+
expect(timestamp).not.toBeNull()
33+
expect(typeof timestamp).toBe("number")
34+
})
35+
36+
it("should clear timestamp when pending approval is cleared", () => {
37+
const store = createStore()
38+
const message: ExtensionChatMessage = {
39+
ts: Date.now(),
40+
type: "ask",
41+
ask: "tool",
42+
text: '{"tool":"readFile"}',
43+
}
44+
45+
store.set(setPendingApprovalAtom, message)
46+
expect(store.get(approvalSetTimestampAtom)).not.toBeNull()
47+
48+
store.set(clearPendingApprovalAtom)
49+
expect(store.get(approvalSetTimestampAtom)).toBeNull()
50+
})
51+
52+
it("isApprovalPendingAtom should return true immediately", () => {
53+
const store = createStore()
54+
const message: ExtensionChatMessage = {
55+
ts: Date.now(),
56+
type: "ask",
57+
ask: "tool",
58+
text: '{"tool":"readFile"}',
59+
}
60+
61+
store.set(setPendingApprovalAtom, message)
62+
63+
// Immediate check should return true
64+
expect(store.get(isApprovalPendingAtom)).toBe(true)
65+
})
66+
67+
it("should handle timestamp and approval state together", () => {
68+
const store = createStore()
69+
const message: ExtensionChatMessage = {
70+
ts: Date.now(),
71+
type: "ask",
72+
ask: "tool",
73+
text: '{"tool":"readFile"}',
74+
}
75+
76+
store.set(setPendingApprovalAtom, message)
77+
78+
// Both timestamp and approval should be set
79+
expect(store.get(approvalSetTimestampAtom)).not.toBeNull()
80+
expect(store.get(isApprovalPendingAtom)).toBe(true)
81+
82+
// Clear approval
83+
store.set(clearPendingApprovalAtom)
84+
85+
// Both should be cleared
86+
expect(store.get(approvalSetTimestampAtom)).toBeNull()
87+
expect(store.get(isApprovalPendingAtom)).toBe(false)
88+
})
89+
})

cli/src/state/atoms/approval.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export const approvalProcessingAtom = atom<ApprovalProcessingState>({
4646
*/
4747
export const selectedApprovalIndexAtom = selectedIndexAtom
4848

49+
/**
50+
* Atom to track when approval was set (for delay logic)
51+
*/
52+
export const approvalSetTimestampAtom = atom<number | null>(null)
53+
4954
/**
5055
* Derived atom to check if there's a pending approval
5156
*/
@@ -226,6 +231,11 @@ export const setPendingApprovalAtom = atom(null, (get, set, message: ExtensionCh
226231
const messageToSet = message ? { ...message } : null
227232
set(pendingApprovalAtom, messageToSet)
228233

234+
// Set timestamp for delay tracking when setting a new message
235+
if (isNewMessage && messageToSet !== null) {
236+
set(approvalSetTimestampAtom, Date.now())
237+
}
238+
229239
// Reset selection if this is a new message (different timestamp)
230240
if (isNewMessage) {
231241
set(selectedIndexAtom, 0)
@@ -241,6 +251,7 @@ export const clearPendingApprovalAtom = atom(null, (get, set) => {
241251
const processing = get(approvalProcessingAtom)
242252

243253
set(pendingApprovalAtom, null)
254+
set(approvalSetTimestampAtom, null)
244255

245256
// Also clear processing state if it matches
246257
if (processing.isProcessing && processing.processingTs === current?.ts) {
@@ -284,6 +295,7 @@ export const startApprovalProcessingAtom = atom(null, (get, set, operation: "app
284295
*/
285296
export const completeApprovalProcessingAtom = atom(null, (get, set) => {
286297
set(pendingApprovalAtom, null)
298+
set(approvalSetTimestampAtom, null)
287299
set(selectedIndexAtom, 0)
288300
set(approvalProcessingAtom, { isProcessing: false })
289301
})

0 commit comments

Comments
 (0)