diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 69a9e8d249..6b527ef777 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,7 +7,7 @@ contact_links: url: https://github.com/MatterAIOrg/AxonCode/discussions/categories/2-design-improvements about: Suggestions for better design (where the current UI/UX is not clear). - name: Leave a Review - url: https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code&ssr=false#review-details + url: https://marketplace.visualstudio.com/items?itemName=matterai.axon-code&ssr=false#review-details about: Enjoying Axon Code? Leave a review here! - name: Join our Discord url: https://discord.gg/fJU5DvanU3 diff --git a/apps/kilocode-docs/.kilocode/rules/memory-bank/tech.md b/apps/kilocode-docs/.kilocode/rules/memory-bank/tech.md index c1ad17360d..c2a8bf17e0 100644 --- a/apps/kilocode-docs/.kilocode/rules/memory-bank/tech.md +++ b/apps/kilocode-docs/.kilocode/rules/memory-bank/tech.md @@ -110,7 +110,7 @@ npx docusaurus build ### VS Code Marketplace -- **Extension URL**: https://marketplace.visualstudio.com/items?itemName=kilocode.kilo-code +- **Extension URL**: https://marketplace.visualstudio.com/items?itemName=matterai.axon-code - **Open VSX**: https://open-vsx.org/extension/kilocode/kilo-code ## Deployment Architecture diff --git a/apps/kilocode-docs/docs/getting-started/installing.md b/apps/kilocode-docs/docs/getting-started/installing.md index bc52553004..11657fafa5 100644 --- a/apps/kilocode-docs/docs/getting-started/installing.md +++ b/apps/kilocode-docs/docs/getting-started/installing.md @@ -15,7 +15,7 @@ Axon Code is a VS Code extension that brings AI-powered coding assistance direct :::tip -If you already have VS Code installed: [Click here to install Axon Code](vscode:extension/kilocode.Kilo-Code) +If you already have VS Code installed: [Click here to install Axon Code](vscode:extension/matterai.axon-code) ::: @@ -36,7 +36,7 @@ After installation, find the Axon Code icon ( { const webviewFrameEl = workbox.frameLocator( - 'iframe[src*="extensionId=kilocode.kilo-code"][src*="purpose=webviewView"]', + 'iframe[src*="extensionId=matterai.axon-code"][src*="purpose=webviewView"]', ) await webviewFrameEl.locator("#active-frame") return webviewFrameEl.frameLocator("#active-frame") diff --git a/apps/playwright-e2e/tests/playwright-base-test.ts b/apps/playwright-e2e/tests/playwright-base-test.ts index 52c958ed45..cfc3498a5c 100644 --- a/apps/playwright-e2e/tests/playwright-base-test.ts +++ b/apps/playwright-e2e/tests/playwright-base-test.ts @@ -69,7 +69,7 @@ export const test = base.extend({ "--disable-crash-reporter", "--enable-logging", "--log-level=0", - "--disable-extensions-except=kilocode.kilo-code", + "--disable-extensions-except=matterai.axon-code", "--disable-extension-recommendations", "--disable-extension-update-check", "--disable-default-apps", @@ -79,7 +79,7 @@ export const test = base.extend({ `--extensionDevelopmentPath=${path.resolve(__dirname, "..", "..", "..", "src")}`, `--extensions-dir=${path.join(defaultCachePath, "extensions")}`, `--user-data-dir=${userDataDir}`, - "--enable-proposed-api=kilocode.kilo-code", + "--enable-proposed-api=matterai.axon-code", await createProject(), ], }) diff --git a/apps/vscode-e2e/.vscode-test.mjs b/apps/vscode-e2e/.vscode-test.mjs index 42149d118b..0a3d99b460 100644 --- a/apps/vscode-e2e/.vscode-test.mjs +++ b/apps/vscode-e2e/.vscode-test.mjs @@ -12,5 +12,5 @@ export default defineConfig({ ui: "tdd", timeout: 60000, }, - launchArgs: ["--enable-proposed-api=kilocode.Kilo-Code", "--disable-extensions"], + launchArgs: ["--enable-proposed-api=matterai.axon-code", "--disable-extensions"], }) diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index 4268de7b4a..ce131d8fbb 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -8,7 +8,7 @@ import type { RooCodeAPI } from "@roo-code/types" import { waitFor } from "./utils" export async function run() { - const extension = vscode.extensions.getExtension("kilocode.kilo-code") + const extension = vscode.extensions.getExtension("matterai.axon-code") if (!extension) { throw new Error("Extension not found") diff --git a/benchmark/src/runExercise.ts b/benchmark/src/runExercise.ts index cf6cdca330..350d6a717d 100644 --- a/benchmark/src/runExercise.ts +++ b/benchmark/src/runExercise.ts @@ -28,7 +28,7 @@ export async function run() { * Activate the extension. */ - const extension = vscode.extensions.getExtension("kilocode.Kilo-Code") + const extension = vscode.extensions.getExtension("matterai.axon-code") if (!extension) { throw new Error("Extension not found.") diff --git a/cli/src/host/VSCode.ts b/cli/src/host/VSCode.ts index 052eb74e77..4201b28ea2 100644 --- a/cli/src/host/VSCode.ts +++ b/cli/src/host/VSCode.ts @@ -2068,7 +2068,7 @@ export function createVSCodeAPIMock(extensionRootPath: string, workspacePath: st all: [], getExtension: (extensionId: string) => { // Mock the extension object with extensionUri for theme loading - if (extensionId === "kilocode.kilo-code") { + if (extensionId === "matterai.axon-code") { return { id: extensionId, extensionUri: context.extensionUri, diff --git a/packages/types/src/__tests__/kilocode.test.ts b/packages/types/src/__tests__/kilocode.test.ts index 7196b60db1..1333b76a87 100644 --- a/packages/types/src/__tests__/kilocode.test.ts +++ b/packages/types/src/__tests__/kilocode.test.ts @@ -258,8 +258,8 @@ describe("URL functions", () => { expect(getKiloUrlFromToken("https://api.matterai.so/organizations/123/defaults", prodToken)).toBe( "https://api.matterai.so/organizations/123/defaults", ) - expect(getKiloUrlFromToken("https://api.matterai.so/v1/web/", prodToken)).toBe( - "https://api.matterai.so/v1/web/", + expect(getKiloUrlFromToken("https://api2.matterai.so/v1/web/", prodToken)).toBe( + "https://api2.matterai.so/v1/web/", ) expect(getKiloUrlFromToken("https://api.matterai.so/users/notifications", prodToken)).toBe( "https://api.matterai.so/users/notifications", diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index 636fb3f2c3..8dcb549984 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -5,8 +5,8 @@ import { z } from "zod" */ export const CODEBASE_INDEX_DEFAULTS = { MIN_SEARCH_RESULTS: 10, - MAX_SEARCH_RESULTS: 5, - DEFAULT_SEARCH_RESULTS: 5, + MAX_SEARCH_RESULTS: 2, + DEFAULT_SEARCH_RESULTS: 2, SEARCH_RESULTS_STEP: 10, MIN_SEARCH_SCORE: 0.5, MAX_SEARCH_SCORE: 1, diff --git a/scripts/reset-kilocode-state.sh b/scripts/reset-kilocode-state.sh index 9aeaeb69bf..f316f78af7 100755 --- a/scripts/reset-kilocode-state.sh +++ b/scripts/reset-kilocode-state.sh @@ -5,12 +5,12 @@ echo "Kilocode state is being reset. This probably doesn't work while VS Code i # Reset the secrets: sqlite3 ~/Library/Application\ Support/Code/User/globalStorage/state.vscdb \ "DELETE FROM ItemTable WHERE \ - key = 'kilocode.kilo-code' OR \ + key = 'matterai.axon-code' OR \ key LIKE 'workbench.view.extension.kilo-code%' OR \ - key LIKE 'secret://{\"extensionId\":\"kilocode.kilo-code\",%';" + key LIKE 'secret://{\"extensionId\":\"matterai.axon-code\",%';" # delete all kilocode state files: -rm -rf ~/Library/Application\ Support/Code/User/globalStorage/kilocode.kilo-code/ +rm -rf ~/Library/Application\ Support/Code/User/globalStorage/matterai.axon-code/ # clear some of the vscode cache that I've observed contains kilocode related entries: rm -f ~/Library/Application\ Support/Code/CachedProfilesData/__default__profile__/extensions.user.cache diff --git a/src/api/providers/kilocode-models.ts b/src/api/providers/kilocode-models.ts index 5f65b8953e..1bdcd73e51 100644 --- a/src/api/providers/kilocode-models.ts +++ b/src/api/providers/kilocode-models.ts @@ -27,10 +27,10 @@ export type KiloCodeModel = { } export const KILO_CODE_MODELS: Record = { - "axon-code": { - id: "axon-code", - name: "Axon Code", - description: "Axon Code is super intelligent LLM model for coding tasks", + "axon-auto": { + id: "axon-auto", + name: "Auto", + description: "Auto is a model that automatically selects the best model for the task", input_modalities: ["text"], context_length: 200000, max_output_length: 32768, @@ -53,22 +53,21 @@ export const KILO_CODE_MODELS: Record = { created: 1750426201, owned_by: "matterai", pricing: { - prompt: "0.000001", - completion: "0.000004", + prompt: "0.0", + completion: "0.0", image: "0", request: "0", input_cache_reads: "0", input_cache_writes: "0", }, }, - "axon-mini": { - id: "axon-mini", - name: "Axon Mini", - description: - "Axon Mini is an general purpose super intelligent LLM coding model for low-effort day-to-day tasks", + "axon-code": { + id: "axon-code", + name: "Axon Code 1.3", + description: "Axon Code is super intelligent LLM model for coding tasks", input_modalities: ["text"], context_length: 200000, - max_output_length: 16384, + max_output_length: 32768, output_modalities: ["text"], supported_sampling_parameters: [ "temperature", @@ -88,8 +87,8 @@ export const KILO_CODE_MODELS: Record = { created: 1750426201, owned_by: "matterai", pricing: { - prompt: "2.5e-7", - completion: "0.000001", + prompt: "0.0", + completion: "0.0", image: "0", request: "0", input_cache_reads: "0", @@ -98,9 +97,9 @@ export const KILO_CODE_MODELS: Record = { }, "axon-code-2": { id: "axon-code-2", - name: "Axon Code 2", + name: "Axon Code 2.1", description: - "Axon Code 2 is the next-generation of Axon Code for coding tasks, currently in experimental stage.", + "Axon Code 2.1 is the next-generation of Axon Code for coding tasks, currently in experimental stage.", input_modalities: ["text"], context_length: 200000, max_output_length: 64000, @@ -133,9 +132,9 @@ export const KILO_CODE_MODELS: Record = { }, "axon-code-2-pro": { id: "axon-code-2-pro", - name: "Axon Code 2 Pro", + name: "Axon Code 2.1 Pro", description: - "Axon Code 2 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.", + "Axon Code 2.1 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.", input_modalities: ["text"], context_length: 200000, max_output_length: 64000, @@ -166,73 +165,4 @@ export const KILO_CODE_MODELS: Record = { input_cache_writes: "0", }, }, - // "gemini-3-flash-preview": { - // id: "gemini-3-flash-preview", - // name: "Gemini 3 Flash Preview", - // description: - // "Gemini 3 Flash Preview model is built for speed, combining frontier intelligence with superior search and grounding.", - // input_modalities: ["text"], - // context_length: 256000, - // max_output_length: 32768, - // output_modalities: ["text"], - // supported_sampling_parameters: [ - // "temperature", - // "top_p", - // "top_k", - // "repetition_penalty", - // "frequency_penalty", - // "presence_penalty", - // "seed", - // "stop", - // ], - // supported_features: ["tools", "structured_outputs", "web_search"], - // openrouter: { - // slug: "google/gemini-3-flash-preview", - // }, - // datacenters: [{ country_code: "US" }], - // created: 1750426201, - // owned_by: "google", - // pricing: { - // prompt: "0.000001", - // completion: "0.000004", - // image: "0", - // request: "0", - // input_cache_reads: "0", - // input_cache_writes: "0", - // }, - // }, - // "gemini-3-pro-preview": { - // id: "gemini-3-pro-preview", - // name: "Gemini 3 Pro Preview", - // description: "Gemini 3 Pro Preview model is an agentic and vibe-coding model by Google", - // input_modalities: ["text"], - // context_length: 256000, - // max_output_length: 32768, - // output_modalities: ["text"], - // supported_sampling_parameters: [ - // "temperature", - // "top_p", - // "top_k", - // "repetition_penalty", - // "frequency_penalty", - // "presence_penalty", - // "seed", - // "stop", - // ], - // supported_features: ["tools", "structured_outputs", "web_search"], - // openrouter: { - // slug: "google/gemini-3-pro-preview", - // }, - // datacenters: [{ country_code: "US" }], - // created: 1750426201, - // owned_by: "google", - // pricing: { - // prompt: "0.000001", - // completion: "0.000004", - // image: "0", - // request: "0", - // input_cache_reads: "0", - // input_cache_writes: "0", - // }, - // }, } diff --git a/src/api/providers/kilocode-openrouter.ts b/src/api/providers/kilocode-openrouter.ts index 79befefc04..3c2b550d3a 100644 --- a/src/api/providers/kilocode-openrouter.ts +++ b/src/api/providers/kilocode-openrouter.ts @@ -30,7 +30,7 @@ export class KilocodeOpenrouterHandler extends OpenRouterHandler { constructor(options: ApiHandlerOptions) { options = { ...options, - openRouterBaseUrl: getKiloUrlFromToken("https://api.matterai.so/v1/web/", options.kilocodeToken ?? ""), + openRouterBaseUrl: getKiloUrlFromToken("https://api2.matterai.so/v1/web/", options.kilocodeToken ?? ""), openRouterApiKey: options.kilocodeToken, } diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index d72eaf3f30..e7dd17d0cd 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -228,14 +228,12 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH this.customRequestOptions(metadata), // kilocode_change ) } catch (error) { - // kilocode_change start if (this.providerName == "KiloCode" && isAnyRecognizedKiloCodeError(error)) { throw error } const err = new Error(makeOpenRouterErrorReadable(error)) as any err.status = error?.status || error?.code throw err - // kilocode_change end } let lastUsage: CompletionUsage | undefined = undefined @@ -243,7 +241,6 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH try { let fullContent = "" - let fullReasoning = "" // kilocode_change: variable kept for structural integrity if needed, but unused logs removed let isThinking = false diff --git a/src/core/tools/reportBugTool.ts b/src/core/tools/reportBugTool.ts index 699c81fc0f..92aae1895e 100644 --- a/src/core/tools/reportBugTool.ts +++ b/src/core/tools/reportBugTool.ts @@ -49,7 +49,7 @@ export async function reportBugTool( // Derive system information values algorithmically const operatingSystem = os.platform() + " " + os.release() const kilocodeVersion = - vscode.extensions.getExtension("kilocode.kilo-code")?.packageJSON.version || "Unknown" + vscode.extensions.getExtension("matterai.axon-code")?.packageJSON.version || "Unknown" const systemInfo = `VSCode: ${vscode.version}, Node.js: ${process.version}, Architecture: ${os.arch()}` const providerAndModel = `${(await cline.providerRef.deref()?.contextProxy.getGlobalState("apiProvider")) as string} / ${cline.api.getModel().id}` diff --git a/src/extension.ts b/src/extension.ts index dbbc13b687..5c333993c3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -256,7 +256,7 @@ export async function activate(context: vscode.ExtensionContext) { // https://discord.com/channels/1349288496988160052/1395865796026040470 await vscode.commands.executeCommand( "workbench.action.openWalkthrough", - "kilocode.kilo-code#kiloCodeWalkthrough", + "matterai.axon-code#kiloCodeWalkthrough", false, ) } catch (error) { diff --git a/src/integrations/notifications/__tests__/index.spec.ts b/src/integrations/notifications/__tests__/index.spec.ts index fdaa78eefa..dc6040146a 100644 --- a/src/integrations/notifications/__tests__/index.spec.ts +++ b/src/integrations/notifications/__tests__/index.spec.ts @@ -24,14 +24,23 @@ vi.mock("vscode", () => ({ fsPath: path.join(__dirname, "..", "..", "..", ...pathSegments), })), }, + env: { + appName: "Visual Studio Code", + }, + window: { + showInformationMessage: vi.fn().mockResolvedValue(undefined), + }, })) // Import after mocking import { showSystemNotification } from "../index" import * as os from "os" +import * as vscode from "vscode" const mockedExeca = vi.mocked(execa) const mockedPlatform = vi.mocked(os.platform) +const mockedGetExtension = vi.mocked(vscode.extensions.getExtension) +const mockedShowInformationMessage = vi.mocked(vscode.window.showInformationMessage) describe("showSystemNotification", () => { beforeEach(() => { @@ -66,15 +75,15 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) expect(mockedExeca).toHaveBeenCalledTimes(1) }) - it("should fall back to osascript when terminal-notifier fails", async () => { - // First call (terminal-notifier) fails + it("should fall back to VS Code notification when terminal-notifier fails", async () => { + // terminal-notifier fails mockedExeca.mockRejectedValueOnce(new Error("terminal-notifier not found")) - // Second call (osascript) succeeds - mockedExeca.mockResolvedValueOnce({} as any) await showSystemNotification({ title: "Test Title", @@ -83,8 +92,8 @@ describe("showSystemNotification", () => { }) const expectedIconPath = path.join(__dirname, "..", "..", "..", "assets", "icons", "matterai-ic.png") - expect(mockedExeca).toHaveBeenCalledTimes(2) - expect(mockedExeca).toHaveBeenNthCalledWith(1, "terminal-notifier", [ + expect(mockedExeca).toHaveBeenCalledTimes(1) + expect(mockedExeca).toHaveBeenCalledWith("terminal-notifier", [ "-message", "Test Message", "-title", @@ -95,11 +104,11 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) - expect(mockedExeca).toHaveBeenNthCalledWith(2, "osascript", [ - "-e", - 'display notification "Test Message" with title "Test Title" subtitle "Test Subtitle" sound name "Tink"', - ]) + // Should fall back to VS Code notification + expect(mockedShowInformationMessage).toHaveBeenCalledWith("Test Title: Test Subtitle: Test Message") }) it("should handle terminal-notifier with minimal options", async () => { @@ -119,6 +128,8 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) }) @@ -140,6 +151,8 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) }) @@ -164,14 +177,14 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) }) - it("should fall back to osascript and escape quotes properly", async () => { + it("should fall back to VS Code notification and handle quotes properly", async () => { // terminal-notifier fails mockedExeca.mockRejectedValueOnce(new Error("not found")) - // osascript succeeds - mockedExeca.mockResolvedValueOnce({} as any) await showSystemNotification({ title: 'Title with "quotes"', @@ -179,22 +192,22 @@ describe("showSystemNotification", () => { message: 'Message with "quotes"', }) - expect(mockedExeca).toHaveBeenNthCalledWith(2, "osascript", [ - "-e", - 'display notification "Message with \\"quotes\\"" with title "Title with \\"quotes\\"" subtitle "Subtitle with \\"quotes\\"" sound name "Tink"', - ]) + // Should fall back to VS Code notification with escaped quotes + expect(mockedShowInformationMessage).toHaveBeenCalledWith( + 'Title with \\"quotes\\": Subtitle with \\"quotes\\": Message with \\"quotes\\"', + ) }) - it("should throw error when both terminal-notifier and osascript fail", async () => { - // Both calls fail + it("should fall back to VS Code notification when terminal-notifier fails repeatedly", async () => { + // terminal-notifier fails mockedExeca.mockRejectedValue(new Error("Command failed")) await showSystemNotification({ message: "Test Message", }) - // Should not throw but log error to console - expect(console.error).toHaveBeenCalledWith("Could not show system notification", expect.any(Error)) + // Should fall back to VS Code notification + expect(mockedShowInformationMessage).toHaveBeenCalledWith("Axon Code: Test Message") }) }) @@ -279,6 +292,8 @@ describe("showSystemNotification", () => { "Tink", "-appIcon", expectedIconPath, + "-activate", + "com.microsoft.VSCode", ]) }) }) diff --git a/src/integrations/notifications/index.ts b/src/integrations/notifications/index.ts index 333b8328c4..a2f3ffba1b 100644 --- a/src/integrations/notifications/index.ts +++ b/src/integrations/notifications/index.ts @@ -8,6 +8,30 @@ interface NotificationOptions { message: string } +/** + * Get the bundle ID for the current VS Code-based editor + * Used to activate the correct app when clicking notifications + */ +function getEditorBundleId(): string { + const appName = vscode.env.appName.toLowerCase() + + // Map editor names to their bundle IDs + if (appName.includes("cursor")) { + return "com.todesktop.230313mzl4w4u92" + } + if (appName.includes("insiders")) { + return "com.microsoft.VSCodeInsiders" + } + if (appName.includes("vscodium")) { + return "com.visualstudio.code.oss" + } + if (appName.includes("windsurf")) { + return "com.codeium.windsurf" + } + // Default to VS Code + return "com.microsoft.VSCode" +} + async function showMacOSNotification(options: NotificationOptions): Promise { const { title, subtitle = "", message } = options @@ -23,25 +47,26 @@ async function showMacOSNotification(options: NotificationOptions): Promise { diff --git a/src/package.json b/src/package.json index 7eb5dbb6d2..95a03fef31 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "matterai", - "version": "5.4.0", + "version": "5.4.1", "icon": "assets/icons/matterai-ic.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/src/shared/kilocode/getTaskHistory.ts b/src/shared/kilocode/getTaskHistory.ts index 8d05f6b73c..ce4f30817c 100644 --- a/src/shared/kilocode/getTaskHistory.ts +++ b/src/shared/kilocode/getTaskHistory.ts @@ -3,7 +3,7 @@ import { HistoryItem } from "@roo-code/types" import { highlightFzfMatch } from "../../../webview-ui/src/utils/highlight" // weird hack, but apparently it works import { TaskHistoryRequestPayload, TaskHistoryResponsePayload } from "../WebviewMessage" -const PAGE_SIZE = 10 +const PAGE_SIZE = 20 export function getTaskHistory( taskHistory: HistoryItem[], diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 4e430ca9bc..5c023ad7d7 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -59,6 +59,7 @@ import { PlayIcon } from "@/utils/customIcons" interface ChatRowProps { message: ClineMessage + messageIndex?: number // kilocode_change: for sticky message tracking lastModifiedMessage?: ClineMessage isExpanded: boolean isLast: boolean @@ -144,6 +145,7 @@ const computeDiffStats = (diff?: string | null) => { export const ChatRowContent = ({ message, + messageIndex, // kilocode_change: for sticky message tracking lastModifiedMessage, isExpanded, isLast, @@ -1463,17 +1465,16 @@ export const ChatRowContent = ({ ) case "user_feedback": return ( -
+
{/*
{t("chat:feedback.youSaid")}
*/}
@@ -1501,7 +1502,7 @@ export const ChatRowContent = ({
{ if (!isStreaming) { handleEditClick() diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 7141a57d88..ff71dcc755 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -64,6 +64,7 @@ import { ChatTextArea } from "./ChatTextArea" import { showSystemNotification } from "@/kilocode/helpers" // kilocode_change import BottomControls from "../kilocode/BottomControls" // kilocode_change import KiloTaskHeader from "../kilocode/KiloTaskHeader" // kilocode_change +import StickyUserMessage from "../kilocode/StickyUserMessage" // kilocode_change import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" // import ProfileViolationWarning from "./ProfileViolationWarning" kilocode_change: unused @@ -257,6 +258,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction(null) const userRespondedRef = useRef(false) const [currentFollowUpTs, setCurrentFollowUpTs] = useState(null) + // kilocode_change start: Sticky user message state + const [stickyMessageIndex, setStickyMessageIndex] = useState(null) + const stickyHeaderRef = useRef(null) + const virtuosoScrollerRef = useRef(null) + const [stickyHeaderHeight, setStickyHeaderHeight] = useState(0) + // kilocode_change end const [iconsBaseUri] = useState(() => { const w = window as any return w.ICONS_BASE_URI || "" @@ -1743,6 +1750,88 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + const indices: number[] = [] + groupedMessages.forEach((msg, i) => { + if (!Array.isArray(msg) && msg.type === "say" && msg.say === "user_feedback") { + indices.push(i) + } + }) + return indices + }, [groupedMessages]) + + useEffect(() => { + const scroller = virtuosoScrollerRef.current + if (!scroller) return + + const handleScroll = () => { + if (userFeedbackIndices.length === 0) { + setStickyMessageIndex(null) + return + } + + const stickyHeight = stickyHeaderRef.current?.offsetHeight ?? 40 + const scrollerRect = scroller.getBoundingClientRect() + const threshold = scrollerRect.top + stickyHeight + + // Find the first rendered item to determine our position relative to the virtual list + const firstRenderedEl = scroller.querySelector("[data-item-index]") + const firstRenderedIndex = firstRenderedEl + ? parseInt(firstRenderedEl.getAttribute("data-item-index") || "0", 10) + : 0 + + let bestIndex: number | null = null + + for (const idx of userFeedbackIndices) { + const el = scroller.querySelector(`[data-item-index="${idx}"]`) as HTMLElement | null + + if (!el) { + if (idx < firstRenderedIndex) { + // Not in DOM and index is lower than first rendered -> Above viewport + bestIndex = idx + continue + } else { + // Not in DOM and index is higher -> Below viewport + break + } + } + + const elTop = el.getBoundingClientRect().top + if (elTop <= threshold) { + bestIndex = idx + } else { + // Below the threshold, everything after will be too + break + } + } + + setStickyMessageIndex(bestIndex) + } + + scroller.addEventListener("scroll", handleScroll, { passive: true }) + handleScroll() // Initial check + return () => scroller.removeEventListener("scroll", handleScroll) + }, [userFeedbackIndices]) + + // Track sticky header height with ResizeObserver so list items don't hide behind it + useEffect(() => { + const el = stickyHeaderRef.current + if (!el) { + setStickyHeaderHeight(0) + return + } + + const updateHeight = () => setStickyHeaderHeight(el.offsetHeight) + updateHeight() + + const observer = new ResizeObserver(updateHeight) + observer.observe(el) + return () => observer.disconnect() + }, [task?.ts]) // re-run when task mounts/changes + // kilocode_change end + // Effect to handle showing the checkpoint warning after a delay useEffect(() => { // Only show the warning when there's a task but no visible messages yet @@ -2174,7 +2263,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction - {/* kilocode_change start */} {hasSystemPromptOverride && (
@@ -2243,7 +2331,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction

- Setup Automated PR Reviews + Setup Agentic PR Reviews

-
+
+ {/* kilocode_change: Sticky user message - positioned outside Virtuoso for true sticky behavior */} +
+
+ +
+
, + }} atBottomStateChange={(isAtBottom: boolean) => { setIsAtBottom(isAtBottom) if (isAtBottom) { @@ -2353,6 +2457,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (ref instanceof HTMLElement) { + virtuosoScrollerRef.current = ref + } + }} />
diff --git a/webview-ui/src/components/chat/CodebaseSearchResult.tsx b/webview-ui/src/components/chat/CodebaseSearchResult.tsx index 363ae4eba0..e1670e4164 100644 --- a/webview-ui/src/components/chat/CodebaseSearchResult.tsx +++ b/webview-ui/src/components/chat/CodebaseSearchResult.tsx @@ -11,7 +11,12 @@ interface CodebaseSearchResultProps { language: string } -const CodebaseSearchResult: React.FC = ({ filePath, score, startLine, endLine }) => { +const CodebaseSearchResult: React.FC = ({ + filePath, + // score, + startLine, + endLine, +}) => { // const { _t } = useTranslation("chat") const handleClick = () => { @@ -29,7 +34,7 @@ const CodebaseSearchResult: React.FC = ({ filePath, s //
+ className="px-2 py-1 rounded-md text-sm cursor-pointer hover:bg-[var(--color-matterai-background-dark)] hover:text-white">
{filePath.split("/").at(-1)}:{startLine === endLine ? startLine : `${startLine}-${endLine}`} @@ -37,9 +42,9 @@ const CodebaseSearchResult: React.FC = ({ filePath, s {filePath.split("/").slice(0, -1).join("/")} - + {/* {score.toFixed(3)} - + */}
//
diff --git a/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx index eb594af100..c14e8dc291 100644 --- a/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx +++ b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx @@ -19,7 +19,7 @@ const CodebaseSearchResultsDisplay: React.FC
setCodebaseSearchResultsExpanded(!codebaseSearchResultsExpanded)} - className="cursor-pointer flex items-center justify-between px-2 py-2 border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)] rounded-lg"> + className="cursor-pointer flex items-center justify-between px-2 py-1.5 bg-[var(--vscode-editor-background)] rounded-lg"> { + const [currentIndex, setCurrentIndex] = useState(0) + const [opacity, setOpacity] = useState(1) + + useEffect(() => { + const interval = setInterval(() => { + setOpacity(0) + setTimeout(() => { + setCurrentIndex((prev) => (prev + 1) % LOADING_STATES.length) + setOpacity(1) + }, 300) + }, 3000) + + return () => clearInterval(interval) + }, []) + + return ( + + {LOADING_STATES[currentIndex]} + + ) +} interface FileChange { relPath: string @@ -80,6 +107,7 @@ export const SourceControlPanel: React.FC = ({ vscode.postMessage({ type: "openFile", text: comment.path, + values: { line: comment.startLine }, }) } @@ -155,7 +183,7 @@ ${comment.suggestion}` }, }) // We can't close the panel directly from here as state is in parent, - // but typically "Apply All" implies we are done with this review session. + // but typically "Fix All" implies we are done with this review session. // For now, we just apply. // If we want to close, we should call onClose(), but maybe user wants to see confirmation. // Let's just apply for now. @@ -235,8 +263,8 @@ ${comment.suggestion} {fileChanges.length > 0 && isLoading && (
- - Analyzing... + +
)} @@ -351,10 +379,10 @@ ${comment.suggestion} {/* Review Header */}
- - Review Results + + Review Results {codeReviewResult.reviewComments?.length > 0 && ( - + {codeReviewResult.reviewComments.length} )} @@ -364,18 +392,16 @@ ${comment.suggestion} {hasKilocodeToken ? (
- + - - Apply All + + Fix All
) : ( - - + + {copyButtonText} )} @@ -385,7 +411,7 @@ ${comment.suggestion} {/* Review Summary */} {codeReviewResult.reviewBody && ( -
+
{codeReviewResult.reviewBody}
)} @@ -401,8 +427,8 @@ ${comment.suggestion} className="px-3 py-2.5 border-b border-vscode-editorWidget-border last:border-b-0 hover:bg-vscode-list-hoverBackground">
) : ( handleCopyPrompt(comment)}> - + Copy )} diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index fef1c8a792..d0515bfebc 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -247,7 +247,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
- + { isSelected={selectedTaskIds.includes(item.id)} onToggleSelection={toggleTaskSelection} onDelete={setDeleteTaskId} - className="m-2" /> )} /> diff --git a/webview-ui/src/components/kilocode/KiloTaskHeader.tsx b/webview-ui/src/components/kilocode/KiloTaskHeader.tsx index 02549bb3cd..5d3e6213af 100644 --- a/webview-ui/src/components/kilocode/KiloTaskHeader.tsx +++ b/webview-ui/src/components/kilocode/KiloTaskHeader.tsx @@ -1,16 +1,11 @@ // kilocode_change: new file -import { memo, useRef, useState } from "react" +import { memo } from "react" import type { ClineMessage } from "@roo-code/types" -import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" -import { useExtensionState } from "@src/context/ExtensionStateContext" import { cn } from "@src/lib/utils" -import Thumbnails from "../common/Thumbnails" -import { ReadOnlyChatText } from "../chat/ReadOnlyChatText" - -import { TodoListDisplay } from "../chat/TodoListDisplay" +// import { TodoListDisplay } from "../chat/TodoListDisplay" export interface TaskHeaderProps { task: ClineMessage @@ -20,252 +15,43 @@ export interface TaskHeaderProps { cacheReads?: number totalCost: number contextTokens: number - // buttonsDisabled: boolean handleCondenseContext: (taskId: string) => void onClose: () => void groupedMessages: (ClineMessage | ClineMessage[])[] onMessageClick?: (index: number) => void isTaskActive?: boolean todos?: any[] - title?: string // kilocode_change: Task title from backend + title?: string } const KiloTaskHeader = ({ - task, - // tokensIn, - // tokensOut, - // cacheWrites, - // cacheReads, - // totalCost, - // contextTokens, - // buttonsDisabled, - // handleCondenseContext, onClose, - // groupedMessages, - // onMessageClick, - // isTaskActive = false, - todos, + // todos, title, }: TaskHeaderProps) => { - // const { t } = useTranslation() - // const { showTaskTimeline } = useExtensionState() - const { apiConfiguration } = useExtensionState() - const { info: model } = useSelectedModel(apiConfiguration) - const [isTaskExpanded, setIsTaskExpanded] = useState(false) - - const textContainerRef = useRef(null) - const textRef = useRef(null) - const contextWindow = model?.contextWindow || 1 - - // const { width: windowWidth } = useWindowSize() - - // const condenseButton = ( - // - // - // - // ) - - // const hasTodos = todos && Array.isArray(todos) && todos.length > 0 - return (
- {/* kilocode_change: Show title with X button at the top */} - {title && ( -
-
- {title} -
- -
- )} + {/* Title with close button */}
-
setIsTaskExpanded(!isTaskExpanded)}> - {/*
- -
*/} -
- {/* - {t("chat:task.title")} - {!isTaskExpanded && ":"} - */} - {!isTaskExpanded && ( -
- -
- )} -
+
+ {title || "New task"}
- {/* - - */} +
- {/* Collapsed state: Track context and cost if we have any */} - {!isTaskExpanded && contextWindow > 0 && ( -
- {/* {showTaskTimeline && ( - - )} */} - - {/*
- - {condenseButton} - - {!!totalCost && ${totalCost.toFixed(2)}} -
*/} -
- )} - {/* Expanded state: Show task text and images */} - {isTaskExpanded && ( - <> -
setIsTaskExpanded(!isTaskExpanded)} - ref={textContainerRef} - className="text-vscode-font-size overflow-y-auto break-words break-anywhere relative"> -
- -
-
- {task.images && task.images.length > 0 && } - - {/* {showTaskTimeline && ( - - )} */} - - {/*
- {isTaskExpanded && contextWindow > 0 && ( -
-
- - {t("chat:task.contextWindow")} - -
- - {condenseButton} -
- )} -
-
- {t("chat:task.tokens")} - {typeof tokensIn === "number" && tokensIn > 0 && ( - - - {formatLargeNumber(tokensIn)} - - )} - {typeof tokensOut === "number" && tokensOut > 0 && ( - - - {formatLargeNumber(tokensOut)} - - )} -
- {!totalCost && } -
- - {((typeof cacheReads === "number" && cacheReads > 0) || - (typeof cacheWrites === "number" && cacheWrites > 0)) && ( -
- {t("chat:task.cache")} - {typeof cacheWrites === "number" && cacheWrites > 0 && ( - - - {formatLargeNumber(cacheWrites)} - - )} - {typeof cacheReads === "number" && cacheReads > 0 && ( - - - {formatLargeNumber(cacheReads)} - - )} -
- )} - - {!!totalCost && ( -
-
- {t("chat:task.apiCost")} - ${totalCost?.toFixed(2)} -
- -
- )} -
*/} - - )}
- + {/* */}
) } diff --git a/webview-ui/src/components/kilocode/StickyUserMessage.tsx b/webview-ui/src/components/kilocode/StickyUserMessage.tsx new file mode 100644 index 0000000000..5e93b6f8b1 --- /dev/null +++ b/webview-ui/src/components/kilocode/StickyUserMessage.tsx @@ -0,0 +1,126 @@ +// kilocode_change: new file - Sticky user message component for chat +import { memo, useState } from "react" +import type { ClineMessage } from "@roo-code/types" +import { ReadOnlyChatText } from "../chat/ReadOnlyChatText" +import { cn } from "@src/lib/utils" + +export interface StickyUserMessageProps { + task: ClineMessage + messages: (ClineMessage | ClineMessage[])[] + stickyIndex: number | null +} + +/** + * StickyUserMessage - Displays the current "sticky" user message. + * + * This works like sticky headers on mobile UI: + * - Shows the initial prompt (task.text) by default + * - As user scrolls, it tracks which user message should be sticky + * - Uses CSS position: sticky to stay at the top of the scroll container + */ +const StickyUserMessage = ({ task, messages, stickyIndex }: StickyUserMessageProps) => { + const [isExpanded, setIsExpanded] = useState(false) + + // Get the message to display based on stickyIndex + // stickyIndex: null = task prompt, number = index in messages array (groupedMessages) + const getStickyMessage = () => { + // If stickyIndex is null, show the initial task prompt + if (stickyIndex === null) { + return { + text: task?.text, + images: task?.images, + } + } + + // Find the user_feedback message at the given index in groupedMessages + const msg = messages[stickyIndex] + + if (!msg || Array.isArray(msg)) { + return { + text: task?.text, + images: task?.images, + } + } + + if (msg.type === "say" && msg.say === "user_feedback") { + return { + text: msg.text, + images: msg.images, + } + } + + // Fallback to task prompt + return { + text: task?.text, + images: task?.images, + } + } + + const stickyMessage = getStickyMessage() + + if (!stickyMessage?.text && !stickyMessage?.images?.length) { + return null + } + + return ( +
+
setIsExpanded(!isExpanded)}> +
+ {!isExpanded ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ {isExpanded && stickyMessage.images && stickyMessage.images.length > 0 && ( +
+ {stickyMessage.images.slice(0, 4).map((img, i) => ( + {`Image + ))} + {stickyMessage.images.length > 4 && ( + +{stickyMessage.images.length - 4} more + )} +
+ )} +
+ ) +} + +export default memo(StickyUserMessage) diff --git a/webview-ui/src/components/kilocode/chat/ModelSelector.tsx b/webview-ui/src/components/kilocode/chat/ModelSelector.tsx index e9db7171ca..8b27e43ecd 100644 --- a/webview-ui/src/components/kilocode/chat/ModelSelector.tsx +++ b/webview-ui/src/components/kilocode/chat/ModelSelector.tsx @@ -1,13 +1,14 @@ -import { useMemo } from "react" -import { SelectDropdown, DropdownOptionType } from "@/components/ui" +import { DropdownOption, DropdownOptionType, SelectDropdown, StandardTooltip } from "@/components/ui" +import { usePreferredModels } from "@/components/ui/hooks/kilocode/usePreferredModels" +import { Alert02Icon, Brain01Icon } from "@/utils/customIcons" import { OPENROUTER_DEFAULT_PROVIDER_NAME, type ProviderSettings } from "@roo-code/types" -import { vscode } from "@src/utils/vscode" import { useAppTranslation } from "@src/i18n/TranslationContext" import { cn } from "@src/lib/utils" -import { prettyModelName, getModelCredits } from "../../../utils/prettyModelName" +import { vscode } from "@src/utils/vscode" +import { useMemo } from "react" +import { prettyModelName } from "../../../utils/prettyModelName" import { useProviderModels } from "../hooks/useProviderModels" import { getModelIdKey, getSelectedModelId } from "../hooks/useSelectedModel" -import { usePreferredModels } from "@/components/ui/hooks/kilocode/usePreferredModels" interface ModelSelectorProps { currentApiConfigName?: string @@ -17,7 +18,8 @@ interface ModelSelectorProps { export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallbackText }: ModelSelectorProps) => { const { t } = useAppTranslation() - const { provider, providerModels, providerDefaultModel, isLoading, isError } = useProviderModels(apiConfiguration) + const { provider, providerModels, providerDefaultModel, isLoading, isError, proModelIds, proModelsEnabled } = + useProviderModels(apiConfiguration) const selectedModelId = getSelectedModelId({ provider, apiConfiguration, @@ -30,15 +32,22 @@ export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallback const missingModelIds = modelsIds.indexOf(selectedModelId) >= 0 ? [] : [selectedModelId] return missingModelIds.concat(modelsIds).map((modelId) => { const baseLabel = providerModels[modelId]?.displayName ?? prettyModelName(modelId) - const credits = getModelCredits(modelId) - const label = credits ? `${baseLabel} ${credits}` : baseLabel + // const credits = getModelCredits(modelId) + // const label = credits ? `${baseLabel} ${credits}` : baseLabel + const label = baseLabel + // kilocode_change: Check if this is a pro model that's disabled + const isProModel = proModelIds?.includes(modelId) + const isProModelDisabled = isProModel && !proModelsEnabled + return { value: modelId, label, type: DropdownOptionType.ITEM, + disabled: isProModelDisabled, + isProModelDisabled, } }) - }, [modelsIds, providerModels, selectedModelId]) + }, [modelsIds, providerModels, selectedModelId, proModelIds, proModelsEnabled]) const disabled = isLoading || isError @@ -61,6 +70,40 @@ export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallback }) } + const renderItem = (option: DropdownOption & { isProModelDisabled?: boolean }) => { + return ( +
+
+
{option.label}
+
+ + {option.isProModelDisabled && ( + + Pro models are only available on the Paid Plan + +
+ }> + + + + + )} +
+ ) + } + if (isLoading) { return null } @@ -83,6 +126,7 @@ export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallback )} triggerIcon={true} itemClassName="group" + renderItem={renderItem} /> ) } diff --git a/webview-ui/src/components/kilocode/hooks/useProviderModels.ts b/webview-ui/src/components/kilocode/hooks/useProviderModels.ts index 082edfee20..88e2381f09 100644 --- a/webview-ui/src/components/kilocode/hooks/useProviderModels.ts +++ b/webview-ui/src/components/kilocode/hooks/useProviderModels.ts @@ -1,4 +1,3 @@ -import { useMemo } from "react" import { type ProviderName, type ProviderSettings, @@ -323,22 +322,20 @@ export const useProviderModels = (apiConfiguration?: ProviderSettings) => { }) : FALLBACK_MODELS - // kilocode_change start: Filter out axon-code-2-pro if beta models are not enabled - const filteredModels = useMemo(() => { - if (!betaModelsEnabled) { - // Filter out axon-code-2-pro when beta models are not enabled - const { "axon-code-2-pro": _, ...rest } = models as ModelRecord - return rest - } - return models - }, [models, betaModelsEnabled]) + // kilocode_change start: Always show all models including axon-code-2-pro + // Pro models are marked as disabled if betaModelsEnabled is false + const proModelIds = ["axon-code-2-pro"] + const proModelsEnabled = betaModelsEnabled // kilocode_change end return { provider, - providerModels: filteredModels as ModelRecord, + providerModels: models as ModelRecord, providerDefaultModel: defaultModel, isLoading: routerModels.isLoading, isError: routerModels.isError, + // kilocode_change: pro models support + proModelIds, + proModelsEnabled, } } diff --git a/webview-ui/src/components/kilocode/settings/providers/KiloCode.tsx b/webview-ui/src/components/kilocode/settings/providers/KiloCode.tsx index fa74c65c4d..88c7fef0c4 100644 --- a/webview-ui/src/components/kilocode/settings/providers/KiloCode.tsx +++ b/webview-ui/src/components/kilocode/settings/providers/KiloCode.tsx @@ -9,8 +9,8 @@ import { ModelPicker } from "../../../settings/ModelPicker" import { OrganizationSelector } from "../../common/OrganizationSelector" import { getKiloCodeBackendSignInUrl } from "../../helpers" import { useExtensionState } from "@/context/ExtensionStateContext" -import { useMemo } from "react" -import type { ModelRecord } from "@roo/api" +import { ProfileData, WebviewMessage } from "@roo/WebviewMessage" +import { useEffect, useRef, useState } from "react" type KiloCodeProps = { apiConfiguration: ProviderSettings @@ -38,18 +38,79 @@ export const KiloCode = ({ kilocodeDefaultModel, }: KiloCodeProps) => { const { t } = useAppTranslation() - const { betaModelsEnabled } = useExtensionState() - - // Filter out axon-code-2-pro if beta models are not enabled - const filteredModels = useMemo(() => { - const models = routerModels?.["kilocode-openrouter"] ?? {} - if (!betaModelsEnabled) { - // Filter out axon-code-2-pro when beta models are not enabled - const { "axon-code-2-pro": _, ...rest } = models as ModelRecord - return rest + const { betaModelsEnabled, clineMessages } = useExtensionState() + + // Profile data state for usage info + const [profileData, setProfileData] = useState(null) + const previousMessagesRef = useRef("") + + // Fetch profile data on mount if token exists + useEffect(() => { + if (apiConfiguration?.kilocodeToken) { + vscode.postMessage({ type: "fetchProfileDataRequest" }) + } + }, [apiConfiguration?.kilocodeToken]) + + // Listen for profile data response + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "profileDataResponse") { + const payload = message.payload as any + if (payload?.success && payload.data) { + setProfileData(payload.data) + } + } + } + + window.addEventListener("message", handleMessage) + return () => { + window.removeEventListener("message", handleMessage) + } + }, []) + + // Watch for new assistant responses and fetch updated profile data + useEffect(() => { + if (!apiConfiguration?.kilocodeToken || !clineMessages) return + + const currentMessagesHash = JSON.stringify( + clineMessages.map((msg) => ({ + type: msg.type, + say: msg.say, + partial: msg.partial, + ts: msg.ts, + })), + ) + + if (previousMessagesRef.current !== currentMessagesHash) { + const hasNewAssistantResponse = clineMessages.some( + (msg) => msg.type === "say" && (msg.say === "text" || msg.say === "completion_result") && !msg.partial, + ) + + if (hasNewAssistantResponse && previousMessagesRef.current !== "") { + vscode.postMessage({ type: "fetchProfileDataRequest" }) + } + + previousMessagesRef.current = currentMessagesHash } - return models - }, [routerModels, betaModelsEnabled]) + }, [clineMessages, apiConfiguration?.kilocodeToken]) + + // Calculate usage percentage from profile data + const usagePercentage = + profileData?.usagePercentage !== undefined + ? profileData.usagePercentage + : profileData?.usedCredits !== undefined && + profileData?.totalCredits !== undefined && + profileData.totalCredits > 0 + ? (profileData.usedCredits / profileData.totalCredits) * 100 + : null + + // Always show all models including axon-code-2-pro + // The model will be marked as disabled if betaModelsEnabled is false + const models = routerModels?.["kilocode-openrouter"] ?? {} + + // List of pro model IDs which require paid plan + const proModelIds = ["axon-code-2-pro"] // const handleInputChange = useCallback( // ( @@ -78,28 +139,54 @@ export const KiloCode = ({ return ( <>
- +
{!hideKiloCodeButton && (apiConfiguration.kilocodeToken ? ( -
- +
+ {/* Usage info */} + {profileData && ( +
+
+
+ Current Plan +
+
+ {profileData.plan?.toLocaleUpperCase()} +
+
+
+
+ Monthly Credits +
+
+ ${(profileData.remainingCredits || 0).toFixed(1)} / $ + {(profileData.totalCredits || 0).toFixed(1)} remaining ( + {usagePercentage !== null + ? `${usagePercentage.toFixed(0)}% used` + : "loading..."} + ) +
+
+ {profileData.remainingReviews !== undefined && ( +
+
+ Monthly Reviews +
+
+ {profileData.remainingReviews.toFixed(0)} reviews remaining +
+
+ )} +
+ )} + + {/* Manage plan button */} + + Manage/Upgrade plan +
) : ( + + {!hideKiloCodeButton && apiConfiguration.kilocodeToken ? ( + + ) : ( + <> + )} ) } diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index 2d3d755b46..46fd2ae82c 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -19,11 +19,14 @@ import { PopoverContent, PopoverTrigger, SelectSeparator, // kilocode_change + StandardTooltip, // kilocode_change } from "@src/components/ui" import { useEscapeKey } from "@src/hooks/useEscapeKey" import { cn } from "@src/lib/utils" +import { vscode } from "@src/utils/vscode" // kilocode_change import { ApiErrorMessage } from "./ApiErrorMessage" +import { Alert02Icon } from "@/utils/customIcons" type ModelIdKey = keyof Pick< ProviderSettings, @@ -57,6 +60,9 @@ interface ModelPickerProps { ) => void organizationAllowList: OrganizationAllowList errorMessage?: string + // kilocode_change: pro models support + proModelIds?: string[] + proModelsEnabled?: boolean } export const ModelPicker = ({ @@ -69,6 +75,9 @@ export const ModelPicker = ({ setApiConfigurationField, // organizationAllowList, // kilocode_change: unused errorMessage, + // kilocode_change: pro models support + proModelIds = [], + proModelsEnabled = true, }: ModelPickerProps) => { const { t } = useAppTranslation() @@ -94,6 +103,12 @@ export const ModelPicker = ({ return } + // kilocode_change: prevent selection of pro models when not enabled + const isProModel = proModelIds.includes(modelId) + if (isProModel && !proModelsEnabled) { + return + } + setOpen(false) setApiConfigurationField(modelIdKey, modelId) @@ -105,7 +120,7 @@ export const ModelPicker = ({ // Delay to ensure the popover is closed before setting the search value. selectTimeoutRef.current = setTimeout(() => setSearchValue(""), 100) }, - [modelIdKey, setApiConfigurationField], + [modelIdKey, setApiConfigurationField, proModelIds, proModelsEnabled], ) const onOpenChange = useCallback((open: boolean) => { @@ -155,7 +170,7 @@ export const ModelPicker = ({ return ( <>
- + +
+ }> + + + + + )} = { - "axon-code": { - id: "axon-code", - name: "Axon Code", - description: "Axon Code is super intelligent LLM model for coding tasks", + "axon-auto": { + id: "axon-auto", + name: "Auto", + description: "Auto is a model that automatically selects the best model for the task", input_modalities: ["text"], context_length: 200000, max_output_length: 32768, @@ -64,22 +64,21 @@ const KILO_CODE_MODELS: Record = { created: 1750426201, owned_by: "matterai", pricing: { - prompt: "0.000001", - completion: "0.000004", + prompt: "0.0", + completion: "0.0", image: "0", request: "0", input_cache_reads: "0", input_cache_writes: "0", }, }, - "axon-code-2": { - id: "axon-code-2", - name: "Axon Code 2", - description: - "Axon Code 2 is the next-generation of Axon Code for coding tasks, currently in experimental stage.", + "axon-code": { + id: "axon-code", + name: "Axon Code 1.3", + description: "Axon Code is super intelligent LLM model for coding tasks", input_modalities: ["text"], context_length: 200000, - max_output_length: 64000, + max_output_length: 32768, output_modalities: ["text"], supported_sampling_parameters: [ "temperature", @@ -99,22 +98,22 @@ const KILO_CODE_MODELS: Record = { created: 1750426201, owned_by: "matterai", pricing: { - prompt: "0.0000012", - completion: "0.0000048", + prompt: "0.0", + completion: "0.0", image: "0", request: "0", input_cache_reads: "0", input_cache_writes: "0", }, }, - "axon-mini": { - id: "axon-mini", - name: "Axon Mini", + "axon-code-2": { + id: "axon-code-2", + name: "Axon Code 2.1", description: - "Axon Mini is an general purpose super intelligent LLM coding model for low-effort day-to-day tasks", + "Axon Code 2.1 is the next-generation of Axon Code for coding tasks, currently in experimental stage.", input_modalities: ["text"], context_length: 200000, - max_output_length: 16384, + max_output_length: 64000, output_modalities: ["text"], supported_sampling_parameters: [ "temperature", @@ -134,8 +133,8 @@ const KILO_CODE_MODELS: Record = { created: 1750426201, owned_by: "matterai", pricing: { - prompt: "2.5e-7", - completion: "0.000001", + prompt: "0.0000012", + completion: "0.0000048", image: "0", request: "0", input_cache_reads: "0", @@ -144,9 +143,9 @@ const KILO_CODE_MODELS: Record = { }, "axon-code-2-pro": { id: "axon-code-2-pro", - name: "Axon Code 2 Pro", + name: "Axon Code 2.1 Pro", description: - "Axon Code 2 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.", + "Axon Code 2.1 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.", input_modalities: ["text"], context_length: 200000, max_output_length: 64000, @@ -177,75 +176,6 @@ const KILO_CODE_MODELS: Record = { input_cache_writes: "0", }, }, - // "gemini-3-flash-preview": { - // id: "gemini-3-flash-preview", - // name: "Gemini 3 Flash Preview", - // description: - // "Gemini 3 Flash Preview model is built for speed, combining frontier intelligence with superior search and grounding.", - // input_modalities: ["text"], - // context_length: 256000, - // max_output_length: 32768, - // output_modalities: ["text"], - // supported_sampling_parameters: [ - // "temperature", - // "top_p", - // "top_k", - // "repetition_penalty", - // "frequency_penalty", - // "presence_penalty", - // "seed", - // "stop", - // ], - // supported_features: ["tools", "structured_outputs", "web_search"], - // openrouter: { - // slug: "google/gemini-3-flash-preview", - // }, - // datacenters: [{ country_code: "US" }], - // created: 1750426201, - // owned_by: "google", - // pricing: { - // prompt: "0.000001", - // completion: "0.000004", - // image: "0", - // request: "0", - // input_cache_reads: "0", - // input_cache_writes: "0", - // }, - // }, - // "gemini-3-pro-preview": { - // id: "gemini-3-pro-preview", - // name: "Gemini 3 Pro Preview", - // description: "Gemini 3 Pro Preview model is an agentic and vibe-coding model by Google", - // input_modalities: ["text"], - // context_length: 256000, - // max_output_length: 32768, - // output_modalities: ["text"], - // supported_sampling_parameters: [ - // "temperature", - // "top_p", - // "top_k", - // "repetition_penalty", - // "frequency_penalty", - // "presence_penalty", - // "seed", - // "stop", - // ], - // supported_features: ["tools", "structured_outputs", "web_search"], - // openrouter: { - // slug: "google/gemini-3-pro-preview", - // }, - // datacenters: [{ country_code: "US" }], - // created: 1750426201, - // owned_by: "google", - // pricing: { - // prompt: "0.000001", - // completion: "0.000004", - // image: "0", - // request: "0", - // input_cache_reads: "0", - // input_cache_writes: "0", - // }, - // }, } const parsePrice = (value?: string): number | undefined => { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index deb06dd807..ffc2560c75 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -222,8 +222,8 @@ "didSearchOutsideWorkspace": "searched this directory(outside of the workspace) for {{regex}}" }, "codebaseSearch": { - "wantsToSearch": "searching codebase for {{query}}", - "wantsToSearchWithPath": "searching codebase for {{query}} in {{path}}", + "wantsToSearch": "Exploring {{query}}", + "wantsToSearchWithPath": "Exploring {{query}} in {{path}}", "didSearch_one": "Found 1 result", "didSearch_other": "Found {{count}} results", "resultTooltip": "Similarity score: {{score}} (click to open file)" diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index ba2762b4b2..78f5b0388c 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -152,6 +152,7 @@ --color-matterai-chip-blue: #8bf4f720; --color-matterai-yellow: #fff0ac; --color-matterai-blue-dark: #1a3b59; + --color-matterai-background-dark: #15181c; } @layer base { diff --git a/webview-ui/src/utils/customIcons.tsx b/webview-ui/src/utils/customIcons.tsx index bde89a4ff4..34dcc6f78b 100644 --- a/webview-ui/src/utils/customIcons.tsx +++ b/webview-ui/src/utils/customIcons.tsx @@ -188,3 +188,151 @@ export const PlayIcon = (props: React.SVGProps) => ( /> ) + +export const Alert02Icon = (props: React.SVGProps) => ( + + + + + +) + +export const Brain01Icon = (props: React.SVGProps) => ( + + + +) + +export const ChatSpark01Icon = (props: React.SVGProps) => ( + + + + + +) + +export const Copy01Icon = (props: React.SVGProps) => ( + + + + +) + +export const TickDouble02Icon = (props: React.SVGProps) => ( + + + + +) + +export const Tick02Icon = (props: React.SVGProps) => ( + + + +) diff --git a/webview-ui/src/utils/prettyModelName.ts b/webview-ui/src/utils/prettyModelName.ts index ec643686cc..09266e46c4 100644 --- a/webview-ui/src/utils/prettyModelName.ts +++ b/webview-ui/src/utils/prettyModelName.ts @@ -1,7 +1,7 @@ // Hardcoded credits information for Axon models const AXON_MODEL_CREDITS: Record = { - "axon-mini": "(0.5x)", - "axon-code": "(0.8x)", + "axon-auto": "(0.25x)", + "axon-code": "(0.5x)", "axon-code-2": "(1x)", "axon-code-2-pro": "(1.5x)", }