From 46213dcebda653c1575b67ef93fc8aab065a9db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 26 May 2026 20:48:18 -0600 Subject: [PATCH 01/59] fix(vscode): improve chat error styling --- .changeset/polite-errors-glow.md | 6 ++ packages/kilo-ui/src/components/card.css | 7 ++ .../kilo-ui/src/components/error-details.css | 70 ++++++++++-- .../kilo-ui/src/components/error-details.tsx | 2 +- packages/kilo-ui/src/styles/vscode-bridge.css | 28 ++--- .../src/components/chat/ErrorDisplay.tsx | 8 +- .../webview-ui/src/stories/chat.stories.tsx | 102 ++++++++++++++++++ 7 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 .changeset/polite-errors-glow.md diff --git a/.changeset/polite-errors-glow.md b/.changeset/polite-errors-glow.md new file mode 100644 index 00000000000..bc37608e936 --- /dev/null +++ b/.changeset/polite-errors-glow.md @@ -0,0 +1,6 @@ +--- +"@kilocode/kilo-ui": patch +"kilo-code": patch +--- + +Improve chat error styling in the VS Code extension. diff --git a/packages/kilo-ui/src/components/card.css b/packages/kilo-ui/src/components/card.css index 6ae4d7d2693..360d1028608 100644 --- a/packages/kilo-ui/src/components/card.css +++ b/packages/kilo-ui/src/components/card.css @@ -5,6 +5,13 @@ padding: 8px; border: 1px solid var(--border-weak-base); + &[data-variant="error"] { + padding: 12px; + background-color: color-mix(in srgb, var(--surface-critical-strong) 10%, var(--surface-inset-base)); + border-color: color-mix(in srgb, var(--border-critical-selected) 55%, var(--border-weaker-base)); + color: var(--text-base); + } + &[data-variant="warning"] { padding: 12px 14px; } diff --git a/packages/kilo-ui/src/components/error-details.css b/packages/kilo-ui/src/components/error-details.css index 259150984e0..88f97120a2e 100644 --- a/packages/kilo-ui/src/components/error-details.css +++ b/packages/kilo-ui/src/components/error-details.css @@ -1,42 +1,92 @@ .error-card { - padding-bottom: 0; - background-color: var(--surface-critical-base); + gap: 8px; + padding-bottom: 12px; + --error-card-accent: var(--text-on-critical-base); +} + +.error-card-body { + display: flex; + align-items: flex-start; + gap: 8px; +} + +.error-card-body [data-component="icon"] { + color: var(--error-card-accent); + margin-top: 2px; +} + +.error-card-message { + flex: 1; + min-width: 0; + color: var(--text-strong); + font-size: var(--font-size-base); + line-height: var(--line-height-large); + overflow-wrap: anywhere; } .error-card [data-component="collapsible"] { - margin-top: 8px; + margin-top: 0; + padding-left: 24px; } -.error-details-trigger { +.error-card .error-details-trigger[data-slot="collapsible-trigger"] { display: inline-flex; align-items: center; - gap: 4px; + align-self: flex-start; + gap: 2px; + width: auto; + height: 22px; font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); opacity: 0.85; cursor: pointer; background: none; border: none; - color: inherit; - padding: 0; + border-radius: var(--radius-sm); + color: var(--error-card-accent); + padding: 0 6px; } -.error-details-trigger:hover { +.error-card .error-details-trigger[data-slot="collapsible-trigger"]:hover { + background-color: color-mix(in srgb, var(--error-card-accent) 12%, transparent); opacity: 1; } +.error-card .error-details-trigger[data-slot="collapsible-trigger"]:focus-visible { + background-color: color-mix(in srgb, var(--error-card-accent) 12%, transparent); + outline: 1px solid var(--border-focus); + outline-offset: 2px; +} + +.error-card .error-details-trigger [data-slot="collapsible-arrow"] { + width: 16px; + height: 16px; + opacity: 1; +} + +.error-card .error-details-trigger [data-slot="collapsible-arrow-icon"] { + color: currentColor; +} + .error-details { display: flex; flex-direction: column; - gap: 4px; + gap: 6px; font-size: var(--font-size-small); - margin-top: 4px; + margin-top: 8px; } .error-detail-pre { margin: 0; max-height: 120px; overflow-y: auto; + background-color: color-mix(in srgb, var(--surface-inset-base) 82%, var(--background-base)); + border: 1px solid color-mix(in srgb, var(--border-critical-base) 30%, var(--border-weaker-base)); + border-radius: var(--radius-sm); + color: var(--text-base); font-size: var(--font-size-small); + line-height: var(--line-height-large); + padding: 8px; white-space: pre-wrap; word-break: break-all; flex: 1; diff --git a/packages/kilo-ui/src/components/error-details.tsx b/packages/kilo-ui/src/components/error-details.tsx index c720bd80f8c..1a306385865 100644 --- a/packages/kilo-ui/src/components/error-details.tsx +++ b/packages/kilo-ui/src/components/error-details.tsx @@ -11,7 +11,7 @@ export function ErrorDetails(props: ErrorDetailsProps) { return (
-
{raw()}
+
{raw()}
) } diff --git a/packages/kilo-ui/src/styles/vscode-bridge.css b/packages/kilo-ui/src/styles/vscode-bridge.css index 5ad8a6a6920..534aea426c0 100644 --- a/packages/kilo-ui/src/styles/vscode-bridge.css +++ b/packages/kilo-ui/src/styles/vscode-bridge.css @@ -68,7 +68,7 @@ html[data-theme="kilo-vscode"] { --surface-critical-base: var(--vscode-editorMarkerNavigationError-headerBackground); --surface-critical-weak: var(--vscode-editorMarkerNavigationError-headerBackground); - --surface-critical-strong: var(--vscode-charts-red); + --surface-critical-strong: var(--vscode-errorForeground, var(--vscode-charts-red)); --surface-info-base: var(--vscode-editorMarkerNavigationInfo-headerBackground); --surface-info-weak: var(--vscode-editorMarkerNavigationInfo-headerBackground); @@ -116,9 +116,9 @@ html[data-theme="kilo-vscode"] { --text-on-success-base: var(--vscode-charts-green); --text-on-success-weak: var(--vscode-charts-green); --text-on-success-strong: var(--vscode-charts-green); - --text-on-critical-base: var(--vscode-charts-red); - --text-on-critical-weak: var(--vscode-charts-red); - --text-on-critical-strong: var(--vscode-charts-red); + --text-on-critical-base: var(--vscode-errorForeground, var(--vscode-charts-red)); + --text-on-critical-weak: var(--vscode-errorForeground, var(--vscode-charts-red)); + --text-on-critical-strong: var(--vscode-errorForeground, var(--vscode-charts-red)); --text-on-warning-base: var(--vscode-charts-yellow); --text-on-warning-weak: var(--vscode-charts-yellow); --text-on-warning-strong: var(--vscode-charts-yellow); @@ -183,9 +183,9 @@ html[data-theme="kilo-vscode"] { --border-warning-base: var(--vscode-charts-yellow); --border-warning-hover: var(--vscode-charts-yellow); --border-warning-selected: var(--vscode-charts-yellow); - --border-critical-base: var(--vscode-charts-red); - --border-critical-hover: var(--vscode-charts-red); - --border-critical-selected: var(--vscode-charts-red); + --border-critical-base: var(--vscode-inputValidation-errorBorder, var(--vscode-errorForeground, var(--vscode-charts-red))); + --border-critical-hover: var(--vscode-inputValidation-errorBorder, var(--vscode-errorForeground, var(--vscode-charts-red))); + --border-critical-selected: var(--vscode-inputValidation-errorBorder, var(--vscode-errorForeground, var(--vscode-charts-red))); --border-info-base: var(--vscode-charts-blue); --border-info-hover: var(--vscode-charts-blue); --border-info-selected: var(--vscode-charts-blue); @@ -221,9 +221,9 @@ html[data-theme="kilo-vscode"] { --icon-warning-base: var(--vscode-charts-yellow); --icon-warning-hover: var(--vscode-charts-yellow); --icon-warning-active: var(--vscode-charts-yellow); - --icon-critical-base: var(--vscode-charts-red); - --icon-critical-hover: var(--vscode-charts-red); - --icon-critical-active: var(--vscode-charts-red); + --icon-critical-base: var(--vscode-errorForeground, var(--vscode-charts-red)); + --icon-critical-hover: var(--vscode-errorForeground, var(--vscode-charts-red)); + --icon-critical-active: var(--vscode-errorForeground, var(--vscode-charts-red)); --icon-info-base: var(--vscode-charts-blue); --icon-info-hover: var(--vscode-charts-blue); --icon-info-active: var(--vscode-charts-blue); @@ -239,9 +239,9 @@ html[data-theme="kilo-vscode"] { --icon-on-warning-base: var(--vscode-charts-yellow); --icon-on-warning-hover: var(--vscode-charts-yellow); --icon-on-warning-selected: var(--vscode-charts-yellow); - --icon-on-critical-base: var(--vscode-charts-red); - --icon-on-critical-hover: var(--vscode-charts-red); - --icon-on-critical-selected: var(--vscode-charts-red); + --icon-on-critical-base: var(--vscode-errorForeground, var(--vscode-charts-red)); + --icon-on-critical-hover: var(--vscode-errorForeground, var(--vscode-charts-red)); + --icon-on-critical-selected: var(--vscode-errorForeground, var(--vscode-charts-red)); --icon-on-info-base: var(--vscode-charts-blue); --icon-on-info-hover: var(--vscode-charts-blue); --icon-on-info-selected: var(--vscode-charts-blue); @@ -273,7 +273,7 @@ html[data-theme="kilo-vscode"] { --syntax-object: var(--vscode-editor-foreground); --syntax-success: var(--vscode-charts-green); --syntax-warning: var(--vscode-charts-yellow); - --syntax-critical: var(--vscode-charts-red); + --syntax-critical: var(--vscode-errorForeground, var(--vscode-charts-red)); --syntax-info: var(--vscode-charts-blue); --syntax-diff-add: var(--vscode-gitDecoration-addedResourceForeground); --syntax-diff-delete: var(--vscode-gitDecoration-deletedResourceForeground); diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ErrorDisplay.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ErrorDisplay.tsx index ef600bccfc3..52ae08d88e6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ErrorDisplay.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ErrorDisplay.tsx @@ -3,6 +3,7 @@ import { Card } from "@kilocode/kilo-ui/card" import { Collapsible } from "@kilocode/kilo-ui/collapsible" import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { ErrorDetails } from "@kilocode/kilo-ui/error-details" +import { Icon } from "@kilocode/kilo-ui/icon" import { Button } from "@kilocode/kilo-ui/button" import type { AssistantMessage } from "@kilocode/sdk/v2" import { useLanguage } from "../../context/language" @@ -62,8 +63,11 @@ export const ErrorDisplay: Component = (props) => { return ( - {errorText()} + +
+ +
{errorText()}
+
{t("error.details.show")} diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx index b8b41bcaf92..692960c8e2d 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -8,8 +8,10 @@ */ import type { Meta, StoryObj } from "storybook-solidjs-vite" +import type { AssistantMessage } from "@kilocode/sdk/v2" import { StoryProviders, defaultMockData, mockSessionValue } from "./StoryProviders" import { ChatView } from "../components/chat/ChatView" +import { ErrorDisplay } from "../components/chat/ErrorDisplay" import { TaskHeader } from "../components/chat/TaskHeader" import { QuestionDock } from "../components/chat/QuestionDock" import { SuggestBar } from "../components/chat/SuggestBar" @@ -76,6 +78,72 @@ const reviewSuggestion: SuggestionRequest = { tool: { messageID: "asst-msg-002", callID: "call-suggest-001" }, } +const policyMessage = + "No endpoints found matching your data policy (Free model training). Configure: https://openrouter.ai/settings/privacy" + +const policyError: NonNullable = { + name: "APIError", + data: { + message: policyMessage, + statusCode: 400, + isRetryable: false, + responseBody: JSON.stringify( + { + error: { + type: "Bad Request", + message: "Data collection is required for this model. Please enable data collection to use this model.", + }, + }, + null, + 2, + ), + }, +} + +function BeforeErrorDisplay() { + return ( +
+
{policyMessage}
+
+ +
+
+ ) +} + // --------------------------------------------------------------------------- // Meta // --------------------------------------------------------------------------- @@ -231,6 +299,40 @@ export const SuggestBarReview: Story = { ), } +export const ErrorDisplayDataPolicy: Story = { + name: "ErrorDisplay — data policy", + render: () => ( + +
+ +
+
+ ), +} + +export const ErrorDisplayBeforeAfter: Story = { + name: "ErrorDisplay — before / after", + render: () => ( + +
+
+ Compare the previous error treatment reconstructed from pre-change CSS with the updated readable card. +
+
+
+

Before

+ +
+
+

After

+ +
+
+
+
+ ), +} + const toolUserID = "user-msg-spacing-001" const toolAssistantID = "asst-msg-spacing-001" const queuedUserID = "user-msg-spacing-002" From d02c3a6bf31f2a696363d136d8b4cffd57dc6aba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 27 May 2026 02:51:48 +0000 Subject: [PATCH 02/59] chore: update kilo-vscode visual regression baselines --- .../chat/error-display-before-after-chromium-linux.png | 3 +++ .../chat/error-display-data-policy-chromium-linux.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-data-policy-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png new file mode 100644 index 00000000000..7ff28e5ac05 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de207e3a717fe43dbb297ebebf018b14b010cee02fb060135c41bf8f5a599c68 +size 24422 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-data-policy-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-data-policy-chromium-linux.png new file mode 100644 index 00000000000..010968eb6a0 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-data-policy-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3050c782d797be7dad951b76199df2c87195415e699d3e8481bbf40ad0dfccf +size 10073 From 7d2dbb0ae206f5cc3d39a06c2a3dbe3c631373ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Uruchurtu?= Date: Tue, 26 May 2026 20:56:07 -0600 Subject: [PATCH 03/59] chore(vscode): trim error story coverage --- ...or-display-before-after-chromium-linux.png | 3 - .../webview-ui/src/stories/chat.stories.tsx | 67 ------------------- 2 files changed, 70 deletions(-) delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png deleted file mode 100644 index 7ff28e5ac05..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-before-after-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de207e3a717fe43dbb297ebebf018b14b010cee02fb060135c41bf8f5a599c68 -size 24422 diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx index 692960c8e2d..4d966d9d116 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -100,50 +100,6 @@ const policyError: NonNullable = { }, } -function BeforeErrorDisplay() { - return ( -
-
{policyMessage}
-
- -
-
- ) -} - // --------------------------------------------------------------------------- // Meta // --------------------------------------------------------------------------- @@ -310,29 +266,6 @@ export const ErrorDisplayDataPolicy: Story = { ), } -export const ErrorDisplayBeforeAfter: Story = { - name: "ErrorDisplay — before / after", - render: () => ( - -
-
- Compare the previous error treatment reconstructed from pre-change CSS with the updated readable card. -
-
-
-

Before

- -
-
-

After

- -
-
-
-
- ), -} - const toolUserID = "user-msg-spacing-001" const toolAssistantID = "asst-msg-spacing-001" const queuedUserID = "user-msg-spacing-002" From 4e6f366a75c71b6c5a2e3499e116b61c21355fbe Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 27 May 2026 13:49:03 +0200 Subject: [PATCH 04/59] fix(vscode): add context handoff for forked sessions --- .changeset/forked-session-context.md | 5 ++ .../src/agent-manager/AgentManagerProvider.ts | 1 + .../src/agent-manager/continue-in-worktree.ts | 4 + .../src/agent-manager/fork-handoff.ts | 41 +++++++++ .../src/agent-manager/fork-session.ts | 10 ++- .../src/kilo-provider/fork-session.ts | 1 + .../tests/unit/continue-in-worktree.test.ts | 17 +++- .../tests/unit/fork-handoff.test.ts | 42 +++++++++ .../tests/unit/fork-session.test.ts | 87 +++++++++++++++++++ 9 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 .changeset/forked-session-context.md create mode 100644 packages/kilo-vscode/src/agent-manager/fork-handoff.ts create mode 100644 packages/kilo-vscode/tests/unit/fork-handoff.test.ts create mode 100644 packages/kilo-vscode/tests/unit/fork-session.test.ts diff --git a/.changeset/forked-session-context.md b/.changeset/forked-session-context.md new file mode 100644 index 00000000000..b3061ce95e7 --- /dev/null +++ b/.changeset/forked-session-context.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Preserve prior context in forked sessions while recognizing the selected direction and current worktree context. diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index d14dcbaf8ec..dd16ecc6098 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -1152,6 +1152,7 @@ export class AgentManagerProvider implements Disposable { { getClient: () => this.connectionService.getClient(), state: this.getStateManager(), + directory: this.getRoot(), postError: (msg) => this.postToWebview({ type: "error", message: msg }), registerWorktreeSession: (sid, dir) => this.registerWorktreeSession(sid, dir), pushState: () => this.pushState(), diff --git a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts index e25cb355588..cd49f2d2064 100644 --- a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts +++ b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts @@ -4,6 +4,7 @@ import type { WorktreeStateManager } from "./WorktreeStateManager" import { capture as captureGitState, apply as applyGitState, type GitSnapshot } from "./git-transfer" import { getErrorMessage } from "../kilo-provider-utils" import { PLATFORM } from "./constants" +import { recordForkHandoff } from "./fork-handoff" export interface ContinueContext { root: string @@ -82,6 +83,9 @@ export async function forkSession(ctx: ContinueContext, sessionId: string, dir: } try { const { data } = await client.session.fork({ sessionID: sessionId, directory: dir }, { throwOnError: true }) + await recordForkHandoff({ client, sessionId: data.id, directory: dir }).catch((err) => { + ctx.log("Failed to record fork handoff:", getErrorMessage(err)) + }) return { ok: true, value: data } } catch (err) { return { ok: false, error: `Failed to fork session: ${getErrorMessage(err)}` } diff --git a/packages/kilo-vscode/src/agent-manager/fork-handoff.ts b/packages/kilo-vscode/src/agent-manager/fork-handoff.ts new file mode 100644 index 00000000000..efbf36361c9 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/fork-handoff.ts @@ -0,0 +1,41 @@ +import type { KiloClient } from "@kilocode/sdk/v2/client" + +export interface ForkHandoffInput { + client: KiloClient + sessionId: string + directory?: string +} + +export function forkText(input: Pick): string { + return [ + "", + "This session was forked from an existing session in the current repository or worktree.", + ...(input.directory + ? [ + `Use this as the current working directory: ${input.directory}`, + "For this fork, this location supersedes any earlier repository or worktree location retained in the copied context.", + ] + : []), + "The prior conversation context was retained intentionally.", + "The user may continue the same task, explore an alternative approach, or provide new instructions.", + "Follow the user's next instruction as the direction for this fork, using retained context when relevant.", + "", + ].join("\n") +} + +export async function recordForkHandoff(input: ForkHandoffInput): Promise { + const payload = { + sessionID: input.sessionId, + ...(input.directory ? { directory: input.directory } : {}), + noReply: true, + parts: [ + { + type: "text", + text: forkText(input), + synthetic: true, + }, + ], + } as Parameters[0] + + await input.client.session.promptAsync(payload, { throwOnError: true }) +} diff --git a/packages/kilo-vscode/src/agent-manager/fork-session.ts b/packages/kilo-vscode/src/agent-manager/fork-session.ts index c78e8086b14..d6d4118c6aa 100644 --- a/packages/kilo-vscode/src/agent-manager/fork-session.ts +++ b/packages/kilo-vscode/src/agent-manager/fork-session.ts @@ -3,10 +3,12 @@ import { getErrorMessage } from "../kilo-provider-utils" import { TelemetryProxy, TelemetryEventName } from "../services/telemetry" import type { WorktreeStateManager } from "./WorktreeStateManager" import { PLATFORM } from "./constants" +import { recordForkHandoff } from "./fork-handoff" export interface ForkContext { getClient: () => KiloClient state: WorktreeStateManager | undefined + directory: string | undefined postError: (message: string) => void registerWorktreeSession: (sessionId: string, directory: string) => void pushState: () => void @@ -37,8 +39,8 @@ export async function forkSession( } const directory = (() => { - if (!worktreeId || !ctx.state) return undefined - return ctx.state.getWorktree(worktreeId)?.path + if (!worktreeId || !ctx.state) return ctx.directory + return ctx.state.getWorktree(worktreeId)?.path ?? ctx.directory })() let forked: Session @@ -63,6 +65,10 @@ export async function forkSession( if (directory) ctx.registerWorktreeSession(forked.id, directory) } + await recordForkHandoff({ client, sessionId: forked.id, directory }).catch((err) => { + ctx.log("forkSession: failed to record fork handoff:", getErrorMessage(err)) + }) + ctx.pushState() ctx.notifyForked(forked, sessionId, worktreeId) ctx.registerSession(forked) diff --git a/packages/kilo-vscode/src/kilo-provider/fork-session.ts b/packages/kilo-vscode/src/kilo-provider/fork-session.ts index 1416320581c..d97fc213a0b 100644 --- a/packages/kilo-vscode/src/kilo-provider/fork-session.ts +++ b/packages/kilo-vscode/src/kilo-provider/fork-session.ts @@ -32,6 +32,7 @@ export async function handleForkSession(ctx: ForkContext, sessionId: string, mes { getClient: () => ctx.connection.getClient(), state: undefined, + directory: ctx.directory(sessionId), postError: (message) => ctx.post({ type: "error", message }), registerWorktreeSession: () => {}, pushState: () => {}, diff --git a/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts b/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts index c2dab4a182a..4b72acdcac0 100644 --- a/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts +++ b/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "bun:test" +import { describe, expect, it, mock } from "bun:test" import { abortSession, captureState, @@ -8,6 +8,7 @@ import { type StepResult, } from "../../src/agent-manager/continue-in-worktree" import type { CreateWorktreeResult } from "../../src/agent-manager/WorktreeManager" +import { forkText } from "../../src/agent-manager/fork-handoff" import type { Session } from "@kilocode/sdk/v2/client" const noop = () => {} @@ -100,17 +101,27 @@ describe("continue-in-worktree steps", () => { if (!res.ok) expect(res.error).toContain("fork failed") }) - it("returns forked session on success", async () => { + it("records handoff instructions for the forked worktree session", async () => { const forked = session("forked-1") + const promptAsync = mock(async () => ({})) const c = ctx({ getClient: () => ({ - session: { fork: () => Promise.resolve({ data: forked }) }, + session: { fork: () => Promise.resolve({ data: forked }), promptAsync }, }) as never, }) const res = await forkSession(c, "session-1", "/tmp/wt") expect(res.ok).toBe(true) if (res.ok) expect(res.value.id).toBe("forked-1") + expect(promptAsync).toHaveBeenCalledWith( + { + sessionID: "forked-1", + directory: "/tmp/wt", + noReply: true, + parts: [{ type: "text", text: forkText({ directory: "/tmp/wt" }), synthetic: true }], + }, + { throwOnError: true }, + ) }) }) diff --git a/packages/kilo-vscode/tests/unit/fork-handoff.test.ts b/packages/kilo-vscode/tests/unit/fork-handoff.test.ts new file mode 100644 index 00000000000..9fb775cd220 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/fork-handoff.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, mock } from "bun:test" +import { forkText, recordForkHandoff } from "../../src/agent-manager/fork-handoff" + +describe("fork handoff", () => { + it("describes retained context without assuming a new task", () => { + const text = forkText({ directory: "/repo/.kilo/worktrees/feature" }) + + expect(text).toContain("This session was forked from an existing session in the current repository or worktree.") + expect(text).toContain("Use this as the current working directory: /repo/.kilo/worktrees/feature") + expect(text).toContain("this location supersedes any earlier repository or worktree location") + expect(text).toContain("The prior conversation context was retained intentionally.") + expect(text).toContain("continue the same task, explore an alternative approach, or provide new instructions") + expect(text).toContain("Follow the user's next instruction as the direction for this fork") + }) + + it("records a hidden no-reply handoff in the forked session", async () => { + const promptAsync = mock(async () => ({})) + const client = { session: { promptAsync } } + + await recordForkHandoff({ + client: client as never, + sessionId: "session-fork", + directory: "/repo/.kilo/worktrees/feature", + }) + + expect(promptAsync).toHaveBeenCalledWith( + { + sessionID: "session-fork", + directory: "/repo/.kilo/worktrees/feature", + noReply: true, + parts: [ + { + type: "text", + text: forkText({ directory: "/repo/.kilo/worktrees/feature" }), + synthetic: true, + }, + ], + }, + { throwOnError: true }, + ) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/fork-session.test.ts b/packages/kilo-vscode/tests/unit/fork-session.test.ts new file mode 100644 index 00000000000..b5526c2688c --- /dev/null +++ b/packages/kilo-vscode/tests/unit/fork-session.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it, mock } from "bun:test" +import type { Session } from "@kilocode/sdk/v2/client" +import { forkText } from "../../src/agent-manager/fork-handoff" +import { forkSession, type ForkContext } from "../../src/agent-manager/fork-session" + +const noop = () => {} + +function session(id: string): Session { + return { id, title: id, createdAt: "", updatedAt: "" } as Session +} + +function ctx(client: unknown, overrides: Partial = {}): ForkContext { + return { + getClient: () => client as never, + state: undefined, + directory: "/repo", + postError: noop, + registerWorktreeSession: noop, + pushState: noop, + notifyForked: noop, + registerSession: noop, + log: noop, + ...overrides, + } +} + +describe("agent manager fork session", () => { + it("records the hidden handoff in the current repository", async () => { + const fork = mock(async () => ({ data: session("forked") })) + const promptAsync = mock(async () => ({})) + const client = { session: { fork, promptAsync } } + + await forkSession(ctx(client), "source", undefined, "message") + + expect(fork).toHaveBeenCalledWith( + { sessionID: "source", directory: "/repo", messageID: "message" }, + { throwOnError: true }, + ) + expect(promptAsync).toHaveBeenCalledWith( + { + sessionID: "forked", + directory: "/repo", + noReply: true, + parts: [{ type: "text", text: forkText({ directory: "/repo" }), synthetic: true }], + }, + { throwOnError: true }, + ) + }) + + it("uses the selected worktree directory for the handoff", async () => { + const fork = mock(async () => ({ data: session("forked") })) + const promptAsync = mock(async () => ({})) + const client = { session: { fork, promptAsync } } + const state = { + getWorktree: () => ({ path: "/repo/.kilo/worktrees/feature" }), + addSession: mock(() => undefined), + } + + await forkSession(ctx(client, { state: state as never }), "source", "worktree") + + expect(fork).toHaveBeenCalledWith( + { sessionID: "source", directory: "/repo/.kilo/worktrees/feature" }, + { throwOnError: true }, + ) + expect(promptAsync).toHaveBeenCalledWith(expect.objectContaining({ directory: "/repo/.kilo/worktrees/feature" }), { + throwOnError: true, + }) + }) + + it("still exposes the fork when recording the handoff fails", async () => { + const notify = mock(() => undefined) + const log = mock(() => undefined) + const client = { + session: { + fork: mock(async () => ({ data: session("forked") })), + promptAsync: mock(async () => { + throw new Error("handoff failed") + }), + }, + } + + await forkSession(ctx(client, { notifyForked: notify, log }), "source") + + expect(notify).toHaveBeenCalledWith(expect.objectContaining({ id: "forked" }), "source", undefined) + expect(log).toHaveBeenCalledWith("forkSession: failed to record fork handoff:", "handoff failed") + }) +}) From a411ba9321020f30f3e4454aafc9d7b14f6d1e08 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 12:07:12 -0400 Subject: [PATCH 05/59] ci(jetbrains): add release PR publishing flow --- .../workflows/prepare-jetbrains-release.yml | 64 ++++++ .github/workflows/publish-jetbrains.yml | 21 +- .github/workflows/tag-jetbrains-release.yml | 45 +++++ packages/kilo-jetbrains/CHANGELOG.md | 8 + packages/kilo-jetbrains/RELEASE_TODO.md | 18 +- packages/kilo-jetbrains/RELEASING.md | 120 ++++++++--- packages/kilo-jetbrains/build.gradle.kts | 25 ++- .../kilo-jetbrains/gradle/libs.versions.toml | 2 + script/check-workflows.ts | 2 + script/jetbrains-release-pr.ts | 186 ++++++++++++++++++ script/jetbrains-release-tag.ts | 78 ++++++++ 11 files changed, 527 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/prepare-jetbrains-release.yml create mode 100644 .github/workflows/tag-jetbrains-release.yml create mode 100644 packages/kilo-jetbrains/CHANGELOG.md create mode 100644 script/jetbrains-release-pr.ts create mode 100644 script/jetbrains-release-tag.ts diff --git a/.github/workflows/prepare-jetbrains-release.yml b/.github/workflows/prepare-jetbrains-release.yml new file mode 100644 index 00000000000..0ba90451fba --- /dev/null +++ b/.github/workflows/prepare-jetbrains-release.yml @@ -0,0 +1,64 @@ +# kilocode_change - new file +name: prepare-jetbrains-release + +on: + workflow_dispatch: + inputs: + kind: + description: "Release kind" + required: true + type: choice + options: + - rc + - stable + version: + description: "Version, e.g. 7.3.13-rc.1 or 7.3.13" + required: true + type: string + from_tag: + description: "Optional previous tag for changelog range" + required: false + type: string + +permissions: + contents: write + pull-requests: write + +concurrency: + group: prepare-jetbrains-release-${{ inputs.version }} + cancel-in-progress: false + +jobs: + prepare: + if: github.repository == 'Kilo-Org/kilocode' + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Setup Git Committer + id: committer + uses: ./.github/actions/setup-git-committer + with: + kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} + kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} + + - name: Create or update release PR + run: | + args=(--kind "$KIND" --version "$VERSION") + if [[ -n "$FROM_TAG" ]]; then + args+=(--from-tag "$FROM_TAG") + fi + bun script/jetbrains-release-pr.ts "${args[@]}" + env: + GH_TOKEN: ${{ steps.committer.outputs.token }} + GH_REPO: ${{ github.repository }} + KIND: ${{ inputs.kind }} + VERSION: ${{ inputs.version }} + FROM_TAG: ${{ inputs.from_tag }} diff --git a/.github/workflows/publish-jetbrains.yml b/.github/workflows/publish-jetbrains.yml index 4db34d33be8..4f545457757 100644 --- a/.github/workflows/publish-jetbrains.yml +++ b/.github/workflows/publish-jetbrains.yml @@ -23,6 +23,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4 @@ -73,8 +75,7 @@ jobs: echo "marketplace_channel=default" echo "cli_channel=latest" } >> "$GITHUB_OUTPUT" - echo "Stable JetBrains Marketplace publishing is implemented but intentionally disabled; use an rc tag such as jetbrains/v7.0.1-rc.1." >&2 - exit 1 + exit 0 fi echo "Unsupported JetBrains plugin version '$version'. Expected jetbrains/vx.y.z-rc.n or jetbrains/vx.y.z." >&2 @@ -111,6 +112,12 @@ jobs: env: CHANNEL: ${{ steps.version.outputs.marketplace_channel }} + - name: Render release notes + working-directory: packages/kilo-jetbrains + run: ./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md + env: + VERSION: ${{ steps.version.outputs.version }} + - name: Publish to JetBrains Marketplace working-directory: packages/kilo-jetbrains run: ./gradlew publishPlugin -Pproduction=true -Pkilo.channel="$CHANNEL" @@ -147,16 +154,22 @@ jobs: run: | tag="$GITHUB_REF_NAME" title="JetBrains $VERSION" - notes="JetBrains plugin $VERSION." + flags=(--title "$title" --notes-file "$NOTES") + if [[ "$KIND" == "rc" ]]; then + flags+=(--prerelease) + fi if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then gh release upload "$tag" "$ARCHIVE" --clobber --repo "$GITHUB_REPOSITORY" + gh release edit "$tag" "${flags[@]}" --repo "$GITHUB_REPOSITORY" exit 0 fi - gh release create "$tag" "$ARCHIVE" --title "$title" --notes "$notes" --prerelease --repo "$GITHUB_REPOSITORY" + gh release create "$tag" "$ARCHIVE" "${flags[@]}" --repo "$GITHUB_REPOSITORY" env: GH_TOKEN: ${{ github.token }} VERSION: ${{ steps.version.outputs.version }} + KIND: ${{ steps.version.outputs.kind }} ARCHIVE: ${{ steps.archive.outputs.path }} + NOTES: packages/kilo-jetbrains/build/release-notes.md - name: Upload workflow artifact if: always() diff --git a/.github/workflows/tag-jetbrains-release.yml b/.github/workflows/tag-jetbrains-release.yml new file mode 100644 index 00000000000..55913f0d47e --- /dev/null +++ b/.github/workflows/tag-jetbrains-release.yml @@ -0,0 +1,45 @@ +# kilocode_change - new file +name: tag-jetbrains-release + +on: + pull_request: + types: + - closed + +permissions: + contents: write + pull-requests: read + +jobs: + tag: + if: >- + github.repository == 'Kilo-Org/kilocode' && + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') && + contains(github.event.pull_request.labels.*.name, 'jetbrains-release') && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Setup Git Committer + id: committer + uses: ./.github/actions/setup-git-committer + with: + kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} + kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} + + - name: Create release tag + run: bun script/jetbrains-release-tag.ts --pr "$PR_NUMBER" + env: + GH_TOKEN: ${{ steps.committer.outputs.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/packages/kilo-jetbrains/CHANGELOG.md b/packages/kilo-jetbrains/CHANGELOG.md new file mode 100644 index 00000000000..6e145106e57 --- /dev/null +++ b/packages/kilo-jetbrains/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## [Unreleased] + +## [0.0.0-dev] + +### Added +- Initial JetBrains plugin development builds. diff --git a/packages/kilo-jetbrains/RELEASE_TODO.md b/packages/kilo-jetbrains/RELEASE_TODO.md index 1f8efae536f..fcb1e36099c 100644 --- a/packages/kilo-jetbrains/RELEASE_TODO.md +++ b/packages/kilo-jetbrains/RELEASE_TODO.md @@ -14,6 +14,7 @@ - Create a JetBrains Marketplace permanent token from Marketplace `My Tokens`. - Add `JETBRAINS_MARKETPLACE_TOKEN` to GitHub Actions secrets or the protected environment. - Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases from `jetbrains/v*` tags. +- Confirm `KILO_MAINTAINER_APP_ID` and `KILO_MAINTAINER_APP_SECRET` are available to create release PRs and tags. - Optionally create a protected `jetbrains-marketplace` GitHub Environment with required reviewers. - If using an environment, move the Marketplace and signing secrets there and set the workflow job environment. @@ -29,16 +30,21 @@ ## Per-RC Release - Choose an RC version in the form `x.y.z-rc.n`. -- Push tag `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`. +- Run the `prepare-jetbrains-release` workflow with `kind=rc` and version `x.y.z-rc.n`. +- Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR. +- Merge the release PR to create tag `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`. - Watch the `publish-jetbrains` workflow. - Download and retain the workflow artifact if needed. - Confirm the update appears on the JetBrains Marketplace `eap` channel. - Confirm the GitHub Release for the `jetbrains/vx.y.z-rc.n` tag exists and contains the JetBrains plugin ZIP asset. - Share `https://plugins.jetbrains.com/plugins/eap/list` with testers. -## Stable Release Guard +## Per-Stable Release -- Stable tags like `jetbrains/vx.y.z` are intentionally rejected for now. -- Before enabling stable releases, remove the workflow stable guard. -- Verify `kilo.channel=default` publishes to the default Marketplace channel. -- Update this checklist before stable releases are enabled. +- Choose a stable version in the form `x.y.z`. +- Run the `prepare-jetbrains-release` workflow with `kind=stable` and version `x.y.z`. +- Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR. +- Merge the release PR to create tag `jetbrains/vx.y.z`. +- Watch the `publish-jetbrains` workflow. +- Confirm the update appears on the default JetBrains Marketplace channel. +- Confirm the GitHub Release for the `jetbrains/vx.y.z` tag exists and contains the JetBrains plugin ZIP asset. diff --git a/packages/kilo-jetbrains/RELEASING.md b/packages/kilo-jetbrains/RELEASING.md index a5d223cb501..f20b292c61e 100644 --- a/packages/kilo-jetbrains/RELEASING.md +++ b/packages/kilo-jetbrains/RELEASING.md @@ -1,62 +1,120 @@ # Releasing the JetBrains Plugin -## RC releases (currently the only supported flow) +JetBrains releases use a release PR. The PR is where maintainers review and edit the version and changelog before anything is published. -Stable release tags (`jetbrains/vx.y.z`) are recognized by the workflow but intentionally rejected. Only RC tags are accepted right now. +## Create a Release PR -### 1. Create and push a tag +1. Open the GitHub Actions workflow: -Tag format: `jetbrains/v..-rc.` +[https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml) -``` -git tag jetbrains/v7.0.1-rc.1 -git push origin jetbrains/v7.0.1-rc.1 +2. Click **Run workflow**. + +3. Fill the inputs: + +| Input | Value | +|---|---| +| `kind` | `rc` for an EAP release, `stable` for a default Marketplace release. | +| `version` | `x.y.z-rc.n` for RCs, `x.y.z` for stable releases. | +| `from_tag` | Optional previous tag for the changelog range. Leave empty unless the default range is wrong. | + +Examples: + +```text +kind=rc +version=7.3.13-rc.1 ``` -### 2. Watch the workflow +```text +kind=stable +version=7.3.13 +``` -The `publish-jetbrains` workflow starts automatically on tag push. Follow progress at: +## Changelog Range Defaults + +The workflow chooses a changelog base automatically: + +| Release | Default `from_tag` | +|---|---| +| First RC for a version, e.g. `7.3.13-rc.1` | Latest stable JetBrains tag. | +| Later RC, e.g. `7.3.13-rc.2` | Previous RC for the same base version. | +| Stable, e.g. `7.3.13` | Latest stable JetBrains tag, ignoring RCs. | + +Use `from_tag` only to override this comparison range. + +## Review the PR + +The workflow creates or updates a branch like: + +```text +jetbrains/release/v7.3.13-rc.1 +``` + +The PR updates: + +| File | Purpose | +|---|---| +| `packages/kilo-jetbrains/package.json` | JetBrains plugin package version. | +| `packages/kilo-jetbrains/CHANGELOG.md` | Release notes packaged into the plugin. | + +Review and edit `packages/kilo-jetbrains/CHANGELOG.md` before merging. This changelog entry is rendered into JetBrains ``, so it appears on the Marketplace and inside IntelliJ plugin UI. + +## Merge and Publish + +When the release PR is merged, the `tag-jetbrains-release` workflow validates it and creates: + +```text +jetbrains/v +``` + +That tag triggers the `publish-jetbrains` workflow: [https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml) -The workflow: -1. Validates the tag format and required secrets. -2. Downloads CLI binaries for all 6 platforms from the matching GitHub Release. -3. Verifies and signs the plugin with `./gradlew verifyPlugin publishPlugin -Pproduction=true`. -4. Publishes the signed ZIP to the JetBrains Marketplace `eap` channel. -5. Uploads the signed ZIP to a GitHub prerelease for the tag. +Publishing behavior: -### 3. Verify on the Marketplace +| Version | Marketplace channel | GitHub release | +|---|---|---| +| `x.y.z-rc.n` | `eap` | Prerelease | +| `x.y.z` | default | Stable release | -Once the workflow succeeds, the new version should appear in the plugin's version list: +The workflow verifies, signs, and publishes the plugin ZIP, then uploads the ZIP to the matching GitHub Release. -[https://plugins.jetbrains.com/plugin/28350-kilo-code/edit/versions](https://plugins.jetbrains.com/plugin/28350-kilo-code/edit/versions) +## Installing RC Builds ---- - -## Installing RC builds via the custom plugin repository - -RC builds are published to the `eap` channel, not the default channel. To get them in IntelliJ IDEA: +RC builds are published to the `eap` channel. To get them in IntelliJ IDEA: 1. Open **Settings > Plugins**. 2. Click the gear icon and choose **Manage Plugin Repositories**. 3. Add the following URL: -``` +```text https://plugins.jetbrains.com/plugins/list?channel=eap&pluginId=28350 ``` -4. Search for **Kilo Code** in the Marketplace tab — the latest RC version will appear and update automatically. +4. Search for **Kilo Code** in the Marketplace tab. ---- +## Manual Recovery -## Required GitHub Actions secrets +If the PR was merged but the tag workflow failed after validation, create the tag manually at the merge commit: + +```bash +git fetch origin main +git tag jetbrains/v7.3.13 +git push origin jetbrains/v7.3.13 +``` + +Do not create a tag before the release PR is merged unless intentionally bypassing the release-PR flow. + +## Required GitHub Actions Secrets | Secret | Purpose | |---|---| -| `JETBRAINS_MARKETPLACE_TOKEN` | Marketplace API token for publishing | -| `JETBRAINS_CERTIFICATE_CHAIN` | PEM certificate chain for plugin signing | -| `JETBRAINS_PRIVATE_KEY` | PEM private key for plugin signing | -| `JETBRAINS_PRIVATE_KEY_PASSWORD` | Password for the private key | +| `KILO_MAINTAINER_APP_ID` | GitHub App ID used to create/update release PRs and tags. | +| `KILO_MAINTAINER_APP_SECRET` | GitHub App private key used to create/update release PRs and tags. | +| `JETBRAINS_MARKETPLACE_TOKEN` | Marketplace API token for publishing. | +| `JETBRAINS_CERTIFICATE_CHAIN` | PEM certificate chain for plugin signing. | +| `JETBRAINS_PRIVATE_KEY` | PEM private key for plugin signing. | +| `JETBRAINS_PRIVATE_KEY_PASSWORD` | Password for the private key. | Before the first publish, complete `RELEASE_TODO.md` to set up these secrets and the Marketplace plugin entry. diff --git a/packages/kilo-jetbrains/build.gradle.kts b/packages/kilo-jetbrains/build.gradle.kts index af2d807656c..95c83977136 100644 --- a/packages/kilo-jetbrains/build.gradle.kts +++ b/packages/kilo-jetbrains/build.gradle.kts @@ -1,7 +1,9 @@ +import org.jetbrains.changelog.Changelog import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware.SplitModeTarget +import java.time.LocalDate group = "ai.kilocode.jetbrains" @@ -44,7 +46,6 @@ val ver = if (release) checked( ?: error("Missing JetBrains plugin version. Publish builds must run from a jetbrains/v tag."), ) else checked(gitTag()?.removePrefix("jetbrains/v") ?: "0.0.0-dev") -val notes = providers.gradleProperty("kilo.changeNotes").orElse("Release candidate build.") val channel = providers.gradleProperty("kilo.channel").map { it.trim() }.orElse("default") val splitPort = providers.gradleProperty("kilo.splitModeServerPort").orNull?.let(::port) ?: fallback() val isolated = providers.gradleProperty("kilo.dev.storage.isolated").map { it.toBoolean() }.orElse(false) @@ -59,12 +60,34 @@ plugins { id("java") alias(libs.plugins.intellij.platform) alias(libs.plugins.detekt) + alias(libs.plugins.changelog) alias(libs.plugins.kotlin) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.compose.compiler) apply false } +changelog { + version = ver + path = file("CHANGELOG.md").canonicalPath + header = provider { "[${version.get()}] - ${LocalDate.now()}" } + unreleasedTerm = "[Unreleased]" + keepUnreleasedSection = true + repositoryUrl = "https://github.com/Kilo-Org/kilocode" + groups = listOf("Added", "Changed", "Fixed", "Removed", "Security") + combinePreReleases = false +} + +val notes = providers.gradleProperty("kilo.changeNotes").orElse( + provider { + val item = if (changelog.has(ver)) changelog.get(ver) else changelog.getUnreleased() + changelog.renderItem( + item.withHeader(false).withEmptySections(false), + Changelog.OutputType.HTML, + ) + }, +) + subprojects { apply(plugin = "org.jetbrains.intellij.platform.module") apply(plugin = "io.gitlab.arturbosch.detekt") diff --git a/packages/kilo-jetbrains/gradle/libs.versions.toml b/packages/kilo-jetbrains/gradle/libs.versions.toml index 267d49a8159..b255dc98c7b 100644 --- a/packages/kilo-jetbrains/gradle/libs.versions.toml +++ b/packages/kilo-jetbrains/gradle/libs.versions.toml @@ -10,6 +10,7 @@ openapi-generator = "7.21.0" detekt = "1.23.8" commonmark = "0.28.0" zxing = "3.5.3" +changelog = "2.5.0" [libraries] commonmark = { module = "org.commonmark:commonmark", version.ref = "commonmark" } @@ -31,3 +32,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-jvm-plugin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-serialization-plugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-jvm-plugin" } openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } +changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } diff --git a/script/check-workflows.ts b/script/check-workflows.ts index addb9e9ba8f..a3657379903 100644 --- a/script/check-workflows.ts +++ b/script/check-workflows.ts @@ -43,10 +43,12 @@ const active = new Set([ "generate.yml", "nix-eval.yml", "nix-hashes.yml", + "prepare-jetbrains-release.yml", "publish-jetbrains.yml", "publish.yml", "smoke-test.yml", "source-check-links.yml", + "tag-jetbrains-release.yml", "test-vscode.yml", "test.yml", "triage.yml", diff --git a/script/jetbrains-release-pr.ts b/script/jetbrains-release-pr.ts new file mode 100644 index 00000000000..7a9e5b502dc --- /dev/null +++ b/script/jetbrains-release-pr.ts @@ -0,0 +1,186 @@ +#!/usr/bin/env bun +// kilocode_change - new file + +import { $ } from "bun" +import semver from "semver" +import { parseArgs } from "util" + +const pkgfile = new URL("../packages/kilo-jetbrains/package.json", import.meta.url).pathname +const log = new URL("../packages/kilo-jetbrains/CHANGELOG.md", import.meta.url).pathname +const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode" + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + kind: { type: "string" }, + version: { type: "string" }, + "from-tag": { type: "string" }, + dry: { type: "boolean", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, +}) + +if (values.help) { + console.log(` +Usage: bun script/jetbrains-release-pr.ts --kind --version [--from-tag ] [--dry] + +Examples: + bun script/jetbrains-release-pr.ts --kind rc --version 7.3.13-rc.1 + bun script/jetbrains-release-pr.ts --kind stable --version 7.3.13 +`) + process.exit(0) +} + +const kind = values.kind +const ver = values.version +const dry = values.dry ?? false + +if (kind !== "rc" && kind !== "stable") throw new Error("--kind must be rc or stable") +if (!ver) throw new Error("--version is required") +if (kind === "rc" && !/^\d+\.\d+\.\d+-rc\.\d+$/.test(ver)) throw new Error("RC versions must match x.y.z-rc.n") +if (kind === "stable" && !/^\d+\.\d+\.\d+$/.test(ver)) throw new Error("Stable versions must match x.y.z") +if (!semver.valid(ver)) throw new Error(`Invalid semver: ${ver}`) + +await $`git fetch origin main --tags` + +const tag = `jetbrains/v${ver}` +const branch = `jetbrains/release/v${ver}` +const from = values["from-tag"] ?? (await base(ver, kind)) +const notes = await release(from, tag) +const entry = section(ver, notes) + +console.log(`JetBrains ${kind} release PR`) +console.log(`version: ${ver}`) +console.log(`base: ${from}`) +console.log(`tag: ${tag}`) +console.log(`branch: ${branch}`) + +if (dry) { + console.log("\nGenerated changelog entry:\n") + console.log(entry) + console.log("\nDry run complete. No branch, commit, push, or PR was created.") + process.exit(0) +} + +await $`git checkout -B ${branch} origin/main` +await writepkg(ver) +await writelog(ver, entry) +await $`git add packages/kilo-jetbrains/package.json packages/kilo-jetbrains/CHANGELOG.md` + +const changed = await $`git diff --cached --quiet`.nothrow() +if (changed.exitCode !== 0) await $`git commit -m ${`release(jetbrains): v${ver}`}` + +await $`git push --force-with-lease origin ${branch}` + +const text = body(ver, kind, from, tag, notes) +const view = await $`gh pr view ${branch} --repo ${repo} --json number --jq .number`.nothrow() +if (view.exitCode === 0 && view.stdout.toString().trim()) { + const num = view.stdout.toString().trim() + await $`gh pr edit ${num} --repo ${repo} --title ${`release(jetbrains): v${ver}`} --body ${text}` + await $`gh pr edit ${num} --repo ${repo} --add-label jetbrains-release --add-label release`.nothrow() + console.log(`Updated PR #${num}`) + process.exit(0) +} + +const create = await $`gh pr create --repo ${repo} --base main --head ${branch} --title ${`release(jetbrains): v${ver}`} --body ${text}`.text() +await $`gh pr edit ${branch} --repo ${repo} --add-label jetbrains-release --add-label release`.nothrow() +console.log(create.trim()) + +async function base(ver: string, kind: "rc" | "stable") { + const text = await $`git tag --list ${"jetbrains/v*"}`.text() + const tags = text + .split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + .map((item) => ({ tag: item, ver: item.replace(/^jetbrains\/v/, "") })) + .filter((item) => semver.valid(item.ver)) + + const target = semver.parse(ver)! + const stable = tags + .filter((item) => !semver.prerelease(item.ver) && semver.lt(item.ver, ver)) + .sort((a, b) => semver.rcompare(a.ver, b.ver)) + + if (kind === "stable") { + const hit = stable[0] + if (!hit) throw new Error("No previous stable JetBrains tag found; pass --from-tag") + return hit.tag + } + + const rc = tags + .filter((item) => { + const parsed = semver.parse(item.ver) + if (!parsed) return false + if (parsed.major !== target.major || parsed.minor !== target.minor || parsed.patch !== target.patch) return false + return Boolean(semver.prerelease(item.ver)) && semver.lt(item.ver, ver) + }) + .sort((a, b) => semver.rcompare(a.ver, b.ver)) + + const hit = rc[0] ?? stable[0] + if (!hit) throw new Error("No previous JetBrains tag found; pass --from-tag") + return hit.tag +} + +async function release(from: string, tag: string) { + const res = await $`gh api repos/${repo}/releases/generate-notes --method POST -f tag_name=${tag} -f target_commitish=main -f previous_tag_name=${from} --jq .body` + .quiet() + .nothrow() + if (res.exitCode === 0) return res.stdout.toString().trim() + + const text = await $`git log --format=%s ${from}..origin/main`.text() + const lines = text + .split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + .filter((item) => !/^(chore|ci|test|release)(\(|:)/i.test(item)) + return lines.map((item) => `- ${item}`).join("\n") || "- No notable changes." +} + +function section(ver: string, notes: string) { + const date = new Date().toISOString().slice(0, 10) + const lines = bullets(notes) + return [`## [${ver}] - ${date}`, "", "### Changed", ...lines, ""].join("\n") +} + +function bullets(notes: string) { + const lines = notes + .split(/\r?\n/) + .map((item) => item.trim()) + .filter((item) => item.startsWith("- ") || item.startsWith("* ")) + .map((item) => `- ${item.slice(2).trim()}`) + return lines.length > 0 ? lines : ["- No notable changes."] +} + +async function writepkg(ver: string) { + const pkg = await Bun.file(pkgfile).json() + pkg.version = ver + await Bun.write(pkgfile, `${JSON.stringify(pkg, null, 2)}\n`) +} + +async function writelog(ver: string, entry: string) { + const current = await Bun.file(log).text().catch(() => "# Changelog\n\n## [Unreleased]\n") + const clean = current.replace(regex(ver), "").replace(/\n{3,}/g, "\n\n") + const marker = "## [Unreleased]" + if (!clean.includes(marker)) throw new Error("CHANGELOG.md must contain ## [Unreleased]") + const next = clean.replace(marker, `${marker}\n\n${entry.trim()}\n`) + await Bun.write(log, `${next.trim()}\n`) +} + +function regex(ver: string) { + const safe = ver.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`, "m") +} + +function body(ver: string, kind: string, from: string, tag: string, notes: string) { + return `## Summary +- Prepare JetBrains ${kind} release ${ver}. +- Review and edit \`packages/kilo-jetbrains/CHANGELOG.md\` before merging. + +JetBrains-Version: ${ver} +JetBrains-Kind: ${kind} +JetBrains-From-Tag: ${from} +JetBrains-Tag: ${tag} + +## Generated Notes +${notes || "No notable changes."} +` +} diff --git a/script/jetbrains-release-tag.ts b/script/jetbrains-release-tag.ts new file mode 100644 index 00000000000..b2dfc650671 --- /dev/null +++ b/script/jetbrains-release-tag.ts @@ -0,0 +1,78 @@ +#!/usr/bin/env bun +// kilocode_change - new file + +import { $ } from "bun" +import semver from "semver" +import { parseArgs } from "util" + +const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode" +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + pr: { type: "string" }, + dry: { type: "boolean", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, +}) + +if (values.help) { + console.log(` +Usage: bun script/jetbrains-release-tag.ts --pr [--dry] +`) + process.exit(0) +} + +const pr = values.pr ?? process.env.PR_NUMBER +if (!pr) throw new Error("--pr is required") + +type Pull = { + body: string + headRefName: string + isCrossRepository: boolean + labels: { name: string }[] + mergeCommit: { oid: string } | null +} + +const data = (await $`gh pr view ${pr} --repo ${repo} --json body,headRefName,isCrossRepository,labels,mergeCommit`.json()) as Pull +const labels = new Set(data.labels.map((item) => item.name)) +if (!labels.has("jetbrains-release")) throw new Error("PR is missing jetbrains-release label") +if (!data.headRefName.startsWith("jetbrains/release/")) throw new Error("PR head branch must start with jetbrains/release/") +if (data.isCrossRepository) throw new Error("JetBrains release PR must come from this repository") +if (!data.mergeCommit?.oid) throw new Error("PR has no merge commit") + +const ver = marker(data.body, "JetBrains-Version") ?? data.headRefName.replace(/^jetbrains\/release\/v/, "") +const tag = marker(data.body, "JetBrains-Tag") ?? `jetbrains/v${ver}` +if (!semver.valid(ver)) throw new Error(`Invalid JetBrains version: ${ver}`) +if (tag !== `jetbrains/v${ver}`) throw new Error(`Tag ${tag} does not match version ${ver}`) +if (!/^jetbrains\/v\d+\.\d+\.\d+(-rc\.\d+)?$/.test(tag)) throw new Error(`Invalid JetBrains tag: ${tag}`) + +const pkg = await Bun.file("packages/kilo-jetbrains/package.json").json() +if (pkg.version !== ver) throw new Error(`packages/kilo-jetbrains/package.json version is ${pkg.version}, expected ${ver}`) + +const changelog = await Bun.file("packages/kilo-jetbrains/CHANGELOG.md").text() +if (!changelog.includes(`## [${ver}]`)) throw new Error(`CHANGELOG.md is missing section for ${ver}`) + +await $`git fetch origin --tags` +const existing = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow() +if (existing.exitCode === 0) { + const sha = (await $`git rev-list -n 1 ${tag}`.text()).trim() + if (sha === data.mergeCommit.oid) { + console.log(`${tag} already exists at ${sha}`) + process.exit(0) + } + throw new Error(`${tag} already exists at ${sha}, expected ${data.mergeCommit.oid}`) +} + +console.log(`Creating ${tag} at ${data.mergeCommit.oid}`) +if (values.dry) { + console.log("Dry run complete. No tag was created.") + process.exit(0) +} + +await $`git tag ${tag} ${data.mergeCommit.oid}` +await $`git push origin ${tag}` + +function marker(body: string, key: string) { + const line = body.split(/\r?\n/).find((item) => item.startsWith(`${key}:`)) + return line?.slice(key.length + 1).trim() +} From beb8a406f09e4e2efb677a2082a720b680b8301a Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 12:17:49 -0400 Subject: [PATCH 06/59] fix(jetbrains): address release workflow review --- .github/workflows/publish-jetbrains.yml | 7 ++- .github/workflows/tag-jetbrains-release.yml | 2 + packages/kilo-jetbrains/RELEASING.md | 2 + script/jetbrains-release-pr.ts | 47 ++++++++++++++++----- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish-jetbrains.yml b/.github/workflows/publish-jetbrains.yml index 4f545457757..1779227a942 100644 --- a/.github/workflows/publish-jetbrains.yml +++ b/.github/workflows/publish-jetbrains.yml @@ -114,7 +114,12 @@ jobs: - name: Render release notes working-directory: packages/kilo-jetbrains - run: ./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md + run: | + if ! grep -Fq "## [$VERSION]" CHANGELOG.md; then + echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Create a release PR or add the changelog section before tagging." >&2 + exit 1 + fi + ./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md env: VERSION: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/tag-jetbrains-release.yml b/.github/workflows/tag-jetbrains-release.yml index 55913f0d47e..d55ddc4061a 100644 --- a/.github/workflows/tag-jetbrains-release.yml +++ b/.github/workflows/tag-jetbrains-release.yml @@ -5,6 +5,8 @@ on: pull_request: types: - closed + branches: + - main permissions: contents: write diff --git a/packages/kilo-jetbrains/RELEASING.md b/packages/kilo-jetbrains/RELEASING.md index f20b292c61e..dd3150d1ad9 100644 --- a/packages/kilo-jetbrains/RELEASING.md +++ b/packages/kilo-jetbrains/RELEASING.md @@ -42,6 +42,8 @@ The workflow chooses a changelog base automatically: Use `from_tag` only to override this comparison range. +For the first stable JetBrains release, there may be no previous stable tag yet. In that case, pass the last RC or another reviewed JetBrains tag as `from_tag`. + ## Review the PR The workflow creates or updates a branch like: diff --git a/script/jetbrains-release-pr.ts b/script/jetbrains-release-pr.ts index 7a9e5b502dc..d74283573d1 100644 --- a/script/jetbrains-release-pr.ts +++ b/script/jetbrains-release-pr.ts @@ -95,7 +95,7 @@ async function base(ver: string, kind: "rc" | "stable") { .map((item) => ({ tag: item, ver: item.replace(/^jetbrains\/v/, "") })) .filter((item) => semver.valid(item.ver)) - const target = semver.parse(ver)! + const want = semver.parse(ver)! const stable = tags .filter((item) => !semver.prerelease(item.ver) && semver.lt(item.ver, ver)) .sort((a, b) => semver.rcompare(a.ver, b.ver)) @@ -110,7 +110,7 @@ async function base(ver: string, kind: "rc" | "stable") { .filter((item) => { const parsed = semver.parse(item.ver) if (!parsed) return false - if (parsed.major !== target.major || parsed.minor !== target.minor || parsed.patch !== target.patch) return false + if (parsed.major !== want.major || parsed.minor !== want.minor || parsed.patch !== want.patch) return false return Boolean(semver.prerelease(item.ver)) && semver.lt(item.ver, ver) }) .sort((a, b) => semver.rcompare(a.ver, b.ver)) @@ -137,17 +137,41 @@ async function release(from: string, tag: string) { function section(ver: string, notes: string) { const date = new Date().toISOString().slice(0, 10) - const lines = bullets(notes) - return [`## [${ver}] - ${date}`, "", "### Changed", ...lines, ""].join("\n") + const groups = entries(notes) + const lines = [`## [${ver}] - ${date}`, ""] + for (const title of ["Added", "Fixed", "Changed"] as const) { + const items = groups.get(title) + if (!items?.length) continue + lines.push(`### ${title}`, ...items, "") + } + if (lines.length === 2) lines.push("### Changed", "- No notable changes.", "") + return lines.join("\n") } -function bullets(notes: string) { - const lines = notes +function entries(notes: string) { + const groups = new Map([ + ["Added", []], + ["Fixed", []], + ["Changed", []], + ]) + for (const line of notes .split(/\r?\n/) .map((item) => item.trim()) .filter((item) => item.startsWith("- ") || item.startsWith("* ")) - .map((item) => `- ${item.slice(2).trim()}`) - return lines.length > 0 ? lines : ["- No notable changes."] + .map((item) => item.slice(2).trim())) { + if (line.startsWith("@") && line.includes(" made their first contribution ")) continue + const text = `- ${line}` + if (/^(feat|add)(\(.+\))?:/i.test(line)) { + groups.get("Added")!.push(text) + continue + } + if (/^(fix|bug)(\(.+\))?:/i.test(line)) { + groups.get("Fixed")!.push(text) + continue + } + groups.get("Changed")!.push(text) + } + return groups } async function writepkg(ver: string) { @@ -157,7 +181,10 @@ async function writepkg(ver: string) { } async function writelog(ver: string, entry: string) { - const current = await Bun.file(log).text().catch(() => "# Changelog\n\n## [Unreleased]\n") + const current = await Bun.file(log).text().catch((err: NodeJS.ErrnoException) => { + if (err.code === "ENOENT") return "# Changelog\n\n## [Unreleased]\n" + throw err + }) const clean = current.replace(regex(ver), "").replace(/\n{3,}/g, "\n\n") const marker = "## [Unreleased]" if (!clean.includes(marker)) throw new Error("CHANGELOG.md must contain ## [Unreleased]") @@ -167,7 +194,7 @@ async function writelog(ver: string, entry: string) { function regex(ver: string) { const safe = ver.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") - return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`, "m") + return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`) } function body(ver: string, kind: string, from: string, tag: string, notes: string) { From 3b58e05e700030a186565b63ea6ab863a9d80805 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 13:01:11 -0400 Subject: [PATCH 07/59] ci(jetbrains): lock release tag before publish --- .../workflows/prepare-jetbrains-release.yml | 2 +- .github/workflows/publish-jetbrains.yml | 100 +++++++++--------- .github/workflows/tag-jetbrains-release.yml | 47 -------- packages/kilo-jetbrains/RELEASE_TODO.md | 10 +- packages/kilo-jetbrains/RELEASING.md | 38 ++++--- script/check-workflows.ts | 1 - script/jetbrains-release-pr.ts | 37 +++++-- ...e-tag.ts => jetbrains-release-validate.ts} | 59 +++++++---- 8 files changed, 150 insertions(+), 144 deletions(-) delete mode 100644 .github/workflows/tag-jetbrains-release.yml rename script/{jetbrains-release-tag.ts => jetbrains-release-validate.ts} (57%) diff --git a/.github/workflows/prepare-jetbrains-release.yml b/.github/workflows/prepare-jetbrains-release.yml index 0ba90451fba..e9da7833bc4 100644 --- a/.github/workflows/prepare-jetbrains-release.yml +++ b/.github/workflows/prepare-jetbrains-release.yml @@ -49,7 +49,7 @@ jobs: kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} - - name: Create or update release PR + - name: Create release tag and PR run: | args=(--kind "$KIND" --version "$VERSION") if [[ -n "$FROM_TAG" ]]; then diff --git a/.github/workflows/publish-jetbrains.yml b/.github/workflows/publish-jetbrains.yml index 1779227a942..78403032c3e 100644 --- a/.github/workflows/publish-jetbrains.yml +++ b/.github/workflows/publish-jetbrains.yml @@ -2,29 +2,61 @@ name: publish-jetbrains on: - push: - tags: - - "jetbrains/*" + pull_request: + types: + - closed + branches: + - main concurrency: - group: publish-jetbrains-${{ github.ref }} + group: publish-jetbrains-pr-${{ github.event.pull_request.number }} cancel-in-progress: false permissions: contents: write + pull-requests: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: publish: - if: github.repository == 'Kilo-Org/kilocode' + if: >- + github.repository == 'Kilo-Org/kilocode' && + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') && + contains(github.event.pull_request.labels.*.name, 'jetbrains-release') && + github.event.pull_request.head.repo.full_name == github.repository runs-on: blacksmith-8vcpu-ubuntu-2404 steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout merged release PR + uses: actions/checkout@v6 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Setup Bun for validation + uses: ./.github/actions/setup-bun + + - name: Validate release PR and tag + id: release + run: bun script/jetbrains-release-validate.ts --pr "$PR_NUMBER" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + + - name: Save reviewed changelog + run: cp packages/kilo-jetbrains/CHANGELOG.md "$RUNNER_TEMP/jetbrains-CHANGELOG.md" + + - name: Checkout release tag + uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ steps.release.outputs.tag }} + + - name: Restore reviewed changelog + run: cp "$RUNNER_TEMP/jetbrains-CHANGELOG.md" packages/kilo-jetbrains/CHANGELOG.md - name: Setup Node uses: actions/setup-node@v4 @@ -48,39 +80,6 @@ jobs: sudo apt-get update sudo apt-get install -y patchelf zip - - name: Validate version tag - id: version - run: | - tag="$GITHUB_REF_NAME" - if [[ "$tag" != jetbrains/v* ]]; then - echo "Unsupported tag '$tag'. Expected jetbrains/v." >&2 - exit 1 - fi - - version="${tag#jetbrains/v}" - if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then - { - echo "version=$version" - echo "kind=rc" - echo "marketplace_channel=eap" - echo "cli_channel=rc" - } >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - { - echo "version=$version" - echo "kind=stable" - echo "marketplace_channel=default" - echo "cli_channel=latest" - } >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "Unsupported JetBrains plugin version '$version'. Expected jetbrains/vx.y.z-rc.n or jetbrains/vx.y.z." >&2 - exit 1 - - name: Validate publishing secrets run: | missing=0 @@ -101,8 +100,8 @@ jobs: working-directory: packages/kilo-jetbrains run: bun script/build.ts --production --prepare-cli env: - KILO_VERSION: ${{ steps.version.outputs.version }} - KILO_CHANNEL: ${{ steps.version.outputs.cli_channel }} + KILO_VERSION: ${{ steps.release.outputs.version }} + KILO_CHANNEL: ${{ steps.release.outputs.cli_channel }} GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} @@ -110,24 +109,24 @@ jobs: working-directory: packages/kilo-jetbrains run: ./gradlew verifyPlugin -Pproduction=true -Pkilo.channel="$CHANNEL" env: - CHANNEL: ${{ steps.version.outputs.marketplace_channel }} + CHANNEL: ${{ steps.release.outputs.marketplace_channel }} - name: Render release notes working-directory: packages/kilo-jetbrains run: | if ! grep -Fq "## [$VERSION]" CHANGELOG.md; then - echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Create a release PR or add the changelog section before tagging." >&2 + echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Review and merge a release PR before publishing." >&2 exit 1 fi ./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md env: - VERSION: ${{ steps.version.outputs.version }} + VERSION: ${{ steps.release.outputs.version }} - name: Publish to JetBrains Marketplace working-directory: packages/kilo-jetbrains run: ./gradlew publishPlugin -Pproduction=true -Pkilo.channel="$CHANNEL" env: - CHANNEL: ${{ steps.version.outputs.marketplace_channel }} + CHANNEL: ${{ steps.release.outputs.marketplace_channel }} JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }} JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }} JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }} @@ -157,7 +156,7 @@ jobs: - name: Upload to GitHub Release run: | - tag="$GITHUB_REF_NAME" + tag="$TAG" title="JetBrains $VERSION" flags=(--title "$title" --notes-file "$NOTES") if [[ "$KIND" == "rc" ]]; then @@ -171,8 +170,9 @@ jobs: gh release create "$tag" "$ARCHIVE" "${flags[@]}" --repo "$GITHUB_REPOSITORY" env: GH_TOKEN: ${{ github.token }} - VERSION: ${{ steps.version.outputs.version }} - KIND: ${{ steps.version.outputs.kind }} + TAG: ${{ steps.release.outputs.tag }} + VERSION: ${{ steps.release.outputs.version }} + KIND: ${{ steps.release.outputs.kind }} ARCHIVE: ${{ steps.archive.outputs.path }} NOTES: packages/kilo-jetbrains/build/release-notes.md @@ -180,6 +180,6 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: kilo-jetbrains-${{ steps.version.outputs.version }} + name: kilo-jetbrains-${{ steps.release.outputs.version }} path: packages/kilo-jetbrains/build/distributions/*.zip if-no-files-found: ignore diff --git a/.github/workflows/tag-jetbrains-release.yml b/.github/workflows/tag-jetbrains-release.yml deleted file mode 100644 index d55ddc4061a..00000000000 --- a/.github/workflows/tag-jetbrains-release.yml +++ /dev/null @@ -1,47 +0,0 @@ -# kilocode_change - new file -name: tag-jetbrains-release - -on: - pull_request: - types: - - closed - branches: - - main - -permissions: - contents: write - pull-requests: read - -jobs: - tag: - if: >- - github.repository == 'Kilo-Org/kilocode' && - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') && - contains(github.event.pull_request.labels.*.name, 'jetbrains-release') && - github.event.pull_request.head.repo.full_name == github.repository - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - persist-credentials: false - fetch-depth: 0 - ref: ${{ github.event.pull_request.merge_commit_sha }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup Git Committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} - kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} - - - name: Create release tag - run: bun script/jetbrains-release-tag.ts --pr "$PR_NUMBER" - env: - GH_TOKEN: ${{ steps.committer.outputs.token }} - GH_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/packages/kilo-jetbrains/RELEASE_TODO.md b/packages/kilo-jetbrains/RELEASE_TODO.md index fcb1e36099c..b74103894b7 100644 --- a/packages/kilo-jetbrains/RELEASE_TODO.md +++ b/packages/kilo-jetbrains/RELEASE_TODO.md @@ -13,8 +13,8 @@ - Create a JetBrains Marketplace permanent token from Marketplace `My Tokens`. - Add `JETBRAINS_MARKETPLACE_TOKEN` to GitHub Actions secrets or the protected environment. -- Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases from `jetbrains/v*` tags. -- Confirm `KILO_MAINTAINER_APP_ID` and `KILO_MAINTAINER_APP_SECRET` are available to create release PRs and tags. +- Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases for `jetbrains/v*` tags. +- Confirm `KILO_MAINTAINER_APP_ID` and `KILO_MAINTAINER_APP_SECRET` are available to create release PRs and immediate release tags. - Optionally create a protected `jetbrains-marketplace` GitHub Environment with required reviewers. - If using an environment, move the Marketplace and signing secrets there and set the workflow job environment. @@ -31,8 +31,9 @@ - Choose an RC version in the form `x.y.z-rc.n`. - Run the `prepare-jetbrains-release` workflow with `kind=rc` and version `x.y.z-rc.n`. +- Confirm the workflow created `jetbrains/vx.y.z-rc.n` immediately at the intended source commit. - Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR. -- Merge the release PR to create tag `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`. +- Merge the release PR to trigger publish from `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`. - Watch the `publish-jetbrains` workflow. - Download and retain the workflow artifact if needed. - Confirm the update appears on the JetBrains Marketplace `eap` channel. @@ -43,8 +44,9 @@ - Choose a stable version in the form `x.y.z`. - Run the `prepare-jetbrains-release` workflow with `kind=stable` and version `x.y.z`. +- Confirm the workflow created `jetbrains/vx.y.z` immediately at the intended source commit. - Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR. -- Merge the release PR to create tag `jetbrains/vx.y.z`. +- Merge the release PR to trigger publish from `jetbrains/vx.y.z`. - Watch the `publish-jetbrains` workflow. - Confirm the update appears on the default JetBrains Marketplace channel. - Confirm the GitHub Release for the `jetbrains/vx.y.z` tag exists and contains the JetBrains plugin ZIP asset. diff --git a/packages/kilo-jetbrains/RELEASING.md b/packages/kilo-jetbrains/RELEASING.md index dd3150d1ad9..69bea003546 100644 --- a/packages/kilo-jetbrains/RELEASING.md +++ b/packages/kilo-jetbrains/RELEASING.md @@ -1,14 +1,16 @@ # Releasing the JetBrains Plugin -JetBrains releases use a release PR. The PR is where maintainers review and edit the version and changelog before anything is published. +JetBrains releases are locked by an immediate `jetbrains/v` tag, then gated by a reviewed release PR. The tag fixes the exact source code that will be published; the PR is where maintainers review and edit the version and changelog before publishing starts. -## Create a Release PR +The published code comes from `jetbrains/v`. Marketplace and GitHub release notes come from the reviewed changelog merged in the release PR. + +## Create Release Tag And PR 1. Open the GitHub Actions workflow: [https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml) -2. Click **Run workflow**. +2. Click **Run workflow**. This immediately creates `jetbrains/v` at the current `origin/main` commit, then creates or updates the release PR. 3. Fill the inputs: @@ -32,7 +34,7 @@ version=7.3.13 ## Changelog Range Defaults -The workflow chooses a changelog base automatically: +The workflow chooses a changelog base automatically and generates notes against the locked release commit: | Release | Default `from_tag` | |---|---| @@ -40,7 +42,7 @@ The workflow chooses a changelog base automatically: | Later RC, e.g. `7.3.13-rc.2` | Previous RC for the same base version. | | Stable, e.g. `7.3.13` | Latest stable JetBrains tag, ignoring RCs. | -Use `from_tag` only to override this comparison range. +Use `from_tag` only to override this comparison range. It does not change the release target commit. For the first stable JetBrains release, there may be no previous stable tag yet. In that case, pass the last RC or another reviewed JetBrains tag as `from_tag`. @@ -61,15 +63,17 @@ The PR updates: Review and edit `packages/kilo-jetbrains/CHANGELOG.md` before merging. This changelog entry is rendered into JetBrains ``, so it appears on the Marketplace and inside IntelliJ plugin UI. +The PR can change release metadata such as `packages/kilo-jetbrains/package.json` and `packages/kilo-jetbrains/CHANGELOG.md`, but it does not change the tagged source code that will be built. + ## Merge and Publish -When the release PR is merged, the `tag-jetbrains-release` workflow validates it and creates: +When the release PR is merged, the `publish-jetbrains` workflow validates the existing tag and release PR markers: ```text jetbrains/v ``` -That tag triggers the `publish-jetbrains` workflow: +Then it publishes from that tag: [https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml) @@ -80,7 +84,7 @@ Publishing behavior: | `x.y.z-rc.n` | `eap` | Prerelease | | `x.y.z` | default | Stable release | -The workflow verifies, signs, and publishes the plugin ZIP, then uploads the ZIP to the matching GitHub Release. +The workflow checks out `jetbrains/v` for verification, signing, and Marketplace publishing. It overlays the reviewed `packages/kilo-jetbrains/CHANGELOG.md` from the merged PR before rendering release notes and before `publishPlugin`, so Marketplace metadata and the GitHub Release use the reviewed changelog. ## Installing RC Builds @@ -98,22 +102,28 @@ https://plugins.jetbrains.com/plugins/list?channel=eap&pluginId=28350 ## Manual Recovery -If the PR was merged but the tag workflow failed after validation, create the tag manually at the merge commit: +If the prepare workflow created the tag but failed before creating or updating the PR, rerun the workflow for the same version. It reuses the tag if it still points to the same locked commit. + +If publish validation says the tag points to the wrong SHA, stop and inspect manually. Do not move, delete, or recreate release tags casually. + +If publish failed after merge, rerun the failed `publish-jetbrains` workflow if the failure happened before Marketplace accepted the version. Marketplace may reject a duplicate version after a successful publish. + +If Marketplace publishing succeeded but GitHub Release upload failed, manually create or edit the GitHub Release for the existing tag. Use the reviewed release notes from `packages/kilo-jetbrains/CHANGELOG.md` in the merged release PR. + +If the immediate tag must be created manually because the prepare workflow could not push it, create it at the intended locked `origin/main` commit before merging the release PR: ```bash git fetch origin main -git tag jetbrains/v7.3.13 +git tag jetbrains/v7.3.13 git push origin jetbrains/v7.3.13 ``` -Do not create a tag before the release PR is merged unless intentionally bypassing the release-PR flow. - ## Required GitHub Actions Secrets | Secret | Purpose | |---|---| -| `KILO_MAINTAINER_APP_ID` | GitHub App ID used to create/update release PRs and tags. | -| `KILO_MAINTAINER_APP_SECRET` | GitHub App private key used to create/update release PRs and tags. | +| `KILO_MAINTAINER_APP_ID` | GitHub App ID used to create/update release PRs and immediate release tags. | +| `KILO_MAINTAINER_APP_SECRET` | GitHub App private key used to create/update release PRs and immediate release tags. | | `JETBRAINS_MARKETPLACE_TOKEN` | Marketplace API token for publishing. | | `JETBRAINS_CERTIFICATE_CHAIN` | PEM certificate chain for plugin signing. | | `JETBRAINS_PRIVATE_KEY` | PEM private key for plugin signing. | diff --git a/script/check-workflows.ts b/script/check-workflows.ts index a3657379903..77176467e38 100644 --- a/script/check-workflows.ts +++ b/script/check-workflows.ts @@ -48,7 +48,6 @@ const active = new Set([ "publish.yml", "smoke-test.yml", "source-check-links.yml", - "tag-jetbrains-release.yml", "test-vscode.yml", "test.yml", "triage.yml", diff --git a/script/jetbrains-release-pr.ts b/script/jetbrains-release-pr.ts index d74283573d1..c2be1e33ecd 100644 --- a/script/jetbrains-release-pr.ts +++ b/script/jetbrains-release-pr.ts @@ -45,24 +45,28 @@ await $`git fetch origin main --tags` const tag = `jetbrains/v${ver}` const branch = `jetbrains/release/v${ver}` +const sha = (await $`git rev-parse origin/main`.text()).trim() const from = values["from-tag"] ?? (await base(ver, kind)) -const notes = await release(from, tag) +const state = await lock(tag, sha, dry) +const notes = await release(from, tag, sha) const entry = section(ver, notes) console.log(`JetBrains ${kind} release PR`) console.log(`version: ${ver}`) console.log(`base: ${from}`) console.log(`tag: ${tag}`) +console.log(`commit: ${sha}`) console.log(`branch: ${branch}`) +console.log(`tag state: ${state}`) if (dry) { console.log("\nGenerated changelog entry:\n") console.log(entry) - console.log("\nDry run complete. No branch, commit, push, or PR was created.") + console.log("\nDry run complete. No tag, branch, commit, push, or PR was created.") process.exit(0) } -await $`git checkout -B ${branch} origin/main` +await $`git checkout -B ${branch} ${sha}` await writepkg(ver) await writelog(ver, entry) await $`git add packages/kilo-jetbrains/package.json packages/kilo-jetbrains/CHANGELOG.md` @@ -72,7 +76,7 @@ if (changed.exitCode !== 0) await $`git commit -m ${`release(jetbrains): v${ver} await $`git push --force-with-lease origin ${branch}` -const text = body(ver, kind, from, tag, notes) +const text = body(ver, kind, from, tag, sha, notes) const view = await $`gh pr view ${branch} --repo ${repo} --json number --jq .number`.nothrow() if (view.exitCode === 0 && view.stdout.toString().trim()) { const num = view.stdout.toString().trim() @@ -120,13 +124,29 @@ async function base(ver: string, kind: "rc" | "stable") { return hit.tag } -async function release(from: string, tag: string) { - const res = await $`gh api repos/${repo}/releases/generate-notes --method POST -f tag_name=${tag} -f target_commitish=main -f previous_tag_name=${from} --jq .body` +async function lock(tag: string, sha: string, dry: boolean) { + const res = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow() + if (res.exitCode === 0) { + const got = (await $`git rev-list -n 1 ${tag}`.text()).trim() + if (got === sha) return "exists" + throw new Error(`${tag} already exists at ${got}, expected ${sha}`) + } + if (dry) return "would-create" + await $`git tag ${tag} ${sha}` + await $`git push origin ${tag}` + return "created" +} + +async function release(from: string, tag: string, sha: string) { + const res = await $`gh api repos/${repo}/releases/generate-notes --method POST -f tag_name=${tag} -f target_commitish=${sha} -f previous_tag_name=${from} --jq .body` .quiet() .nothrow() if (res.exitCode === 0) return res.stdout.toString().trim() - const text = await $`git log --format=%s ${from}..origin/main`.text() + const base = await $`git rev-parse -q --verify ${from}`.nothrow() + if (base.exitCode !== 0) throw new Error(`Previous JetBrains tag not found: ${from}`) + + const text = await $`git log --format=%s ${from}..${sha}`.text() const lines = text .split(/\r?\n/) .map((item) => item.trim()) @@ -197,7 +217,7 @@ function regex(ver: string) { return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`) } -function body(ver: string, kind: string, from: string, tag: string, notes: string) { +function body(ver: string, kind: string, from: string, tag: string, sha: string, notes: string) { return `## Summary - Prepare JetBrains ${kind} release ${ver}. - Review and edit \`packages/kilo-jetbrains/CHANGELOG.md\` before merging. @@ -206,6 +226,7 @@ JetBrains-Version: ${ver} JetBrains-Kind: ${kind} JetBrains-From-Tag: ${from} JetBrains-Tag: ${tag} +JetBrains-Commit: ${sha} ## Generated Notes ${notes || "No notable changes."} diff --git a/script/jetbrains-release-tag.ts b/script/jetbrains-release-validate.ts similarity index 57% rename from script/jetbrains-release-tag.ts rename to script/jetbrains-release-validate.ts index b2dfc650671..93dee0e9d90 100644 --- a/script/jetbrains-release-tag.ts +++ b/script/jetbrains-release-validate.ts @@ -2,6 +2,7 @@ // kilocode_change - new file import { $ } from "bun" +import { appendFileSync } from "node:fs" import semver from "semver" import { parseArgs } from "util" @@ -17,7 +18,10 @@ const { values } = parseArgs({ if (values.help) { console.log(` -Usage: bun script/jetbrains-release-tag.ts --pr [--dry] +Usage: bun script/jetbrains-release-validate.ts --pr [--dry] + +Validates a merged JetBrains release PR and the pre-created immutable release tag. +This helper never creates, moves, deletes, or pushes tags. `) process.exit(0) } @@ -40,11 +44,25 @@ if (!data.headRefName.startsWith("jetbrains/release/")) throw new Error("PR head if (data.isCrossRepository) throw new Error("JetBrains release PR must come from this repository") if (!data.mergeCommit?.oid) throw new Error("PR has no merge commit") -const ver = marker(data.body, "JetBrains-Version") ?? data.headRefName.replace(/^jetbrains\/release\/v/, "") -const tag = marker(data.body, "JetBrains-Tag") ?? `jetbrains/v${ver}` +const ver = need(data.body, "JetBrains-Version") +const kind = need(data.body, "JetBrains-Kind") +const tag = need(data.body, "JetBrains-Tag") +const commit = need(data.body, "JetBrains-Commit") + if (!semver.valid(ver)) throw new Error(`Invalid JetBrains version: ${ver}`) +if (kind !== "rc" && kind !== "stable") throw new Error(`Invalid JetBrains kind: ${kind}`) +if (kind === "rc" && !/^\d+\.\d+\.\d+-rc\.\d+$/.test(ver)) throw new Error("RC versions must match x.y.z-rc.n") +if (kind === "stable" && !/^\d+\.\d+\.\d+$/.test(ver)) throw new Error("Stable versions must match x.y.z") if (tag !== `jetbrains/v${ver}`) throw new Error(`Tag ${tag} does not match version ${ver}`) if (!/^jetbrains\/v\d+\.\d+\.\d+(-rc\.\d+)?$/.test(tag)) throw new Error(`Invalid JetBrains tag: ${tag}`) +if (!/^[0-9a-f]{40}$/i.test(commit)) throw new Error(`Invalid JetBrains commit: ${commit}`) + +await $`git fetch origin --tags` +const existing = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow() +if (existing.exitCode !== 0) throw new Error(`${tag} does not exist`) + +const sha = (await $`git rev-list -n 1 ${tag}`.text()).trim() +if (sha !== commit) throw new Error(`${tag} points at ${sha}, expected ${commit}`) const pkg = await Bun.file("packages/kilo-jetbrains/package.json").json() if (pkg.version !== ver) throw new Error(`packages/kilo-jetbrains/package.json version is ${pkg.version}, expected ${ver}`) @@ -52,27 +70,30 @@ if (pkg.version !== ver) throw new Error(`packages/kilo-jetbrains/package.json v const changelog = await Bun.file("packages/kilo-jetbrains/CHANGELOG.md").text() if (!changelog.includes(`## [${ver}]`)) throw new Error(`CHANGELOG.md is missing section for ${ver}`) -await $`git fetch origin --tags` -const existing = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow() -if (existing.exitCode === 0) { - const sha = (await $`git rev-list -n 1 ${tag}`.text()).trim() - if (sha === data.mergeCommit.oid) { - console.log(`${tag} already exists at ${sha}`) - process.exit(0) - } - throw new Error(`${tag} already exists at ${sha}, expected ${data.mergeCommit.oid}`) +const marketplace = kind === "rc" ? "eap" : "default" +const cli = kind === "rc" ? "rc" : "latest" +const output = { + version: ver, + kind, + tag, + commit, + merge: data.mergeCommit.oid, + marketplace_channel: marketplace, + cli_channel: cli, } -console.log(`Creating ${tag} at ${data.mergeCommit.oid}`) -if (values.dry) { - console.log("Dry run complete. No tag was created.") - process.exit(0) +for (const [key, value] of Object.entries(output)) console.log(`${key}=${value}`) +if (process.env.GITHUB_OUTPUT && !values.dry) { + appendFileSync(process.env.GITHUB_OUTPUT, Object.entries(output).map(([key, value]) => `${key}=${value}\n`).join("")) } -await $`git tag ${tag} ${data.mergeCommit.oid}` -await $`git push origin ${tag}` - function marker(body: string, key: string) { const line = body.split(/\r?\n/).find((item) => item.startsWith(`${key}:`)) return line?.slice(key.length + 1).trim() } + +function need(body: string, key: string) { + const value = marker(body, key) + if (!value) throw new Error(`PR body is missing ${key}`) + return value +} From 6fa48b6edce3ec9bb97e8b76f2f8cada674e74c9 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 13:20:25 -0400 Subject: [PATCH 08/59] fix(jetbrains): require merged release PR validation --- script/jetbrains-release-validate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/script/jetbrains-release-validate.ts b/script/jetbrains-release-validate.ts index 93dee0e9d90..78fe6b044cc 100644 --- a/script/jetbrains-release-validate.ts +++ b/script/jetbrains-release-validate.ts @@ -34,14 +34,17 @@ type Pull = { headRefName: string isCrossRepository: boolean labels: { name: string }[] + mergedAt: string | null mergeCommit: { oid: string } | null + state: string } -const data = (await $`gh pr view ${pr} --repo ${repo} --json body,headRefName,isCrossRepository,labels,mergeCommit`.json()) as Pull +const data = (await $`gh pr view ${pr} --repo ${repo} --json body,headRefName,isCrossRepository,labels,mergedAt,mergeCommit,state`.json()) as Pull const labels = new Set(data.labels.map((item) => item.name)) if (!labels.has("jetbrains-release")) throw new Error("PR is missing jetbrains-release label") if (!data.headRefName.startsWith("jetbrains/release/")) throw new Error("PR head branch must start with jetbrains/release/") if (data.isCrossRepository) throw new Error("JetBrains release PR must come from this repository") +if (data.state !== "MERGED" || !data.mergedAt) throw new Error("JetBrains release PR must be merged") if (!data.mergeCommit?.oid) throw new Error("PR has no merge commit") const ver = need(data.body, "JetBrains-Version") From 070d82c833cf823632b9d6532f40e49f20245947 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 13:36:39 -0400 Subject: [PATCH 09/59] test(cli): stabilize shell cancellation test --- packages/opencode/test/session/prompt.test.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index ea5aa9255ce..1239e78ceb1 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1578,12 +1578,30 @@ unix( provideTmpdirInstance( (_dir) => Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() + const { prompt, run, chat, sessions } = yield* boot() const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) + .shell({ sessionID: chat.id, agent: "build", command: "printf started; sleep 30" }) // kilocode_change .pipe(Effect.forkChild) - yield* Effect.sleep(50) + // kilocode_change start - avoid cancelling before the shell process is ready on slower CI hosts + yield* waitFor( + "shell start output", + sessions + .messages({ sessionID: chat.id }) + .pipe( + Effect.map((msgs) => + msgs + .flatMap((msg) => msg.parts) + .find( + (part) => + part.type === "tool" && + part.state.status === "running" && + (part.state.metadata?.output ?? "").includes("started"), + ), + ), + ), + ) + // kilocode_change end yield* prompt.cancel(chat.id) From 855aec0f31a94f86137e43639ac5ca25e02e7760 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 13:51:31 -0400 Subject: [PATCH 10/59] Revert "test(cli): stabilize shell cancellation test" This reverts commit 070d82c833cf823632b9d6532f40e49f20245947. --- packages/opencode/test/session/prompt.test.ts | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 1239e78ceb1..ea5aa9255ce 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1578,30 +1578,12 @@ unix( provideTmpdirInstance( (_dir) => Effect.gen(function* () { - const { prompt, run, chat, sessions } = yield* boot() + const { prompt, run, chat } = yield* boot() const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "printf started; sleep 30" }) // kilocode_change + .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) .pipe(Effect.forkChild) - // kilocode_change start - avoid cancelling before the shell process is ready on slower CI hosts - yield* waitFor( - "shell start output", - sessions - .messages({ sessionID: chat.id }) - .pipe( - Effect.map((msgs) => - msgs - .flatMap((msg) => msg.parts) - .find( - (part) => - part.type === "tool" && - part.state.status === "running" && - (part.state.metadata?.output ?? "").includes("started"), - ), - ), - ), - ) - // kilocode_change end + yield* Effect.sleep(50) yield* prompt.cancel(chat.id) From 88f31230a6766d1361b31b30630e87af3d4ac753 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 19:54:56 -0400 Subject: [PATCH 11/59] fix(jetbrains): address toolbar and prompt initialization --- .../kotlin/ai/kilocode/client/KiloToolWindowFactory.kt | 5 +---- .../kilocode/client/session/scroll/ScrollButtonIcon.kt | 9 +++++---- .../ai/kilocode/client/session/ui/prompt/PromptPanel.kt | 4 +++- .../kilocode/client/session/ui/style/SessionUiStyle.kt | 8 ++++++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt index c126b91f2cd..99c596b3d7c 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt @@ -65,10 +65,7 @@ class KiloToolWindowFactory : ToolWindowFactory, DumbAware { manager.newSession() val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar") - if (toolbar is ActionGroup) { - val actions = toolbar.getChildren(null).toList() - toolWindow.setTitleActions(actions) - } + if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList()) } catch (e: Exception) { LOG.error("Failed to set up Kilo tool window content", e) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt index aba6b2f2672..1cb205ad1fe 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt @@ -1,5 +1,6 @@ package ai.kilocode.client.session.scroll +import ai.kilocode.client.session.ui.style.SessionUiStyle import ai.kilocode.client.ui.colorizeIfPossible import ai.kilocode.client.ui.UiStyle import com.intellij.openapi.util.IconLoader @@ -16,16 +17,16 @@ internal object ScrollButtonIcon { return prompt.colorizeIfPossible( fillColor = UiStyle.Colors.warningLabelForeground(), borderColor = Color.WHITE, - fillId = "ScrollQuestion.Background", - strokeId = "ScrollQuestion.Foreground", + fillColors = listOf(SessionUiStyle.ScrollIcon.QUESTION), + borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), ) } return bottom.colorizeIfPossible( fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(), borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(), - fillId = "ScrollButton.Background", - strokeId = "ScrollButton.Foreground", + fillColors = listOf(SessionUiStyle.ScrollIcon.BOTTOM_LIGHT, SessionUiStyle.ScrollIcon.BOTTOM_DARK), + borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), ) } } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt index 28632aa9898..7ff28bfe2e4 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt @@ -26,6 +26,7 @@ import com.intellij.openapi.actionSystem.UiDataProvider import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.keymap.Keymap @@ -363,7 +364,8 @@ class PromptPanel( @RequiresEdt private fun syncEditorHeight() { - val lines = (editor.document.lineCount + SessionUiStyle.View.Prompt.EDITOR_SPARE_LINES).coerceIn( + val count = ReadAction.computeBlocking { editor.document.lineCount } + val lines = (count + SessionUiStyle.View.Prompt.EDITOR_SPARE_LINES).coerceIn( SessionUiStyle.View.Prompt.EDITOR_LINES, SessionUiStyle.View.Prompt.EDITOR_MAX_LINES, ) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt index 433ad686227..4287314ca71 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt @@ -17,6 +17,14 @@ object SessionUiStyle { const val SCROLL_INCREMENT = 16 } + /** Literal source palette values used by session scroll SVG assets before runtime colorization. */ + object ScrollIcon { + const val BOTTOM_LIGHT = 0x384F6B + const val BOTTOM_DARK = 0x233143 + const val QUESTION = 0xE08800 + const val FOREGROUND = 0xFFFFFF + } + /** Shared tokens for individual transcript views and cards. */ object View { const val CARD_LAYOUT_GAP = 6 From 805a05009b5026a8495d0e63ec25dd84fbf2786d Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 19:56:26 -0400 Subject: [PATCH 12/59] build(jetbrains): add marketplace build script --- packages/kilo-jetbrains/AGENTS.md | 1 + .../kilo-jetbrains/script/build-sign-check.sh | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100755 packages/kilo-jetbrains/script/build-sign-check.sh diff --git a/packages/kilo-jetbrains/AGENTS.md b/packages/kilo-jetbrains/AGENTS.md index 1d6c1427845..1fae7c14002 100644 --- a/packages/kilo-jetbrains/AGENTS.md +++ b/packages/kilo-jetbrains/AGENTS.md @@ -168,6 +168,7 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi ## Build and Verification +- **Marketplace build/sign/check**: Use `script/build-sign-check.sh ` from `packages/kilo-jetbrains/script/` to build, sign, and verify the JetBrains Marketplace plugin ZIP. Pass `--skip-verification` only when explicitly needed. - **Typecheck**: `bun run typecheck` or `./gradlew typecheck` from `packages/kilo-jetbrains/` — compiles all Kotlin sources including the generated API client. Does NOT require CLI binaries. - **Full build**: `bun run build` from `packages/kilo-jetbrains/` (prepares CLI binaries + runs Gradle `buildPlugin`). - **Gradle only**: `./gradlew buildPlugin` from `packages/kilo-jetbrains/` (requires CLI binaries already present in `backend/build/generated/cli/`; run `bun run build --prepare-cli` first). diff --git a/packages/kilo-jetbrains/script/build-sign-check.sh b/packages/kilo-jetbrains/script/build-sign-check.sh new file mode 100755 index 00000000000..b871785454d --- /dev/null +++ b/packages/kilo-jetbrains/script/build-sign-check.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: $0 [--skip-verification]" >&2 + echo "Example: $0 7.0.1-rc.1" >&2 + echo "Example: $0 v7.0.1-rc.1 --skip-verification" >&2 + echo "Builds/signs the current checkout without creating or validating a git tag." >&2 +} + +if [[ $# -lt 1 ]]; then + usage + exit 1 +fi + +raw="" +skip_verification=0 + +for arg in "$@"; do + case "$arg" in + --skip-verification) + skip_verification=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + if [[ -n "$raw" ]]; then + echo "Unexpected argument: $arg" >&2 + usage + exit 1 + fi + raw="$arg" + ;; + esac +done + +if [[ -z "$raw" ]]; then + usage + exit 1 +fi + +version="${raw#v}" +if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then + echo "Unsupported version '$raw'. Expected x.y.z-rc.n, for example 7.0.1-rc.1." >&2 + exit 1 +fi + +script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +root="$(git -C "$script" rev-parse --show-toplevel)" +plugin="$(cd "${script}/.." && pwd)" +secrets="${root}/.secrets" +chain="${secrets}/chain.crt" +key="${secrets}/private.pem" +encrypted_key="${secrets}/private_encrypted.pem" +pass="${secrets}/JETBRAINS_PRIVATE_KEY_PASSWORD" + +if [[ ! -d "$plugin" ]]; then + echo "Expected JetBrains plugin package at $plugin" >&2 + exit 1 +fi + +for file in "$chain" "$key" "$pass"; do + if [[ ! -s "$file" ]]; then + echo "Missing required secret file: $file" >&2 + exit 1 + fi + chmod go-rwx "$file" 2>/dev/null || true +done + +if [[ -f "$encrypted_key" ]]; then + chmod go-rwx "$encrypted_key" 2>/dev/null || true +fi + +export JETBRAINS_CERTIFICATE_CHAIN_FILE="$chain" +export JETBRAINS_PRIVATE_KEY_FILE="$key" +export JETBRAINS_PRIVATE_KEY_PASSWORD="$(<"$pass")" + +cd "$plugin" + +./gradlew clean +KILO_VERSION="$version" KILO_CHANNEL=rc bun script/build.ts --production --prepare-cli +./gradlew buildPlugin -Pproduction=true -Pkilo.version="$version" -Pkilo.channel=eap +./gradlew signPlugin -Pproduction=true -Pkilo.version="$version" -Pkilo.channel=eap + +if [[ "$skip_verification" == "1" ]]; then + printf '\nSkipping JetBrains plugin verification.\n' +else + ./gradlew verifyPluginSignature -Pproduction=true -Pkilo.version="$version" -Pkilo.channel=eap + ./gradlew verifyPlugin -Pproduction=true -Pkilo.version="$version" -Pkilo.channel=eap +fi + +printf '\nSigned JetBrains plugin ZIP:\n' +ls -lh build/distributions/*-signed.zip From 391a6b95e63fd0beb062c41351662a9723b19070 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 27 May 2026 20:21:18 -0400 Subject: [PATCH 13/59] fix(jetbrains): avoid verifier-blocked action APIs --- .kilo/plans/1779919295135-mighty-tiger.md | 90 +++++++++++++++++ .kilo/plans/1779921627869-shiny-circuit.md | 72 ++++++++++++++ .../kilocode/client/KiloToolWindowFactory.kt | 2 +- .../client/actions/ShowProfileAction.kt | 3 +- .../ai/kilocode/client/ui/SvgIconColorizer.kt | 98 +++++++++++++------ 5 files changed, 233 insertions(+), 32 deletions(-) create mode 100644 .kilo/plans/1779919295135-mighty-tiger.md create mode 100644 .kilo/plans/1779921627869-shiny-circuit.md diff --git a/.kilo/plans/1779919295135-mighty-tiger.md b/.kilo/plans/1779919295135-mighty-tiger.md new file mode 100644 index 00000000000..371e861e131 --- /dev/null +++ b/.kilo/plans/1779919295135-mighty-tiger.md @@ -0,0 +1,90 @@ +# Fix JetBrains Publish Verification + +## Failure Summary + +The `publish-jetbrains` workflow failed in the `Verify plugin` step for tag `jetbrains/v7.0.1-rc.2`. + +The verifier reports the plugin is otherwise compatible with `IU-261.22158.277`, but fails the build because of blocking problem classes: + +- `INTERNAL_API_USAGES` +- `OVERRIDE_ONLY_API_USAGES` + +Non-blocking verifier output also includes deprecated and experimental API usages, but those are not the reason for this publish failure. + +## Local IntelliJ Source Insights + +The local IntelliJ checkout at `/Users/kirillk/products/intellij-community` confirms the verifier findings and the safe replacements: + +- `platform/editor-ui-api/src/com/intellij/openapi/actionSystem/remoting/ActionRemoteBehavior.kt:10-12` and `70-72` mark `ActionRemoteBehavior` and `ActionRemoteBehaviorSpecification` as `@ApiStatus.Internal` and `@ApiStatus.Experimental`. +- `platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt:338-346` implements `createWithPatcher(...)`, but it depends on internal icon loader state. +- `platform/util/ui/src/com/intellij/util/SVGLoader.kt:105-115` defines `SvgElementColorPatcherProvider`; it is part of the internal patching path. +- `platform/platform-impl/src/com/intellij/ui/GotItComponentBuilder.kt:1169-1204` contains the source pattern that `SvgIconColorizer.kt` mirrors, including digest generation and `setAttribute(...)` behavior. +- `platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.kt:662-681` exposes `ActionUtil.getAction(...)` and `ActionUtil.getActionGroup(vararg ids)` as non-override alternatives for retrieving registered actions. +- `platform/bookmarks/src/com/intellij/ide/bookmark/ui/BookmarksViewFactory.kt:26` shows a platform example of setting tool window title actions using a looked-up action/group instead of calling `ActionGroup.getChildren(...)` directly. +- `platform/platform-api/src/com/intellij/openapi/options/ShowSettingsUtil.java:32-42` exposes the public predicate-based `showSettingsDialog(...)` API already used by `ShowProfileAction`. + +## Root Causes + +1. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt` + - Implements `ActionRemoteBehaviorSpecification.Frontend`. + - The verifier flags `ActionRemoteBehaviorSpecification.Frontend`, `ActionRemoteBehavior`, and `getBehavior()` as internal API usage. + - The actual settings behavior already uses public `ShowSettingsUtil.showSettingsDialog(project, predicate, additionalConfiguration)`. + - The action is declared in `kilo.jetbrains.frontend.xml`, so it should already be frontend-side in split mode without the internal remoting marker. + +2. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt` + - Imports and uses `CachedImageIcon`, `SVGLoader.SvgElementColorPatcherProvider`, and `SvgAttributePatcher` through `createWithPatcher(...)`. + - The verifier flags these classes and methods as internal API usage. + - The local IntelliJ source confirms this helper is copied from internal platform code, but the copied implementation still references internal platform symbols. + - Current usage is only from `ScrollButtonIcon.kt`, so this can be fixed locally without broad UI churn. + +3. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt` + - Calls `toolbar.getChildren(null)` to expand `Kilo.ToolWindowToolbar`. + - The verifier flags direct invocation of `ActionGroup.getChildren(AnActionEvent)` as override-only API usage. + +## Plan + +1. Replace the override-only toolbar expansion in `KiloToolWindowFactory.kt`. + - Stop calling `ActionGroup.getChildren(null)`. + - Prefer `ActionUtil.getActionGroup("Kilo.NewSession", "Kilo.History", "Kilo.ShowProfile", "Kilo.Settings")` or `listOfNotNull(ActionUtil.getAction(...))`. + - Pass the resulting action or action list directly to `toolWindow.setTitleActions(...)`. + - Keep the existing XML group only if still useful for declarative organization; do not expand it manually. + - Remove the now-unused `ActionGroup` import if no longer needed. + +2. Remove internal action-remoting API from `ShowProfileAction.kt`. + - Delete the `ActionRemoteBehaviorSpecification` import. + - Remove `ActionRemoteBehaviorSpecification.Frontend` from the class declaration. + - Keep the existing public predicate-based `ShowSettingsUtil.showSettingsDialog(...)` call; this is already the correct OpenAPI way to open a wrapped configurable and focus `UserProfileConfigurable.FOCUS_ACCOUNT_COMBO`. + - Validate split-mode behavior after the change because the frontend module XML declaration should be sufficient for a frontend-only action. + +3. Replace internal SVG patching in `SvgIconColorizer.kt` with Kilo-owned public-API code. + - Do not import or reference `CachedImageIcon`, `SVGLoader.SvgElementColorPatcherProvider`, `SvgAttributePatcher`, or `createWithPatcher(...)`. + - Keep the useful copied semantics from the IntelliJ internal source: attribute/pixel replacement by fill/stroke role and alpha preservation. + - Implement the replacement as a Kilo-owned `Icon` wrapper using public Swing/Java2D APIs: + - Paint the source icon into an offscreen `BufferedImage` at the current icon size. + - Replace known source colors for the supported scroll icons with target `fillColor` and `borderColor`, preserving alpha. + - Cache the recolored image by icon size and target RGB/alpha to avoid repaint-time allocation where practical. + - Limit the helper to the current use case if possible. `ScrollButtonIcon.kt` only recolors `scroll-bottom.svg` and `scroll-question.svg`, so supporting arbitrary SVG DOM patching is unnecessary. + - If exact SVG attribute-level theming becomes required later, vendor a complete independent SVG loading path under Kilo-owned code rather than referencing JetBrains internal icon classes. + +4. Update `ScrollButtonIcon.kt` only as needed for the new public helper. + - Keep the existing SVG resources if the new helper still paints from them. + - If the helper becomes scroll-icon-specific, pass the source colors or role mapping explicitly from `ScrollButtonIcon.kt` so `SvgIconColorizer.kt` does not need to know XML element IDs. + - Preserve theme-derived target colors: `UiStyle.Colors.warningLabelForeground()`, `JBUI.CurrentTheme.Button.defaultButtonColorStart()`, and `JBUI.CurrentTheme.Button.defaultButtonForeground()`. + +5. Re-check for verifier-blocked APIs. + - Search `packages/kilo-jetbrains/frontend/src/main/kotlin` for `ActionRemoteBehaviorSpecification`, `ActionRemoteBehavior`, `CachedImageIcon`, `SvgElementColorPatcherProvider`, `SvgAttributePatcher`, `createWithPatcher`, and `getChildren(null)`. + - Confirm no remaining usages correspond to the verifier report. + +6. Validate locally from `packages/kilo-jetbrains/`. + - Run `java -version` and confirm Java 21. + - Run `./gradlew typecheck`. + - Run `./gradlew test` if the touched frontend/UI code has related tests or if typecheck passes quickly. + - Run `./gradlew verifyPlugin --stacktrace` for a non-production local verifier pass. + - If production CLI resources are available or can be prepared, run `bun script/build.ts --production --prepare-cli` and `./gradlew verifyPlugin -Pproduction=true -Pkilo.channel=eap --stacktrace` from the tag context or with an equivalent local tag setup. + +## Notes + +- Do not suppress `INTERNAL_API_USAGES` or `OVERRIDE_ONLY_API_USAGES`; the publish workflow is correctly catching Marketplace-incompatible plugin API usage. +- Do not use `com.intellij.openapi.actionSystem.impl.Utils.expandActionGroup(...)`; it lives in an implementation package and is not the clean OpenAPI fix for plugin code. +- The Node.js 20 warning in the job is unrelated to this failure. The workflow already sets `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` and uses Node 24. +- No changeset is required unless the final implementation changes user-visible JetBrains plugin behavior beyond restoring publish verification. diff --git a/.kilo/plans/1779921627869-shiny-circuit.md b/.kilo/plans/1779921627869-shiny-circuit.md new file mode 100644 index 00000000000..0281faca5e0 --- /dev/null +++ b/.kilo/plans/1779921627869-shiny-circuit.md @@ -0,0 +1,72 @@ +# Implement JetBrains Follow-ups + +## Context + +The current worktree already has the previous verifier-oriented changes: + +- `ShowProfileAction` no longer implements the internal remoting marker. Keep this fix. +- `SvgIconColorizer` uses a public Swing/Java2D icon wrapper. Keep this fix. +- `ScrollButtonIcon` currently owns SVG source-color constants that should move to `UiStyle`. +- `KiloToolWindowFactory` currently uses direct per-action lookup, but the requested behavior is to look up `Kilo.ToolWindowToolbar`, cast to `ActionGroup`, and expand its children. +- Runtime stack trace shows `PromptPanel.syncEditorHeight()` calls `editor.document.lineCount` during initialization/style application on EDT without read access. + +## Implementation Plan + +1. Update tool-window title action expansion as requested. + - In `KiloToolWindowFactory.kt`, import `ActionGroup` again. + - Replace the hardcoded `listOfNotNull(ActionManager.getInstance().getAction("Kilo.NewSession"), ...)` with: + - `val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar")` + - `if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList())` + - Keep the existing XML group `Kilo.ToolWindowToolbar` as the source of ordering and membership. + - Note for validation: this intentionally returns to the requested `getChildren(null)` pattern, which may be reported by plugin verifier as override-only API usage again. Verify and report the exact outcome. + +2. Keep `ShowProfileAction` unchanged except for incidental formatting if required. + - Do not reintroduce `ActionRemoteBehaviorSpecification`. + - Keep the public predicate-based `ShowSettingsUtil.showSettingsDialog(...)` implementation. + +3. Move scroll icon source-color constants into `UiStyle`. + - Add a small `object ScrollIcon` (or similarly narrow name) under `UiStyle` with: + - `const val BOTTOM_LIGHT = 0x384F6B` + - `const val BOTTOM_DARK = 0x233143` + - `const val QUESTION = 0xE08800` + - `const val FOREGROUND = 0xFFFFFF` + - These are asset source palette constants, not runtime theme colors, so keep them separate from `UiStyle.Colors` unless the existing style layout strongly favors nesting there. + - Update `ScrollButtonIcon.kt` to reference `UiStyle.ScrollIcon.*` and remove local constants. + - Keep the public `colorizeIfPossible(...)` Java2D implementation unchanged. + +4. Fix prompt editor read-access violation. + - In `PromptPanel.kt`, wrap the `editor.document.lineCount` read inside `syncEditorHeight()` with a public read-action helper. + - Prefer `ReadAction.computeBlocking { editor.document.lineCount }` from `com.intellij.openapi.application.ReadAction` because IntelliJ source documents it as usable from EDT and it avoids the experimental `WriteIntentReadAction` API. + - Keep the rest of `syncEditorHeight()` on EDT; only the model/document access needs the read action. + - Do not move initialization off EDT; this is UI construction and mutation. + +5. Validate blocked verifier symbols and threading fix. + - Search `packages/kilo-jetbrains/frontend/src/main/kotlin` for: + - `ActionRemoteBehaviorSpecification` + - `ActionRemoteBehavior` + - `CachedImageIcon` + - `SvgElementColorPatcherProvider` + - `SvgAttributePatcher` + - `createWithPatcher` + - Also search for `getChildren(null)` and confirm it exists only in `KiloToolWindowFactory.kt` as requested. + +6. Run focused checks from `packages/kilo-jetbrains/`. + - `java -version` to confirm Java 21. + - `./gradlew typecheck`. + - `./gradlew test` because `PromptPanelTest` and session UI tests exercise the touched prompt UI. + - `./gradlew verifyPlugin --stacktrace` if CLI resources are present. + - If verifier fails only on the intentionally reintroduced `ActionGroup.getChildren(null)` override-only usage, report that clearly rather than masking it. + +## Expected Files To Edit + +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt` +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt` +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt` +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` + +## Non-Goals + +- Do not change the already accepted `ShowProfileAction` fix. +- Do not reintroduce internal SVG patching APIs. +- Do not suppress plugin verifier failures. +- Do not edit module XML unless action IDs or group membership need to change, which is not expected. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt index 99c596b3d7c..fcbe14332c7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt @@ -65,7 +65,7 @@ class KiloToolWindowFactory : ToolWindowFactory, DumbAware { manager.newSession() val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar") - if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList()) + if (toolbar is ActionGroup) toolWindow.setTitleActions(listOf(toolbar)) } catch (e: Exception) { LOG.error("Failed to set up Kilo tool window content", e) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt index 83cb7cd9025..ebf954c9fa1 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt @@ -5,7 +5,6 @@ import ai.kilocode.client.settings.profile.UserProfileConfigurable import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.ConfigurableWithId import com.intellij.openapi.options.ShowSettingsUtil @@ -22,7 +21,7 @@ class ShowProfileAction : DumbAwareAction( KiloBundle.message("action.Kilo.ShowProfile.text"), KiloBundle.message("action.Kilo.ShowProfile.description"), AllIcons.General.User, -), ActionRemoteBehaviorSpecification.Frontend { +) { override fun actionPerformed(e: AnActionEvent) { ShowSettingsUtil.getInstance().showSettingsDialog( diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt index f51a7d9b880..1f3a664d06a 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt @@ -1,46 +1,86 @@ package ai.kilocode.client.ui -import com.intellij.ui.icons.CachedImageIcon -import com.intellij.ui.svg.SvgAttributePatcher -import com.intellij.util.SVGLoader +import com.intellij.ui.JBColor import java.awt.Color +import java.awt.Component +import java.awt.Graphics +import java.awt.image.BufferedImage import javax.swing.Icon private const val OPAQUE_ALPHA = 255 +private const val RGB_MASK = 0x00FFFFFF internal fun Icon.colorizeIfPossible( fillColor: Color, borderColor: Color = fillColor, - fillId: String? = null, - strokeId: String? = null, -): Icon = (this as? CachedImageIcon)?.createWithPatcher( - colorPatcher = object : SVGLoader.SvgElementColorPatcherProvider, SvgAttributePatcher { - private val digest = longArrayOf(0L, 440413911775177385) + fillColors: Collection, + borderColors: Collection, +): Icon = ColorizedIcon( + source = this, + fill = fillColor, + border = borderColor, + fills = fillColors.map { it and RGB_MASK }.toSet(), + borders = borderColors.map { it and RGB_MASK }.toSet(), +) - override fun digest(): LongArray { - digest[0] = toLong(fillColor.rgb, borderColor.rgb) - return digest - } +private data class Key( + val width: Int, + val height: Int, + val fill: Int, + val border: Int, + val bright: Boolean, +) - override fun patchColors(attributes: MutableMap) { - val id = attributes["id"] - if (fillId == null || id == fillId) setAttribute(attributes, "fill", fillColor) - if (strokeId == null || id == strokeId) setAttribute(attributes, "stroke", borderColor) - } +private class ColorizedIcon( + private val source: Icon, + private val fill: Color, + private val border: Color, + private val fills: Set, + private val borders: Set, +) : Icon { + private val cache = mutableMapOf() - override fun attributeForPath(path: String) = this + override fun getIconWidth(): Int = source.iconWidth - private fun setAttribute(attributes: MutableMap, key: String, color: Color) { - if (!attributes.containsKey(key) || attributes[key] == "none") return - attributes[key] = "rgb(${color.red},${color.green},${color.blue})" - val alpha = color.alpha - if (alpha != OPAQUE_ALPHA) { - attributes["$key-opacity"] = "${alpha / OPAQUE_ALPHA.toFloat()}" + override fun getIconHeight(): Int = source.iconHeight + + override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { + val img = image(c) + if (img == null) return + g.drawImage(img, x, y, null) + } + + private fun image(c: Component?): BufferedImage? { + val width = iconWidth + val height = iconHeight + if (width <= 0 || height <= 0) return null + + val key = Key(width, height, fill.rgb, border.rgb, JBColor.isBright()) + return cache.getOrPut(key) { + val img = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + val g = img.createGraphics() + try { + source.paintIcon(c, g, 0, 0) + } finally { + g.dispose() } - } - private fun toLong(high: Int, low: Int): Long { - return (high.toLong() shl 32) or (low.toLong() and 0xFFFFFFFFL) + for (py in 0 until height) { + for (px in 0 until width) { + val argb = img.getRGB(px, py) + val rgb = argb and RGB_MASK + if (fills.contains(rgb)) img.setRGB(px, py, replace(argb, fill)) + if (borders.contains(rgb)) img.setRGB(px, py, replace(argb, border)) + } + } + + img } - }, -) ?: this + } + + private fun replace(argb: Int, color: Color): Int { + val alpha = argb ushr 24 + val mixed = alpha * color.alpha / OPAQUE_ALPHA + return (mixed shl 24) or (color.rgb and RGB_MASK) + } +} From 9266d1ca0cc151a5bac41a0cb7b51cdd9c8666a6 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 03:51:38 +0000 Subject: [PATCH 14/59] feat(vscode): replace GitHub Copilot with DeepSeek in popular providers list --- packages/kilo-vscode/src/shared/provider-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/shared/provider-model.ts b/packages/kilo-vscode/src/shared/provider-model.ts index 21de283075f..cb7d6b9c341 100644 --- a/packages/kilo-vscode/src/shared/provider-model.ts +++ b/packages/kilo-vscode/src/shared/provider-model.ts @@ -6,7 +6,7 @@ export const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-_]*$/ export const PROVIDER_PRIORITY = [ KILO_PROVIDER_ID, "anthropic", - "github-copilot", + "deepseek", "openai", "google", "openrouter", From e3d28120b217836b703cc101a5ffe58ff80d9cbb Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 00:18:37 -0400 Subject: [PATCH 15/59] fix(jetbrains): smooth scroll button icon --- .changeset/jetbrains-smooth-scroll-button.md | 5 + .../client/session/scroll/ScrollButtonIcon.kt | 15 ++- .../ai/kilocode/client/ui/SvgIconColorizer.kt | 112 +++++++++++------- 3 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 .changeset/jetbrains-smooth-scroll-button.md diff --git a/.changeset/jetbrains-smooth-scroll-button.md b/.changeset/jetbrains-smooth-scroll-button.md new file mode 100644 index 00000000000..d03f21c3c3a --- /dev/null +++ b/.changeset/jetbrains-smooth-scroll-button.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Smooth the JetBrains session scroll button edges while preserving theme-aware SVG colors. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt index 1cb205ad1fe..694064e6d10 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt @@ -1,20 +1,21 @@ package ai.kilocode.client.session.scroll import ai.kilocode.client.session.ui.style.SessionUiStyle -import ai.kilocode.client.ui.colorizeIfPossible import ai.kilocode.client.ui.UiStyle -import com.intellij.openapi.util.IconLoader +import ai.kilocode.client.ui.colorizedSvgIcon import com.intellij.util.ui.JBUI import java.awt.Color import javax.swing.Icon internal object ScrollButtonIcon { - private val bottom = IconLoader.getIcon("/icons/scroll-bottom.svg", ScrollButtonIcon::class.java) - private val prompt = IconLoader.getIcon("/icons/scroll-question.svg", ScrollButtonIcon::class.java) + private const val BOTTOM = "/icons/scroll-bottom.svg" + private const val PROMPT = "/icons/scroll-question.svg" fun create(question: Boolean = false): Icon { if (question) { - return prompt.colorizeIfPossible( + return colorizedSvgIcon( + path = PROMPT, + owner = ScrollButtonIcon::class.java, fillColor = UiStyle.Colors.warningLabelForeground(), borderColor = Color.WHITE, fillColors = listOf(SessionUiStyle.ScrollIcon.QUESTION), @@ -22,7 +23,9 @@ internal object ScrollButtonIcon { ) } - return bottom.colorizeIfPossible( + return colorizedSvgIcon( + path = BOTTOM, + owner = ScrollButtonIcon::class.java, fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(), borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(), fillColors = listOf(SessionUiStyle.ScrollIcon.BOTTOM_LIGHT, SessionUiStyle.ScrollIcon.BOTTOM_DARK), diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt index 1f3a664d06a..54912b28176 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt @@ -1,22 +1,29 @@ package ai.kilocode.client.ui +import com.intellij.openapi.util.IconLoader import com.intellij.ui.JBColor import java.awt.Color import java.awt.Component import java.awt.Graphics -import java.awt.image.BufferedImage +import java.io.ByteArrayInputStream +import java.net.URI +import java.net.URL +import java.net.URLConnection +import java.net.URLStreamHandler import javax.swing.Icon -private const val OPAQUE_ALPHA = 255 private const val RGB_MASK = 0x00FFFFFF -internal fun Icon.colorizeIfPossible( +internal fun colorizedSvgIcon( + path: String, + owner: Class<*>, fillColor: Color, borderColor: Color = fillColor, fillColors: Collection, borderColors: Collection, -): Icon = ColorizedIcon( - source = this, +): Icon = SvgIcon( + path = path, + owner = owner, fill = fillColor, border = borderColor, fills = fillColors.map { it and RGB_MASK }.toSet(), @@ -24,63 +31,76 @@ internal fun Icon.colorizeIfPossible( ) private data class Key( - val width: Int, - val height: Int, val fill: Int, val border: Int, val bright: Boolean, ) -private class ColorizedIcon( - private val source: Icon, +private class SvgIcon( + private val path: String, + private val owner: Class<*>, private val fill: Color, private val border: Color, private val fills: Set, private val borders: Set, ) : Icon { - private val cache = mutableMapOf() - - override fun getIconWidth(): Int = source.iconWidth - - override fun getIconHeight(): Int = source.iconHeight - - override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { - val img = image(c) - if (img == null) return - g.drawImage(img, x, y, null) + private val cache = mutableMapOf() + private val data by lazy { + owner.getResourceAsStream(path)?.use { it.readBytes() } + ?: error("SVG icon not found: $path") } - private fun image(c: Component?): BufferedImage? { - val width = iconWidth - val height = iconHeight - if (width <= 0 || height <= 0) return null + override fun getIconWidth(): Int = icon().iconWidth - val key = Key(width, height, fill.rgb, border.rgb, JBColor.isBright()) + override fun getIconHeight(): Int = icon().iconHeight + + override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) = icon().paintIcon(c, g, x, y) + + private fun icon(): Icon { + val key = Key(fill.rgb, border.rgb, JBColor.isBright()) return cache.getOrPut(key) { - val img = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) - val g = img.createGraphics() - try { - source.paintIcon(c, g, 0, 0) - } finally { - g.dispose() - } - - for (py in 0 until height) { - for (px in 0 until width) { - val argb = img.getRGB(px, py) - val rgb = argb and RGB_MASK - if (fills.contains(rgb)) img.setRGB(px, py, replace(argb, fill)) - if (borders.contains(rgb)) img.setRGB(px, py, replace(argb, border)) - } - } - - img + IconLoader.findIcon(url(patch()), false) ?: IconLoader.getIcon(path, owner) } } - private fun replace(argb: Int, color: Color): Int { - val alpha = argb ushr 24 - val mixed = alpha * color.alpha / OPAQUE_ALPHA - return (mixed shl 24) or (color.rgb and RGB_MASK) + private fun patch(): ByteArray { + val svg = data.toString(Charsets.UTF_8) + val patched = ATTR.replace(svg) { + val attr = it.groupValues[1] + val rgb = parse(it.groupValues[2]) ?: return@replace it.value + val color = when { + fills.contains(rgb) -> fill + borders.contains(rgb) -> border + else -> return@replace it.value + } + "$attr=\"${hex(color)}\"" + } + return patched.toByteArray(Charsets.UTF_8) + } + + private fun url(data: ByteArray): URL { + val name = path.substringAfterLast('/').removeSuffix(".svg") + val key = "${name}-${fill.rgb}-${border.rgb}-${JBColor.isBright()}.svg" + return URL.of(URI("memory:/kilo-icons/$key"), Handler(data)) } } + +private class Handler(private val data: ByteArray) : URLStreamHandler() { + override fun openConnection(u: URL): URLConnection { + return object : URLConnection(u) { + override fun connect() {} + + override fun getInputStream() = ByteArrayInputStream(data) + } + } +} + +private val ATTR = Regex("""\b(fill|stroke)=["'](#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8}))["']""") + +private fun parse(value: String): Int? { + return value.removePrefix("#").take(6).toIntOrNull(16)?.and(RGB_MASK) +} + +private fun hex(color: Color): String { + return "#%02X%02X%02X".format(color.red, color.green, color.blue) +} From 449c7407841c039407b2213f2219b8c8c79172d7 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 07:44:16 -0400 Subject: [PATCH 16/59] fix(jetbrains): address scroll icon review --- .../kilocode/client/KiloToolWindowFactory.kt | 2 +- .../client/session/scroll/ScrollButtonIcon.kt | 39 +++++++------- .../client/session/ui/prompt/PromptPanel.kt | 3 +- .../ai/kilocode/client/ui/SvgIconColorizer.kt | 54 ++++++++++--------- .../kilo-jetbrains/script/build-sign-check.sh | 5 +- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt index fcbe14332c7..99c596b3d7c 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt @@ -65,7 +65,7 @@ class KiloToolWindowFactory : ToolWindowFactory, DumbAware { manager.newSession() val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar") - if (toolbar is ActionGroup) toolWindow.setTitleActions(listOf(toolbar)) + if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList()) } catch (e: Exception) { LOG.error("Failed to set up Kilo tool window content", e) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt index 694064e6d10..d50d4dfe501 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt @@ -8,28 +8,25 @@ import java.awt.Color import javax.swing.Icon internal object ScrollButtonIcon { - private const val BOTTOM = "/icons/scroll-bottom.svg" - private const val PROMPT = "/icons/scroll-question.svg" + private val bottom = colorizedSvgIcon( + path = "/icons/scroll-bottom.svg", + owner = ScrollButtonIcon::class.java, + fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(), + borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(), + fillColors = listOf(SessionUiStyle.ScrollIcon.BOTTOM_LIGHT, SessionUiStyle.ScrollIcon.BOTTOM_DARK), + borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), + ) + private val prompt = colorizedSvgIcon( + path = "/icons/scroll-question.svg", + owner = ScrollButtonIcon::class.java, + fillColor = UiStyle.Colors.warningLabelForeground(), + borderColor = Color.WHITE, + fillColors = listOf(SessionUiStyle.ScrollIcon.QUESTION), + borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), + ) fun create(question: Boolean = false): Icon { - if (question) { - return colorizedSvgIcon( - path = PROMPT, - owner = ScrollButtonIcon::class.java, - fillColor = UiStyle.Colors.warningLabelForeground(), - borderColor = Color.WHITE, - fillColors = listOf(SessionUiStyle.ScrollIcon.QUESTION), - borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), - ) - } - - return colorizedSvgIcon( - path = BOTTOM, - owner = ScrollButtonIcon::class.java, - fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(), - borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(), - fillColors = listOf(SessionUiStyle.ScrollIcon.BOTTOM_LIGHT, SessionUiStyle.ScrollIcon.BOTTOM_DARK), - borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), - ) + if (question) return prompt + return bottom } } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt index 7ff28bfe2e4..3be312fbbf6 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt @@ -26,7 +26,6 @@ import com.intellij.openapi.actionSystem.UiDataProvider import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.keymap.Keymap @@ -364,7 +363,7 @@ class PromptPanel( @RequiresEdt private fun syncEditorHeight() { - val count = ReadAction.computeBlocking { editor.document.lineCount } + val count = ApplicationManager.getApplication().runReadAction { editor.document.lineCount } val lines = (count + SessionUiStyle.View.Prompt.EDITOR_SPARE_LINES).coerceIn( SessionUiStyle.View.Prompt.EDITOR_LINES, SessionUiStyle.View.Prompt.EDITOR_MAX_LINES, diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt index 54912b28176..b2e40be3477 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt @@ -1,15 +1,15 @@ package ai.kilocode.client.ui -import com.intellij.openapi.util.IconLoader import com.intellij.ui.JBColor +import com.intellij.util.SVGLoader import java.awt.Color import java.awt.Component import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Image +import java.awt.RenderingHints import java.io.ByteArrayInputStream -import java.net.URI -import java.net.URL -import java.net.URLConnection -import java.net.URLStreamHandler +import kotlin.math.ceil import javax.swing.Icon private const val RGB_MASK = 0x00FFFFFF @@ -34,6 +34,7 @@ private data class Key( val fill: Int, val border: Int, val bright: Boolean, + val scale: Double, ) private class SvgIcon( @@ -44,22 +45,31 @@ private class SvgIcon( private val fills: Set, private val borders: Set, ) : Icon { - private val cache = mutableMapOf() + private val cache = mutableMapOf() private val data by lazy { owner.getResourceAsStream(path)?.use { it.readBytes() } ?: error("SVG icon not found: $path") } + private val size by lazy { size(data.toString(Charsets.UTF_8)) } - override fun getIconWidth(): Int = icon().iconWidth + override fun getIconWidth(): Int = size.first - override fun getIconHeight(): Int = icon().iconHeight + override fun getIconHeight(): Int = size.second - override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) = icon().paintIcon(c, g, x, y) + override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { + val g2 = g as? Graphics2D + if (g2 != null) { + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY) + } + g.drawImage(image(g), x, y, iconWidth, iconHeight, null) + } - private fun icon(): Icon { - val key = Key(fill.rgb, border.rgb, JBColor.isBright()) + private fun image(g: Graphics): Image { + val scale = scale(g) + val key = Key(fill.rgb, border.rgb, JBColor.isBright(), scale) return cache.getOrPut(key) { - IconLoader.findIcon(url(patch()), false) ?: IconLoader.getIcon(path, owner) + SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat()) } } @@ -78,24 +88,20 @@ private class SvgIcon( return patched.toByteArray(Charsets.UTF_8) } - private fun url(data: ByteArray): URL { - val name = path.substringAfterLast('/').removeSuffix(".svg") - val key = "${name}-${fill.rgb}-${border.rgb}-${JBColor.isBright()}.svg" - return URL.of(URI("memory:/kilo-icons/$key"), Handler(data)) + private fun size(svg: String): Pair { + val width = SIZE.find(svg)?.groupValues?.get(1)?.toFloatOrNull() + val height = SIZE.find(svg)?.groupValues?.get(2)?.toFloatOrNull() + return Pair(ceil(width ?: 16f).toInt(), ceil(height ?: 16f).toInt()) } -} -private class Handler(private val data: ByteArray) : URLStreamHandler() { - override fun openConnection(u: URL): URLConnection { - return object : URLConnection(u) { - override fun connect() {} - - override fun getInputStream() = ByteArrayInputStream(data) - } + private fun scale(g: Graphics): Double { + if (g !is Graphics2D) return 1.0 + return g.deviceConfiguration.defaultTransform.scaleX.coerceAtLeast(1.0) } } private val ATTR = Regex("""\b(fill|stroke)=["'](#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8}))["']""") +private val SIZE = Regex("""]*\bwidth=["']([0-9.]+)["'][^>]*\bheight=["']([0-9.]+)["']""") private fun parse(value: String): Int? { return value.removePrefix("#").take(6).toIntOrNull(16)?.and(RGB_MASK) diff --git a/packages/kilo-jetbrains/script/build-sign-check.sh b/packages/kilo-jetbrains/script/build-sign-check.sh index b871785454d..87825b9143d 100755 --- a/packages/kilo-jetbrains/script/build-sign-check.sh +++ b/packages/kilo-jetbrains/script/build-sign-check.sh @@ -3,6 +3,7 @@ set -euo pipefail usage() { echo "Usage: $0 [--skip-verification]" >&2 + echo "Example: $0 7.0.1" >&2 echo "Example: $0 7.0.1-rc.1" >&2 echo "Example: $0 v7.0.1-rc.1 --skip-verification" >&2 echo "Builds/signs the current checkout without creating or validating a git tag." >&2 @@ -42,8 +43,8 @@ if [[ -z "$raw" ]]; then fi version="${raw#v}" -if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then - echo "Unsupported version '$raw'. Expected x.y.z-rc.n, for example 7.0.1-rc.1." >&2 +if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then + echo "Unsupported version '$raw'. Expected x.y.z or x.y.z-rc.n, for example 7.0.1 or 7.0.1-rc.1." >&2 exit 1 fi From ad2bb21048769baef12904a883e2c433eb794c6e Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 09:20:09 -0400 Subject: [PATCH 17/59] chore: remove plan files from PR --- .kilo/plans/1779919295135-mighty-tiger.md | 90 ---------------------- .kilo/plans/1779921627869-shiny-circuit.md | 72 ----------------- 2 files changed, 162 deletions(-) delete mode 100644 .kilo/plans/1779919295135-mighty-tiger.md delete mode 100644 .kilo/plans/1779921627869-shiny-circuit.md diff --git a/.kilo/plans/1779919295135-mighty-tiger.md b/.kilo/plans/1779919295135-mighty-tiger.md deleted file mode 100644 index 371e861e131..00000000000 --- a/.kilo/plans/1779919295135-mighty-tiger.md +++ /dev/null @@ -1,90 +0,0 @@ -# Fix JetBrains Publish Verification - -## Failure Summary - -The `publish-jetbrains` workflow failed in the `Verify plugin` step for tag `jetbrains/v7.0.1-rc.2`. - -The verifier reports the plugin is otherwise compatible with `IU-261.22158.277`, but fails the build because of blocking problem classes: - -- `INTERNAL_API_USAGES` -- `OVERRIDE_ONLY_API_USAGES` - -Non-blocking verifier output also includes deprecated and experimental API usages, but those are not the reason for this publish failure. - -## Local IntelliJ Source Insights - -The local IntelliJ checkout at `/Users/kirillk/products/intellij-community` confirms the verifier findings and the safe replacements: - -- `platform/editor-ui-api/src/com/intellij/openapi/actionSystem/remoting/ActionRemoteBehavior.kt:10-12` and `70-72` mark `ActionRemoteBehavior` and `ActionRemoteBehaviorSpecification` as `@ApiStatus.Internal` and `@ApiStatus.Experimental`. -- `platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt:338-346` implements `createWithPatcher(...)`, but it depends on internal icon loader state. -- `platform/util/ui/src/com/intellij/util/SVGLoader.kt:105-115` defines `SvgElementColorPatcherProvider`; it is part of the internal patching path. -- `platform/platform-impl/src/com/intellij/ui/GotItComponentBuilder.kt:1169-1204` contains the source pattern that `SvgIconColorizer.kt` mirrors, including digest generation and `setAttribute(...)` behavior. -- `platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.kt:662-681` exposes `ActionUtil.getAction(...)` and `ActionUtil.getActionGroup(vararg ids)` as non-override alternatives for retrieving registered actions. -- `platform/bookmarks/src/com/intellij/ide/bookmark/ui/BookmarksViewFactory.kt:26` shows a platform example of setting tool window title actions using a looked-up action/group instead of calling `ActionGroup.getChildren(...)` directly. -- `platform/platform-api/src/com/intellij/openapi/options/ShowSettingsUtil.java:32-42` exposes the public predicate-based `showSettingsDialog(...)` API already used by `ShowProfileAction`. - -## Root Causes - -1. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt` - - Implements `ActionRemoteBehaviorSpecification.Frontend`. - - The verifier flags `ActionRemoteBehaviorSpecification.Frontend`, `ActionRemoteBehavior`, and `getBehavior()` as internal API usage. - - The actual settings behavior already uses public `ShowSettingsUtil.showSettingsDialog(project, predicate, additionalConfiguration)`. - - The action is declared in `kilo.jetbrains.frontend.xml`, so it should already be frontend-side in split mode without the internal remoting marker. - -2. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt` - - Imports and uses `CachedImageIcon`, `SVGLoader.SvgElementColorPatcherProvider`, and `SvgAttributePatcher` through `createWithPatcher(...)`. - - The verifier flags these classes and methods as internal API usage. - - The local IntelliJ source confirms this helper is copied from internal platform code, but the copied implementation still references internal platform symbols. - - Current usage is only from `ScrollButtonIcon.kt`, so this can be fixed locally without broad UI churn. - -3. `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt` - - Calls `toolbar.getChildren(null)` to expand `Kilo.ToolWindowToolbar`. - - The verifier flags direct invocation of `ActionGroup.getChildren(AnActionEvent)` as override-only API usage. - -## Plan - -1. Replace the override-only toolbar expansion in `KiloToolWindowFactory.kt`. - - Stop calling `ActionGroup.getChildren(null)`. - - Prefer `ActionUtil.getActionGroup("Kilo.NewSession", "Kilo.History", "Kilo.ShowProfile", "Kilo.Settings")` or `listOfNotNull(ActionUtil.getAction(...))`. - - Pass the resulting action or action list directly to `toolWindow.setTitleActions(...)`. - - Keep the existing XML group only if still useful for declarative organization; do not expand it manually. - - Remove the now-unused `ActionGroup` import if no longer needed. - -2. Remove internal action-remoting API from `ShowProfileAction.kt`. - - Delete the `ActionRemoteBehaviorSpecification` import. - - Remove `ActionRemoteBehaviorSpecification.Frontend` from the class declaration. - - Keep the existing public predicate-based `ShowSettingsUtil.showSettingsDialog(...)` call; this is already the correct OpenAPI way to open a wrapped configurable and focus `UserProfileConfigurable.FOCUS_ACCOUNT_COMBO`. - - Validate split-mode behavior after the change because the frontend module XML declaration should be sufficient for a frontend-only action. - -3. Replace internal SVG patching in `SvgIconColorizer.kt` with Kilo-owned public-API code. - - Do not import or reference `CachedImageIcon`, `SVGLoader.SvgElementColorPatcherProvider`, `SvgAttributePatcher`, or `createWithPatcher(...)`. - - Keep the useful copied semantics from the IntelliJ internal source: attribute/pixel replacement by fill/stroke role and alpha preservation. - - Implement the replacement as a Kilo-owned `Icon` wrapper using public Swing/Java2D APIs: - - Paint the source icon into an offscreen `BufferedImage` at the current icon size. - - Replace known source colors for the supported scroll icons with target `fillColor` and `borderColor`, preserving alpha. - - Cache the recolored image by icon size and target RGB/alpha to avoid repaint-time allocation where practical. - - Limit the helper to the current use case if possible. `ScrollButtonIcon.kt` only recolors `scroll-bottom.svg` and `scroll-question.svg`, so supporting arbitrary SVG DOM patching is unnecessary. - - If exact SVG attribute-level theming becomes required later, vendor a complete independent SVG loading path under Kilo-owned code rather than referencing JetBrains internal icon classes. - -4. Update `ScrollButtonIcon.kt` only as needed for the new public helper. - - Keep the existing SVG resources if the new helper still paints from them. - - If the helper becomes scroll-icon-specific, pass the source colors or role mapping explicitly from `ScrollButtonIcon.kt` so `SvgIconColorizer.kt` does not need to know XML element IDs. - - Preserve theme-derived target colors: `UiStyle.Colors.warningLabelForeground()`, `JBUI.CurrentTheme.Button.defaultButtonColorStart()`, and `JBUI.CurrentTheme.Button.defaultButtonForeground()`. - -5. Re-check for verifier-blocked APIs. - - Search `packages/kilo-jetbrains/frontend/src/main/kotlin` for `ActionRemoteBehaviorSpecification`, `ActionRemoteBehavior`, `CachedImageIcon`, `SvgElementColorPatcherProvider`, `SvgAttributePatcher`, `createWithPatcher`, and `getChildren(null)`. - - Confirm no remaining usages correspond to the verifier report. - -6. Validate locally from `packages/kilo-jetbrains/`. - - Run `java -version` and confirm Java 21. - - Run `./gradlew typecheck`. - - Run `./gradlew test` if the touched frontend/UI code has related tests or if typecheck passes quickly. - - Run `./gradlew verifyPlugin --stacktrace` for a non-production local verifier pass. - - If production CLI resources are available or can be prepared, run `bun script/build.ts --production --prepare-cli` and `./gradlew verifyPlugin -Pproduction=true -Pkilo.channel=eap --stacktrace` from the tag context or with an equivalent local tag setup. - -## Notes - -- Do not suppress `INTERNAL_API_USAGES` or `OVERRIDE_ONLY_API_USAGES`; the publish workflow is correctly catching Marketplace-incompatible plugin API usage. -- Do not use `com.intellij.openapi.actionSystem.impl.Utils.expandActionGroup(...)`; it lives in an implementation package and is not the clean OpenAPI fix for plugin code. -- The Node.js 20 warning in the job is unrelated to this failure. The workflow already sets `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` and uses Node 24. -- No changeset is required unless the final implementation changes user-visible JetBrains plugin behavior beyond restoring publish verification. diff --git a/.kilo/plans/1779921627869-shiny-circuit.md b/.kilo/plans/1779921627869-shiny-circuit.md deleted file mode 100644 index 0281faca5e0..00000000000 --- a/.kilo/plans/1779921627869-shiny-circuit.md +++ /dev/null @@ -1,72 +0,0 @@ -# Implement JetBrains Follow-ups - -## Context - -The current worktree already has the previous verifier-oriented changes: - -- `ShowProfileAction` no longer implements the internal remoting marker. Keep this fix. -- `SvgIconColorizer` uses a public Swing/Java2D icon wrapper. Keep this fix. -- `ScrollButtonIcon` currently owns SVG source-color constants that should move to `UiStyle`. -- `KiloToolWindowFactory` currently uses direct per-action lookup, but the requested behavior is to look up `Kilo.ToolWindowToolbar`, cast to `ActionGroup`, and expand its children. -- Runtime stack trace shows `PromptPanel.syncEditorHeight()` calls `editor.document.lineCount` during initialization/style application on EDT without read access. - -## Implementation Plan - -1. Update tool-window title action expansion as requested. - - In `KiloToolWindowFactory.kt`, import `ActionGroup` again. - - Replace the hardcoded `listOfNotNull(ActionManager.getInstance().getAction("Kilo.NewSession"), ...)` with: - - `val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar")` - - `if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList())` - - Keep the existing XML group `Kilo.ToolWindowToolbar` as the source of ordering and membership. - - Note for validation: this intentionally returns to the requested `getChildren(null)` pattern, which may be reported by plugin verifier as override-only API usage again. Verify and report the exact outcome. - -2. Keep `ShowProfileAction` unchanged except for incidental formatting if required. - - Do not reintroduce `ActionRemoteBehaviorSpecification`. - - Keep the public predicate-based `ShowSettingsUtil.showSettingsDialog(...)` implementation. - -3. Move scroll icon source-color constants into `UiStyle`. - - Add a small `object ScrollIcon` (or similarly narrow name) under `UiStyle` with: - - `const val BOTTOM_LIGHT = 0x384F6B` - - `const val BOTTOM_DARK = 0x233143` - - `const val QUESTION = 0xE08800` - - `const val FOREGROUND = 0xFFFFFF` - - These are asset source palette constants, not runtime theme colors, so keep them separate from `UiStyle.Colors` unless the existing style layout strongly favors nesting there. - - Update `ScrollButtonIcon.kt` to reference `UiStyle.ScrollIcon.*` and remove local constants. - - Keep the public `colorizeIfPossible(...)` Java2D implementation unchanged. - -4. Fix prompt editor read-access violation. - - In `PromptPanel.kt`, wrap the `editor.document.lineCount` read inside `syncEditorHeight()` with a public read-action helper. - - Prefer `ReadAction.computeBlocking { editor.document.lineCount }` from `com.intellij.openapi.application.ReadAction` because IntelliJ source documents it as usable from EDT and it avoids the experimental `WriteIntentReadAction` API. - - Keep the rest of `syncEditorHeight()` on EDT; only the model/document access needs the read action. - - Do not move initialization off EDT; this is UI construction and mutation. - -5. Validate blocked verifier symbols and threading fix. - - Search `packages/kilo-jetbrains/frontend/src/main/kotlin` for: - - `ActionRemoteBehaviorSpecification` - - `ActionRemoteBehavior` - - `CachedImageIcon` - - `SvgElementColorPatcherProvider` - - `SvgAttributePatcher` - - `createWithPatcher` - - Also search for `getChildren(null)` and confirm it exists only in `KiloToolWindowFactory.kt` as requested. - -6. Run focused checks from `packages/kilo-jetbrains/`. - - `java -version` to confirm Java 21. - - `./gradlew typecheck`. - - `./gradlew test` because `PromptPanelTest` and session UI tests exercise the touched prompt UI. - - `./gradlew verifyPlugin --stacktrace` if CLI resources are present. - - If verifier fails only on the intentionally reintroduced `ActionGroup.getChildren(null)` override-only usage, report that clearly rather than masking it. - -## Expected Files To Edit - -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt` -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt` -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt` -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` - -## Non-Goals - -- Do not change the already accepted `ShowProfileAction` fix. -- Do not reintroduce internal SVG patching APIs. -- Do not suppress plugin verifier failures. -- Do not edit module XML unless action IDs or group membership need to change, which is not expected. From e3bfbc7054f34770aeec2bb775bbf13055051d4c Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 09:41:10 -0400 Subject: [PATCH 18/59] fix(jetbrains): avoid unsafe UI initialization --- .../ai/kilocode/client/KiloToolWindowFactory.kt | 10 +++++++--- .../kotlin/ai/kilocode/client/session/SessionUi.kt | 13 ++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt index 99c596b3d7c..d449a5a1da4 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt @@ -4,7 +4,6 @@ import ai.kilocode.client.app.KiloWorkspaceService import ai.kilocode.client.app.Workspace import ai.kilocode.client.session.SessionSidePanelManager import ai.kilocode.log.KiloLog -import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.components.service import com.intellij.openapi.project.DumbAware @@ -64,8 +63,13 @@ class KiloToolWindowFactory : ToolWindowFactory, DumbAware { toolWindow.contentManager.setSelectedContent(content) manager.newSession() - val toolbar = ActionManager.getInstance().getAction("Kilo.ToolWindowToolbar") - if (toolbar is ActionGroup) toolWindow.setTitleActions(toolbar.getChildren(null).toList()) + val actions = listOfNotNull( + ActionManager.getInstance().getAction("Kilo.NewSession"), + ActionManager.getInstance().getAction("Kilo.History"), + ActionManager.getInstance().getAction("Kilo.ShowProfile"), + ActionManager.getInstance().getAction("Kilo.Settings"), + ) + toolWindow.setTitleActions(actions) } catch (e: Exception) { LOG.error("Failed to set up Kilo tool window content", e) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 19628c239a0..7f502d36877 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -40,6 +40,7 @@ import com.intellij.ide.BrowserUtil import com.intellij.ide.ui.LafManagerListener import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ReadAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.colors.EditorColorsListener import com.intellij.openapi.editor.colors.EditorColorsManager @@ -256,11 +257,13 @@ class SessionUi( scroll = SessionScroll(root, sessionContent, messageBody, blankBody) connection = ConnectionPanel(this, controller) - prompt = PromptPanel( - project = project, - onSend = { text -> sendPrompt(text) }, - onAbort = { controller.abort() }, - ) + prompt = ReadAction.computeBlocking { + PromptPanel( + project = project, + onSend = { text -> sendPrompt(text) }, + onAbort = { controller.abort() }, + ) + } sessionContent.add(header, BorderLayout.NORTH) sessionContent.add(scroll.component, BorderLayout.CENTER) From 02daf3aad02dd0d3d8c3d981c6edfbbd9f5d8192 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 15:45:51 +0200 Subject: [PATCH 19/59] refactor(cli): remove Storage promise facade --- .../src/kilo-sessions/kilo-sessions.ts | 29 ++++++++++++------- packages/opencode/src/storage/storage.ts | 10 ------- script/check-opencode-promise-facades.ts | 1 - 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/opencode/src/kilo-sessions/kilo-sessions.ts b/packages/opencode/src/kilo-sessions/kilo-sessions.ts index 710846238c8..6a5ec72c3b6 100644 --- a/packages/opencode/src/kilo-sessions/kilo-sessions.ts +++ b/packages/opencode/src/kilo-sessions/kilo-sessions.ts @@ -493,7 +493,7 @@ export namespace KiloSessions { const result = (await response.json()) as { id: string; ingestPath: string } - await Storage.write(["session_share", sessionId], result) + await save(sessionId, result) log.info("session bootstrap completed", { sessionId }) @@ -537,7 +537,7 @@ export namespace KiloSessions { const url = `https://app.kilo.ai/s/${result.public_id}` - await Storage.write(["session_share", sessionId], { + await save(sessionId, { ...current, url, }) @@ -578,15 +578,23 @@ export namespace KiloSessions { } delete next.url - await Storage.write(["session_share", sessionId], next) + await save(sessionId, next) } - function get(sessionId: string) { - return Storage.read<{ - id: string - url?: string - ingestPath: string - }>(["session_share", sessionId]) + type Share = { + id: string + url?: string + ingestPath: string + } + + async function save(sessionId: string, share: Share) { + const { AppRuntime } = await import("@/effect/app-runtime") + return AppRuntime.runPromise(Storage.Service.use((svc) => svc.write(["session_share", sessionId], share))) + } + + async function get(sessionId: string) { + const { AppRuntime } = await import("@/effect/app-runtime") + return AppRuntime.runPromise(Storage.Service.use((svc) => svc.read(["session_share", sessionId]))) } export async function remove(sessionId: string) { @@ -618,7 +626,8 @@ export namespace KiloSessions { return } - await Storage.remove(["session_share", sessionId]) + const { AppRuntime } = await import("@/effect/app-runtime") + await AppRuntime.runPromise(Storage.Service.use((svc) => svc.remove(["session_share", sessionId]))) } async function fullSync(sessionId: string) { diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index da1199a48da..5b2df1e899a 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -7,7 +7,6 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { NonNegativeInt } from "@/util/schema" import { Git } from "@/git" -import { makeRuntime } from "@/effect/run-service" // kilocode_change const log = Log.create({ service: "storage" }) @@ -332,13 +331,4 @@ export const layer = Layer.effect( export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Git.defaultLayer)) -// kilocode_change start - legacy promise helpers for Kilo callsites -const { runPromise } = makeRuntime(Service, defaultLayer) -export const read = (key: string[]) => runPromise((svc) => svc.read(key)) -export const write = (key: string[], content: T) => runPromise((svc) => svc.write(key, content)) -export const remove = (key: string[]) => runPromise((svc) => svc.remove(key)) -export const list = (prefix: string[]) => runPromise((svc) => svc.list(prefix)) -export const update = (key: string[], fn: (draft: T) => void) => runPromise((svc) => svc.update(key, fn)) -// kilocode_change end - export * as Storage from "./storage" diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index f7319c53d12..2f5b73f5d04 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -29,7 +29,6 @@ const allow: Record = { "session/session.ts": "transitional facade tracked by #10655", "session/summary.ts": "transitional facade removed by #10620", "snapshot/index.ts": "transitional facade tracked by #10660", - "storage/storage.ts": "transitional facade tracked by #10659", "sync/index.ts": "sync event runtime boundary", "tool/registry.ts": "transitional facade removed by #10620", } From 3d3d68f37d91c68d4b952191c5e805ac2256d386 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 15:47:06 +0200 Subject: [PATCH 20/59] refactor(cli): remove legacy Snapshot facade --- packages/opencode/src/snapshot/index.ts | 13 --- .../test/kilocode/snapshot-cache.test.ts | 89 ++++++++++------- .../kilocode/snapshot-freeze-repro.test.ts | 96 ++++++++++--------- script/check-opencode-promise-facades.ts | 1 - 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 5826f6947a2..0ddbd6f8b48 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -3,7 +3,6 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" import z from "zod" -import { makeRuntime } from "@/effect/run-service" // kilocode_change import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -851,16 +850,4 @@ export const defaultLayer = layer.pipe( Layer.provide(Config.defaultLayer), ) -// kilocode_change start - legacy promise helpers for Kilo callsites -const { runPromise } = makeRuntime(Service, defaultLayer) -export const track = () => runPromise((svc) => svc.track()) -export const patch = (hash: string) => runPromise((svc) => svc.patch(hash)) -export const restore = (snapshot: string) => runPromise((svc) => svc.restore(snapshot)) -export const revert = (patches: Patch[]) => runPromise((svc) => svc.revert(patches)) -export const diff = (hash: string) => runPromise((svc) => svc.diff(hash)) -export const diffFull = (from: string, to: string) => runPromise((svc) => svc.diffFull(from, to)) -export const cleanup = () => runPromise((svc) => svc.cleanup()) -export const init = () => runPromise((svc) => svc.init()) -// kilocode_change end - export * as Snapshot from "." diff --git a/packages/opencode/test/kilocode/snapshot-cache.test.ts b/packages/opencode/test/kilocode/snapshot-cache.test.ts index c326c594607..6fb9e86dbb1 100644 --- a/packages/opencode/test/kilocode/snapshot-cache.test.ts +++ b/packages/opencode/test/kilocode/snapshot-cache.test.ts @@ -1,12 +1,13 @@ import { test, expect } from "bun:test" import { $ } from "bun" +import { Effect } from "effect" import { Snapshot } from "../../src/snapshot" import { WithInstance } from "../../src/project/with-instance" import { Filesystem } from "../../src/util/filesystem" import * as Log from "@opencode-ai/core/util/log" import { tmpdir } from "../fixture/fixture" -Log.init({ print: false }) +void Log.init({ print: false }) async function bootstrap() { return tmpdir({ @@ -20,26 +21,33 @@ async function bootstrap() { }) } +function run(body: (snapshot: Snapshot.Interface) => Effect.Effect) { + return Effect.runPromise(Snapshot.Service.use(body).pipe(Effect.provide(Snapshot.defaultLayer))) +} + test("diffFull returns cached result for same hash pair", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const before = await Snapshot.track() - expect(before).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/a.txt`, "MODIFIED") - const after = await Snapshot.track() - expect(after).toBeTruthy() - expect(after).not.toBe(before) + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/a.txt`, "MODIFIED")) + const after = yield* snapshot.track() + expect(after).toBeTruthy() + expect(after).not.toBe(before) - const first = await Snapshot.diffFull(before!, after!) - const second = await Snapshot.diffFull(before!, after!) + const first = yield* snapshot.diffFull(before!, after!) + const second = yield* snapshot.diffFull(before!, after!) - // Should be the exact same array reference (cached) - expect(second).toBe(first) - expect(first.length).toBeGreaterThan(0) - }, + // Should be the exact same array reference (cached) + expect(second).toBe(first) + expect(first.length).toBeGreaterThan(0) + }), + ), }) }) @@ -47,13 +55,16 @@ test("diffFull returns empty array when from === to", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const hash = await Snapshot.track() - expect(hash).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const hash = yield* snapshot.track() + expect(hash).toBeTruthy() - const result = await Snapshot.diffFull(hash!, hash!) - expect(result).toEqual([]) - }, + const result = yield* snapshot.diffFull(hash!, hash!) + expect(result).toEqual([]) + }), + ), }) }) @@ -61,24 +72,30 @@ test("diffFull concurrent calls for same pair share one result", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const before = await Snapshot.track() - expect(before).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/a.txt`, "CONCURRENT") - const after = await Snapshot.track() - expect(after).toBeTruthy() + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/a.txt`, "CONCURRENT")) + const after = yield* snapshot.track() + expect(after).toBeTruthy() - // Fire multiple concurrent calls — they should all resolve to the same object - const results = await Promise.all([ - Snapshot.diffFull(before!, after!), - Snapshot.diffFull(before!, after!), - Snapshot.diffFull(before!, after!), - ]) + // Fire multiple concurrent calls, they should all resolve to the same object. + const results = yield* Effect.all( + [ + snapshot.diffFull(before!, after!), + snapshot.diffFull(before!, after!), + snapshot.diffFull(before!, after!), + ], + { concurrency: "unbounded" }, + ) - expect(results[0]).toBe(results[1]) - expect(results[1]).toBe(results[2]) - expect(results[0].length).toBeGreaterThan(0) - }, + expect(results[0]).toBe(results[1]) + expect(results[1]).toBe(results[2]) + expect(results[0].length).toBeGreaterThan(0) + }), + ), }) }) diff --git a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts index 4234efe98b8..68b1ceb72b5 100644 --- a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts +++ b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts @@ -14,6 +14,7 @@ import { test, expect, afterEach, mock } from "bun:test" import { $ } from "bun" +import { Effect, Fiber } from "effect" import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { Session } from "../../src/session/session" @@ -22,7 +23,11 @@ import { Filesystem } from "../../src/util/filesystem" import * as Log from "@opencode-ai/core/util/log" import { disposeAllInstances, tmpdir } from "../fixture/fixture" -Log.init({ print: false }) +void Log.init({ print: false }) + +function run(body: (snapshot: Snapshot.Interface) => Effect.Effect) { + return Effect.runPromise(Snapshot.Service.use(body).pipe(Effect.provide(Snapshot.defaultLayer))) +} afterEach(async () => { mock.restore() @@ -47,55 +52,60 @@ test("pathological diffFull workload finishes quickly and does not block abort", await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const session = await Session.create({}) + fn: () => + run((snapshot) => + Effect.gen(function* () { + const session = yield* Effect.promise(() => Session.create({})) - const before = await Snapshot.track() - expect(before).toBeTruthy() + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/fat.json`, v2) - const after = await Snapshot.track() - expect(after).toBeTruthy() + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/fat.json`, v2)) + const after = yield* snapshot.track() + expect(after).toBeTruthy() - // Kick off a diffFull that exercises the freeze path. - const diffPromise = Snapshot.diffFull(before!, after!) + // Kick off a diffFull that exercises the freeze path. + const diff = yield* snapshot.diffFull(before!, after!).pipe(Effect.forkChild({ startImmediately: true })) - // Concurrently keep a tick counter running. If the event loop blocks we - // will see this count fall behind wall-clock elapsed. - let ticks = 0 - const start = Date.now() - const timer = setInterval(() => { - ticks++ - }, 25) + // Concurrently keep a tick counter running. If the event loop blocks we + // will see this count fall behind wall-clock elapsed. + let ticks = 0 + const start = Date.now() + const timer = setInterval(() => { + ticks++ + }, 25) - // Fire an abort request against the Hono app in the middle of the diff. - const app = Server.Default().app - const abortStart = Date.now() - const res = await app.request(`/session/${session.id}/abort`, { method: "POST" }) - const abortLatency = Date.now() - abortStart - expect(res.status).toBe(200) - // The abort endpoint must respond well under a second even under load. - expect(abortLatency).toBeLessThan(2000) + // Fire an abort request against the Hono app in the middle of the diff. + const app = Server.Default().app + const abortStart = Date.now() + const res = yield* Effect.promise(() => + Promise.resolve(app.request(`/session/${session.id}/abort`, { method: "POST" })), + ) + const abortLatency = Date.now() - abortStart + expect(res.status).toBe(200) + // The abort endpoint must respond well under a second even under load. + expect(abortLatency).toBeLessThan(2000) - const diffs = await diffPromise - clearInterval(timer) - const total = Date.now() - start + const diffs = yield* Fiber.join(diff) + clearInterval(timer) + const total = Date.now() - start - // The freeze workload must finish in bounded time. Five seconds is - // generous even for a slow CI box; without the fix this hangs. - expect(total).toBeLessThan(5000) - // And we must have ticked at least a few times during the work — proves - // the event loop stayed responsive (ESC would actually arrive). - expect(ticks).toBeGreaterThan(0) + // The freeze workload must finish in bounded time. Five seconds is + // generous even for a slow CI box; without the fix this hangs. + expect(total).toBeLessThan(5000) + // And we must have ticked at least a few times during the work, proving + // the event loop stayed responsive (ESC would actually arrive). + expect(ticks).toBeGreaterThan(0) - // With git-based diff the patch is a real unified diff, not empty. - const hit = diffs.find((d) => d.file === "fat.json") - expect(hit).toBeDefined() - expect(hit!.patch).toMatch(/^diff --git /m) - expect(hit!.patch).toContain("-v1_line_0") - expect(hit!.patch).toContain("+v2_line_0") - expect(hit!.additions).toBeGreaterThan(0) - expect(hit!.deletions).toBeGreaterThan(0) - }, + // With git-based diff the patch is a real unified diff, not empty. + const hit = diffs.find((d) => d.file === "fat.json") + expect(hit).toBeDefined() + expect(hit!.patch).toMatch(/^diff --git /m) + expect(hit!.patch).toContain("-v1_line_0") + expect(hit!.patch).toContain("+v2_line_0") + expect(hit!.additions).toBeGreaterThan(0) + expect(hit!.deletions).toBeGreaterThan(0) + }), + ), }) }) diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index f7319c53d12..fe36cc5d33c 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -28,7 +28,6 @@ const allow: Record = { "session/prompt.ts": "transitional facade tracked by #10655", "session/session.ts": "transitional facade tracked by #10655", "session/summary.ts": "transitional facade removed by #10620", - "snapshot/index.ts": "transitional facade tracked by #10660", "storage/storage.ts": "transitional facade tracked by #10659", "sync/index.ts": "sync event runtime boundary", "tool/registry.ts": "transitional facade removed by #10620", From ef2390d7a4ffafc379d1e15db94d3a2cd6dcce9b Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 15:59:11 +0200 Subject: [PATCH 21/59] feat: remove semantic indexing experimental gate --- .changeset/visible-semantic-indexing.md | 6 +++ .../customize/context/codebase-indexing.md | 46 +++---------------- packages/kilo-vscode/src/features.ts | 3 +- .../tests/unit/indexing-utils.test.ts | 40 +++++----------- .../components/settings/ExperimentalTab.tsx | 13 ------ .../kilo-vscode/webview-ui/src/i18n/ar.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/br.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/bs.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/da.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/de.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/en.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/es.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/fr.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/ja.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/ko.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/nl.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/no.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/pl.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/ru.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/th.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/tr.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/uk.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/zh.ts | 3 -- .../kilo-vscode/webview-ui/src/i18n/zht.ts | 3 -- .../webview-ui/src/stories/StoryProviders.tsx | 2 +- .../src/stories/settings.stories.tsx | 9 ---- .../webview-ui/src/types/messages/config.ts | 1 - packages/opencode/src/config/config.ts | 5 +- .../opencode/src/kilocode/config/config.ts | 8 ++++ .../opencode/src/kilocode/indexing-feature.ts | 3 +- packages/opencode/src/kilocode/indexing.ts | 9 ---- .../test/kilocode/config/config.test.ts | 19 ++++++-- .../test/kilocode/indexing-feature.test.ts | 8 +--- .../test/kilocode/indexing-startup.test.ts | 29 ++---------- .../test/kilocode/indexing-worktree.test.ts | 3 -- packages/sdk/js/src/v2/gen/types.gen.ts | 1 - packages/sdk/openapi.json | 3 -- 37 files changed, 59 insertions(+), 206 deletions(-) create mode 100644 .changeset/visible-semantic-indexing.md diff --git a/.changeset/visible-semantic-indexing.md b/.changeset/visible-semantic-indexing.md new file mode 100644 index 00000000000..b7e258038a6 --- /dev/null +++ b/.changeset/visible-semantic-indexing.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": patch +"kilo-code": patch +--- + +Access semantic indexing without an experimental feature toggle while keeping indexing disabled until enabled globally or for a project. diff --git a/packages/kilo-docs/pages/customize/context/codebase-indexing.md b/packages/kilo-docs/pages/customize/context/codebase-indexing.md index f577678670b..819ae399d7b 100644 --- a/packages/kilo-docs/pages/customize/context/codebase-indexing.md +++ b/packages/kilo-docs/pages/customize/context/codebase-indexing.md @@ -7,8 +7,8 @@ description: "Index your codebase for improved AI understanding" Codebase Indexing enables semantic code search across your entire project using AI embeddings. Instead of searching for exact text matches, it understands the _meaning_ of your queries, helping Kilo Code find relevant code even when you don't know specific function names or file locations. -{% callout type="warning" title="Experimental" %} -Codebase Indexing is currently **experimental** in the CLI and the new VS Code extension. You must explicitly opt in before the feature becomes available — see the **Setup** section below. Behavior, configuration, and defaults may change in future releases. +{% callout type="info" title="Opt-in indexing" %} +Codebase Indexing is disabled by default. It starts only after you enable indexing globally or for an individual project. Configuring an embedding provider without enabling one of those toggles does not start indexing. {% /callout %} ## What It Does @@ -34,28 +34,10 @@ This enables natural language queries like "user authentication logic" or "datab {% tabs %} {% tab label="VSCode" %} -### 1. Enable the experimental flag - -Codebase Indexing is gated behind an experimental flag. Until the flag is on, the Indexing UI is hidden and `semantic_search` is unavailable. - -1. Open Kilo Code **Settings** → **Experimental**. -2. Toggle **Semantic Indexing** on. -3. The **Indexing** tab will appear in Settings and the indexing status indicator will appear at the bottom of the prompt input panel. - -Alternatively, set `experimental.semantic_indexing` to `true` in your `kilo.jsonc`: - -```json -{ - "experimental": { - "semantic_indexing": true - } -} -``` - -### 2. Configure indexing +### Configure indexing 1. Open Kilo Code **Settings** → **Indexing**, or click the indexing indicator at the bottom of the prompt input panel. -2. Toggle **Enable Indexing** on. +2. Turn on **Global Enable** to index every workspace, or turn on **Enable for This Project** to index only the current workspace. Both toggles are off until explicitly enabled. 3. Pick an **Embedding Provider** and fill in its required fields. 4. Pick a **Vector Store** (`Qdrant` or `LanceDB`) and configure it. 5. Optionally adjust **Tuning Parameters** (search score, batch size, retries, max results). @@ -106,23 +88,9 @@ The prompt input panel shows a compact indexing status indicator that reflects t {% /tab %} {% tab label="CLI" %} -### 1. Enable the experimental flag +### Configure indexing -Codebase Indexing is gated behind an experimental flag. Until the flag is on, the `/indexing` command is hidden and `semantic_search` is unavailable. - -Set the flag in your `kilo.jsonc`: - -```json -{ - "experimental": { - "semantic_indexing": true - } -} -``` - -Restart the CLI for the change to take effect. The `/indexing` command (and aliases `/index`, `/embedding`) will appear in the command palette once the flag is active. - -### 2. Configure indexing +The `/indexing` command (and aliases `/index`, `/embedding`) is available when the indexing plugin is installed. Indexing remains disabled until it is enabled globally or for the current project. Open a Kilo TUI session and run: @@ -198,7 +166,7 @@ When indexing is enabled, the CLI shows an indexing status badge at the bottom o {% /tab %} {% tab label="VSCode (Legacy)" %} -The legacy extension does not require an experimental flag. +The legacy extension uses its own Codebase Indexing settings panel. ### Open Codebase Indexing Settings diff --git a/packages/kilo-vscode/src/features.ts b/packages/kilo-vscode/src/features.ts index 889c12d8e71..0e0423b9046 100644 --- a/packages/kilo-vscode/src/features.ts +++ b/packages/kilo-vscode/src/features.ts @@ -4,7 +4,6 @@ type PluginSpec = string | [string, Record] type ConfigLike = { plugin?: readonly PluginSpec[] | null - experimental?: { semantic_indexing?: boolean } | null } export type Features = { @@ -13,6 +12,6 @@ export type Features = { export function configFeatures(config?: ConfigLike | null): Features { return { - indexing: hasIndexingPlugin(config?.plugin ?? []) && config?.experimental?.semantic_indexing === true, + indexing: hasIndexingPlugin(config?.plugin ?? []), } } diff --git a/packages/kilo-vscode/tests/unit/indexing-utils.test.ts b/packages/kilo-vscode/tests/unit/indexing-utils.test.ts index cd91df1ecff..0d20e40d656 100644 --- a/packages/kilo-vscode/tests/unit/indexing-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/indexing-utils.test.ts @@ -85,37 +85,19 @@ describe("indexing SSE mapping", () => { }) describe("indexing feature detection", () => { - it("requires experimental.semantic_indexing when indexing plugin is present", () => { - expect(configFeatures({ plugin: ["kilo-indexing"] }).indexing).toBe(false) - expect(configFeatures({ plugin: ["kilo-indexing"], experimental: {} }).indexing).toBe(false) - expect(configFeatures({ plugin: ["kilo-indexing"], experimental: { semantic_indexing: false } }).indexing).toBe( - false, - ) + it("enables indexing settings when the indexing plugin is present", () => { + expect(configFeatures({ plugin: ["kilo-indexing"] }).indexing).toBe(true) }) - it("detects supported indexing plugin specifiers when experimental.semantic_indexing is true", () => { - expect(configFeatures({ plugin: ["kilo-indexing"], experimental: { semantic_indexing: true } }).indexing).toBe(true) - expect( - configFeatures({ plugin: ["kilo-indexing@1.2.3"], experimental: { semantic_indexing: true } }).indexing, - ).toBe(true) - expect( - configFeatures({ plugin: ["@kilocode/kilo-indexing"], experimental: { semantic_indexing: true } }).indexing, - ).toBe(true) - expect( - configFeatures({ plugin: ["@kilocode/kilo-indexing@1.2.3"], experimental: { semantic_indexing: true } }).indexing, - ).toBe(true) - expect( - configFeatures({ - plugin: ["file:///tmp/.opencode/plugin/kilo-indexing.js"], - experimental: { semantic_indexing: true }, - }).indexing, - ).toBe(true) - expect( - configFeatures({ - plugin: ["file:///tmp/node_modules/@kilocode/kilo-indexing/index.js"], - experimental: { semantic_indexing: true }, - }).indexing, - ).toBe(true) + it("detects supported indexing plugin specifiers", () => { + expect(configFeatures({ plugin: ["kilo-indexing"] }).indexing).toBe(true) + expect(configFeatures({ plugin: ["kilo-indexing@1.2.3"] }).indexing).toBe(true) + expect(configFeatures({ plugin: ["@kilocode/kilo-indexing"] }).indexing).toBe(true) + expect(configFeatures({ plugin: ["@kilocode/kilo-indexing@1.2.3"] }).indexing).toBe(true) + expect(configFeatures({ plugin: ["file:///tmp/.opencode/plugin/kilo-indexing.js"] }).indexing).toBe(true) + expect(configFeatures({ plugin: ["file:///tmp/node_modules/@kilocode/kilo-indexing/index.js"] }).indexing).toBe( + true, + ) }) it("ignores unrelated plugin lists", () => { diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx index 320153907f1..77d65f6779f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx @@ -164,19 +164,6 @@ const ExperimentalTab: Component = () => {
- - updateExperimental("semantic_indexing", checked)} - hideLabel - > - {language.t("settings.experimental.semanticIndexing.title")} - - - { const [saved, setSaved] = createSignal>({}) const cfg: Config = { - experimental: { - semantic_indexing: true, - }, indexing: { provider: "openai", model: "text-embedding-3-large", @@ -439,9 +436,6 @@ export const IndexingKiloModelPreset: Story = { name: "IndexingTab - Kilo stale custom model fallback", render: () => { const cfg: Config = { - experimental: { - semantic_indexing: true, - }, indexing: { provider: "kilo", model: "custom/model", @@ -473,9 +467,6 @@ export const IndexingKiloCatalogLoading: Story = { render: () => { const [saved, setSaved] = createSignal>({}) const cfg: Config = { - experimental: { - semantic_indexing: true, - }, indexing: {}, } return ( diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts index 28497f83da9..bd47ed509b8 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts @@ -40,7 +40,6 @@ export interface WatcherConfig { export interface ExperimentalConfig { disable_paste_summary?: boolean batch_tool?: boolean - semantic_indexing?: boolean codebase_search?: boolean speech_to_text_model?: string primary_tools?: string[] diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 48746eb0c9f..742ef72deef 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -74,7 +74,7 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info { function normalizeLoadedConfig(data: unknown, source: string) { if (!isRecord(data)) return data - const copy = { ...data } + const copy = KilocodeConfig.retireIndexingFlag({ ...data }, source) // kilocode_change const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy if (!hadLegacy) return copy delete copy.theme @@ -350,9 +350,6 @@ export const Info = Schema.Struct({ batch_tool: Schema.optional(Schema.Boolean).annotate({ description: "Enable the batch tool" }), codebase_search: Schema.optional(Schema.Boolean).annotate({ description: "Enable AI-powered codebase search" }), // kilocode_change // kilocode_change start - semantic_indexing: Schema.optional(Schema.Boolean).annotate({ - description: "Enable semantic codebase indexing and the semantic_search tool", - }), speech_to_text_model: Schema.optional(Schema.String).annotate({ description: "Speech-to-text transcription model ID to use for voice input", }), diff --git a/packages/opencode/src/kilocode/config/config.ts b/packages/opencode/src/kilocode/config/config.ts index 71844ca3d06..cbaf9afc362 100644 --- a/packages/opencode/src/kilocode/config/config.ts +++ b/packages/opencode/src/kilocode/config/config.ts @@ -115,6 +115,14 @@ export namespace KilocodeConfig { return stripGlobalIndexing(info) } + export function retireIndexingFlag(info: Record, source: string) { + if (!isRecord(info.experimental) || !("semantic_indexing" in info.experimental)) return info + const experimental = { ...info.experimental } + delete experimental.semantic_indexing + log.warn("ignored retired experimental.semantic_indexing config; use indexing.enabled instead", { path: source }) + return { ...info, experimental } + } + function stripGlobalIndexing(info: Config.Info): Config.Info { // Indexing provider/storage settings can be global, but enablement is exposed separately from project enablement. if (info.indexing?.enabled === undefined) return info diff --git a/packages/opencode/src/kilocode/indexing-feature.ts b/packages/opencode/src/kilocode/indexing-feature.ts index a19b1b316b9..3cb2e6042ce 100644 --- a/packages/opencode/src/kilocode/indexing-feature.ts +++ b/packages/opencode/src/kilocode/indexing-feature.ts @@ -9,7 +9,6 @@ type PluginSpec = string | [string, Record] type ConfigLike = { plugin?: readonly PluginSpec[] | null - experimental?: { semantic_indexing?: boolean } | null } type Req = { @@ -21,7 +20,7 @@ type LogLike = { } export function indexingEnabled(config?: ConfigLike | null): boolean { - return hasIndexingPlugin(config?.plugin ?? []) && config?.experimental?.semantic_indexing === true + return hasIndexingPlugin(config?.plugin ?? []) } export function resolveIndexingPlugin(req: Req, log?: LogLike): string { diff --git a/packages/opencode/src/kilocode/indexing.ts b/packages/opencode/src/kilocode/indexing.ts index 03ccf671b9e..ab0c883de69 100644 --- a/packages/opencode/src/kilocode/indexing.ts +++ b/packages/opencode/src/kilocode/indexing.ts @@ -242,15 +242,6 @@ export namespace KiloIndexing { return track(hit, await inert(() => missing())) } - if (cfg.experimental?.semantic_indexing !== true) { - return track( - hit, - await inert(() => - disabledIndexingStatus("Semantic indexing is disabled. Enable it in the Experimental settings."), - ), - ) - } - if (isWorktreePath(dir)) { return track(hit, await inert(() => worktreeDisabled())) } diff --git a/packages/opencode/test/kilocode/config/config.test.ts b/packages/opencode/test/kilocode/config/config.test.ts index cce16404aae..32327bbdc8b 100644 --- a/packages/opencode/test/kilocode/config/config.test.ts +++ b/packages/opencode/test/kilocode/config/config.test.ts @@ -54,9 +54,6 @@ async function writeConfig(dir: string, config: object, name = "kilo.json") { const cfg: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { provider: "ollama", vectorStore: "qdrant", @@ -93,6 +90,22 @@ describe("markdown substitutions", () => { }) describe("kilocode indexing config", () => { + test("ignores retired semantic indexing flags in existing configs", async () => { + await using tmp = await tmpdir({ git: true }) + await writeConfig(tmp.path, { + experimental: { semantic_indexing: true, batch_tool: true }, + }) + + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const config = await load() + expect(config.experimental?.batch_tool).toBe(true) + expect(config.experimental).not.toHaveProperty("semantic_indexing") + }, + }) + }) + test("keeps global indexing enabled in global config", async () => { await using globalTmp = await tmpdir() await using tmp = await tmpdir() diff --git a/packages/opencode/test/kilocode/indexing-feature.test.ts b/packages/opencode/test/kilocode/indexing-feature.test.ts index 627e0d096f4..1cfde17a0b1 100644 --- a/packages/opencode/test/kilocode/indexing-feature.test.ts +++ b/packages/opencode/test/kilocode/indexing-feature.test.ts @@ -9,12 +9,8 @@ import { describe("indexing plugin helpers", () => { test("detects plugin-enabled configs", () => { expect(indexingEnabled({ plugin: ["global-plugin"] })).toBe(false) - expect(indexingEnabled({ plugin: [INDEXING_PLUGIN] })).toBe(false) - expect(indexingEnabled({ plugin: [INDEXING_PLUGIN], experimental: { semantic_indexing: false } })).toBe(false) - expect(indexingEnabled({ plugin: [INDEXING_PLUGIN], experimental: { semantic_indexing: true } })).toBe(true) - expect( - indexingEnabled({ plugin: ["@kilocode/kilo-indexing@1.0.0"], experimental: { semantic_indexing: true } }), - ).toBe(true) + expect(indexingEnabled({ plugin: [INDEXING_PLUGIN] })).toBe(true) + expect(indexingEnabled({ plugin: ["@kilocode/kilo-indexing@1.0.0"] })).toBe(true) }) test("adds indexing plugin when present but missing from config", () => { diff --git a/packages/opencode/test/kilocode/indexing-startup.test.ts b/packages/opencode/test/kilocode/indexing-startup.test.ts index e90222ee0aa..14a5770b2bf 100644 --- a/packages/opencode/test/kilocode/indexing-startup.test.ts +++ b/packages/opencode/test/kilocode/indexing-startup.test.ts @@ -16,9 +16,6 @@ const fetch = global.fetch const cfg: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: true, provider: "ollama", @@ -29,13 +26,9 @@ const cfg: Partial = { }, } -const off: Partial = { +const unset: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: false, - }, indexing: { - enabled: true, provider: "ollama", vectorStore: "qdrant", ollama: { @@ -45,9 +38,6 @@ const off: Partial = { } const inactive: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: false, provider: "ollama", @@ -56,9 +46,6 @@ const inactive: Partial = { } const kilo: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: true, vectorStore: "qdrant", @@ -66,9 +53,6 @@ const kilo: Partial = { } const implicitOpenAi: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: true, vectorStore: "qdrant", @@ -79,9 +63,6 @@ const implicitOpenAi: Partial = { } const staleKilo: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: true, provider: "kilo", @@ -303,8 +284,8 @@ describe("indexing startup degradation", () => { } }) - test("stays disabled when semantic indexing flag is off", async () => { - await using tmp = await tmpdir({ git: true, config: off }) + test("stays disabled when indexing enablement is unset", async () => { + await using tmp = await tmpdir({ git: true, config: unset }) process.env["KILO_CONFIG_DIR"] = tmp.path const init = spyOn(CodeIndexManager.prototype, "initialize") @@ -315,11 +296,11 @@ describe("indexing startup degradation", () => { expect(status).toMatchObject({ state: "Disabled", - message: "Semantic indexing is disabled. Enable it in the Experimental settings.", + message: "Indexing disabled.", }) expect(await KiloIndexing.available()).toBe(false) expect(KiloIndexing.ready()).toBe(false) - expect(await KiloIndexing.search("flag off")).toEqual([]) + expect(await KiloIndexing.search("disabled")).toEqual([]) expect(init).not.toHaveBeenCalled() }, }) diff --git a/packages/opencode/test/kilocode/indexing-worktree.test.ts b/packages/opencode/test/kilocode/indexing-worktree.test.ts index 0a9a303e505..fa42d92e1be 100644 --- a/packages/opencode/test/kilocode/indexing-worktree.test.ts +++ b/packages/opencode/test/kilocode/indexing-worktree.test.ts @@ -7,9 +7,6 @@ import { disposeAllInstances, tmpdir } from "../fixture/fixture" const cfg: Partial = { plugin: ["@kilocode/kilo-indexing"], - experimental: { - semantic_indexing: true, - }, indexing: { enabled: true, provider: "ollama", diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 653ff749dbc..4beaea772f3 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1427,7 +1427,6 @@ export type Config = { disable_paste_summary?: boolean batch_tool?: boolean codebase_search?: boolean - semantic_indexing?: boolean speech_to_text_model?: string openTelemetry?: boolean primary_tools?: Array diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 8f41c9639c3..a752025f001 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -16786,9 +16786,6 @@ "codebase_search": { "type": "boolean" }, - "semantic_indexing": { - "type": "boolean" - }, "speech_to_text_model": { "type": "string" }, From 0107a0163cf73004ee13b0ae5fd46811a273d80a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 16:06:54 +0200 Subject: [PATCH 22/59] feat(cli): guide Agent Manager recall usage --- .changeset/recall-sibling-context.md | 5 +++++ packages/opencode/src/kilocode/tool/agent-manager.txt | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 .changeset/recall-sibling-context.md diff --git a/.changeset/recall-sibling-context.md b/.changeset/recall-sibling-context.md new file mode 100644 index 00000000000..3ecc1fb6f3d --- /dev/null +++ b/.changeset/recall-sibling-context.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Guide Agent Manager orchestration to recall completed session context only when needed. diff --git a/packages/opencode/src/kilocode/tool/agent-manager.txt b/packages/opencode/src/kilocode/tool/agent-manager.txt index d1a85145af5..3c3c59bb4b3 100644 --- a/packages/opencode/src/kilocode/tool/agent-manager.txt +++ b/packages/opencode/src/kilocode/tool/agent-manager.txt @@ -10,4 +10,6 @@ Each task may provide a prompt, a short display name, and a branch name. Keep di By default, multiple tasks are started as independent Agent Manager sessions. Set `versions` to true only when all tasks are alternate versions of the same work that should be compared together. Versioned worktrees are grouped in Agent Manager and branch names may receive version suffixes. +If available, use `kilo_local_recall` only if you need context from a completed Agent Manager session. + Do not use this for ordinary subagent research. Use the `task` tool for internal subagents, and use this only when the user wants visible Agent Manager sessions in the extension. From 96538336971ef8001fe2f9a40e5bb848c0e1051a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 16:08:38 +0200 Subject: [PATCH 23/59] test(cli): wait for disabled indexing startup status --- packages/opencode/test/kilocode/indexing-startup.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/test/kilocode/indexing-startup.test.ts b/packages/opencode/test/kilocode/indexing-startup.test.ts index 14a5770b2bf..63c873a994c 100644 --- a/packages/opencode/test/kilocode/indexing-startup.test.ts +++ b/packages/opencode/test/kilocode/indexing-startup.test.ts @@ -292,7 +292,7 @@ describe("indexing startup degradation", () => { await WithInstance.provide({ directory: tmp.path, fn: async () => { - const status = await KiloIndexing.current() + const status = await wait(() => KiloIndexing.current(), "Disabled") expect(status).toMatchObject({ state: "Disabled", From cc5755d948d1f874ed031904530d6220d247a621 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 14:19:32 +0000 Subject: [PATCH 24/59] feat(vscode): add BYOK Gateway link in provider connect dialog footer --- .../settings/ProviderConnectDialog.tsx | 8 ++++++++ .../webview-ui/src/styles/dialogs.css | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ProviderConnectDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ProviderConnectDialog.tsx index 6dbfd0174ef..a6a8f162f20 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ProviderConnectDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ProviderConnectDialog.tsx @@ -381,6 +381,14 @@ const ProviderConnectDialog: Component = (props) =>
+ + For more usage stats, BYOK via Kilo's Gateway. + diff --git a/packages/kilo-vscode/webview-ui/src/styles/dialogs.css b/packages/kilo-vscode/webview-ui/src/styles/dialogs.css index e6cbfd3efae..ea589714222 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/dialogs.css +++ b/packages/kilo-vscode/webview-ui/src/styles/dialogs.css @@ -9,9 +9,26 @@ .dialog-confirm-actions { display: flex; justify-content: flex-end; + align-items: center; gap: 8px; } +.provider-connect-byok-link { + margin-right: auto; + font-size: var(--kilo-font-size-12); + color: var(--text-weak-base); + text-decoration: none; +} + +.provider-connect-byok-link:hover { + color: var(--text-base); +} + +.provider-connect-byok-link span { + color: var(--vscode-textLink-foreground); + text-decoration: underline; +} + /* Provider Connect Dialog */ .provider-connect-body { font-size: var(--kilo-font-size-13); From 3823862d53ec0eb92d9126e1da46cf3c3bbe4d5b Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 28 May 2026 10:48:05 -0400 Subject: [PATCH 25/59] fix: restore smoke-test dataset filtering --- .github/workflows/smoke-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index b0604eb8ebc..55f29db1a98 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -125,7 +125,7 @@ jobs: ./scripts/run_eval.sh \ -m kilo/anthropic/claude-sonnet-4.6 \ -d terminal-bench-sample \ - -t "log-summary-date-ranges" \ + --include-task-name "log-summary-date-ranges" \ --job-name smoke-test-log-summary \ --timeout-multiplier 2 From 834e544724a82d86ebd4f15f32fdf611146d6d88 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 14:51:35 +0000 Subject: [PATCH 26/59] docs(vscode): add Roo Code migration callout to README --- packages/kilo-vscode/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kilo-vscode/README.md b/packages/kilo-vscode/README.md index 328d3f5d623..691da60553f 100644 --- a/packages/kilo-vscode/README.md +++ b/packages/kilo-vscode/README.md @@ -23,6 +23,8 @@ - [VS Code Marketplace](https://kilo.ai/vscode-marketplace?utm_source=Readme) (download) - [Official Kilo.ai Home page](https://kilo.ai) (learn more) +> 🚀 **Coming from Roo Code?** Switch to Kilo and check out our [migration guide](https://kilo.ai/articles/roo-to-kilo-migration-guide)! + ## Key Features - **Code Generation:** Kilo can generate code using natural language. From 16adbb65077f3ada666680ab20c04460f8e1af28 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 17:07:53 +0200 Subject: [PATCH 27/59] refactor(cli): remove SessionSummary promise facade --- packages/opencode/src/kilo-sessions/kilo-sessions.ts | 5 ++++- packages/opencode/src/session/summary.ts | 6 ------ script/check-opencode-promise-facades.ts | 1 - 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/kilo-sessions/kilo-sessions.ts b/packages/opencode/src/kilo-sessions/kilo-sessions.ts index 6a5ec72c3b6..a6dbe1e929a 100644 --- a/packages/opencode/src/kilo-sessions/kilo-sessions.ts +++ b/packages/opencode/src/kilo-sessions/kilo-sessions.ts @@ -634,7 +634,10 @@ export namespace KiloSessions { log.info("full sync", { sessionId }) const session = await Session.get(SessionID.make(sessionId)) - const diffs = await SessionSummary.diff({ sessionID: SessionID.make(sessionId) }) + const { AppRuntime } = await import("@/effect/app-runtime") + const diffs = await AppRuntime.runPromise( + SessionSummary.Service.use((svc) => svc.diff({ sessionID: SessionID.make(sessionId) })), + ) const messages = await Array.fromAsync(MessageV2.stream(SessionID.make(sessionId))) messages.reverse() const models = await Promise.all( diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index ec2bf512abe..da172b4ac3a 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -7,7 +7,6 @@ import { withStatics } from "@/util/schema" import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID } from "./schema" -import { makeRuntime } from "@/effect/run-service" // kilocode_change function unquoteGitPath(input: string) { if (!input.startsWith('"')) return input @@ -171,9 +170,4 @@ export const DiffInput = Schema.Struct({ }).pipe(withStatics((s) => ({ zod: zod(s) }))) export type DiffInput = Schema.Schema.Type -// kilocode_change start - legacy promise helpers for Kilo callsites -const { runPromise } = makeRuntime(Service, defaultLayer) -export const diff = (input: { sessionID: SessionID; messageID?: MessageID }) => runPromise((svc) => svc.diff(input)) -// kilocode_change end - export * as SessionSummary from "./summary" diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index 8081741aff6..e26a38d1d7a 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -27,7 +27,6 @@ const allow: Record = { "session/compaction.ts": "existing compaction facade outside #10655", "session/prompt.ts": "transitional facade tracked by #10655", "session/session.ts": "transitional facade tracked by #10655", - "session/summary.ts": "transitional facade removed by #10620", "sync/index.ts": "sync event runtime boundary", "tool/registry.ts": "transitional facade removed by #10620", } From 977bf4ee39c76746e592b0995d208306d625dc93 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 17:08:06 +0200 Subject: [PATCH 28/59] refactor(cli): remove Project promise facade --- .../src/kilocode/session-import/service.ts | 3 +- .../opencode/src/kilocode/worktree-family.ts | 3 +- packages/opencode/src/project/project.ts | 7 ---- .../kilocode/session-import-service.test.ts | 33 +++++++++++++++++ .../test/kilocode/worktree-family.test.ts | 35 +++++++++++++++++++ script/check-opencode-promise-facades.ts | 1 - 6 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 packages/opencode/test/kilocode/worktree-family.test.ts diff --git a/packages/opencode/src/kilocode/session-import/service.ts b/packages/opencode/src/kilocode/session-import/service.ts index 6eb12e2e00a..e907263b69e 100644 --- a/packages/opencode/src/kilocode/session-import/service.ts +++ b/packages/opencode/src/kilocode/session-import/service.ts @@ -5,6 +5,7 @@ import { ProjectID } from "../../project/schema" import { WorkspaceID } from "../../control-plane/schema" import { SessionImportType } from "./types" import { Project } from "../../project/project" +import { AppRuntime } from "../../effect/app-runtime" import { eq } from "drizzle-orm" const key = (input: unknown) => [input] as never @@ -18,7 +19,7 @@ export namespace SessionImportService { throw new Error("Legacy project import requires a non-empty worktree") } - const result = await Project.fromDirectory(input.worktree) + const result = await AppRuntime.runPromise(Project.Service.use((svc) => svc.fromDirectory(input.worktree))) return { ok: true, id: result.project.id } } diff --git a/packages/opencode/src/kilocode/worktree-family.ts b/packages/opencode/src/kilocode/worktree-family.ts index ae449361ceb..f7aa58a9b4f 100644 --- a/packages/opencode/src/kilocode/worktree-family.ts +++ b/packages/opencode/src/kilocode/worktree-family.ts @@ -32,7 +32,8 @@ export namespace WorktreeFamily { } } - const dirs = [ctx.worktree, ...(yield* Effect.promise(() => Project.sandboxes(ctx.project.id)))] + const project = yield* Project.Service + const dirs = [ctx.worktree, ...(yield* project.sandboxes(ctx.project.id))] return [...new Set(dirs.map((dir) => Filesystem.resolve(dir)))] }) } diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index badc6247c17..21a8eb19fd7 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -5,7 +5,6 @@ import { eq } from "drizzle-orm" import { ProjectTable } from "./project.sql" import { SessionTable } from "../session/session.sql" import * as Log from "@opencode-ai/core/util/log" -import { makeRuntime } from "@/effect/run-service" // kilocode_change import { Flag } from "@opencode-ai/core/flag/flag" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" @@ -540,10 +539,4 @@ export function setInitialized(id: ProjectID) { ) } -// kilocode_change start - legacy promise helpers for Kilo callsites -const { runPromise } = makeRuntime(Service, defaultLayer) -export const fromDirectory = (directory: string) => runPromise((svc) => svc.fromDirectory(directory)) -export const sandboxes = (id: ProjectID) => runPromise((svc) => svc.sandboxes(id)) -// kilocode_change end - export * as Project from "./project" diff --git a/packages/opencode/test/kilocode/session-import-service.test.ts b/packages/opencode/test/kilocode/session-import-service.test.ts index 6afc1e39b10..4180da9ed08 100644 --- a/packages/opencode/test/kilocode/session-import-service.test.ts +++ b/packages/opencode/test/kilocode/session-import-service.test.ts @@ -1,6 +1,8 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test" import { Database } from "../../src/storage/db" import { SessionImportService } from "../../src/kilocode/session-import/service" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" let spy: ReturnType @@ -76,6 +78,37 @@ function input(force?: boolean) { } } +function project(worktree: string) { + return { + id: "legacy_project", + worktree, + timeCreated: 1, + timeUpdated: 1, + sandboxes: [], + } +} + +describe("SessionImportService.project", () => { + afterEach(async () => { + await resetDatabase() + }) + + test("rejects an empty legacy worktree", async () => { + await expect(SessionImportService.project(project(" "))).rejects.toThrow( + "Legacy project import requires a non-empty worktree", + ) + }) + + test("resolves a valid legacy project through Project.Service", async () => { + await using tmp = await tmpdir({ git: true }) + + const result = await SessionImportService.project(project(tmp.path)) + + expect(result.ok).toBe(true) + expect(result.id).not.toBe("global") + }) +}) + describe("SessionImportService.session", () => { beforeEach(() => { spy = spyOn(Database, "use").mockImplementation((fn: any) => fn(db)) diff --git a/packages/opencode/test/kilocode/worktree-family.test.ts b/packages/opencode/test/kilocode/worktree-family.test.ts new file mode 100644 index 00000000000..abd6d4e369f --- /dev/null +++ b/packages/opencode/test/kilocode/worktree-family.test.ts @@ -0,0 +1,35 @@ +import { describe, expect } from "bun:test" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect, Layer } from "effect" +import { Git } from "../../src/git" +import { InstanceRef } from "../../src/effect/instance-ref" +import { WorktreeFamily } from "../../src/kilocode/worktree-family" +import { Project } from "../../src/project/project" +import { resetDatabase } from "../fixture/db" +import { tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(Layer.mergeAll(Project.defaultLayer, Git.defaultLayer, CrossSpawnSpawner.defaultLayer)) + +describe("WorktreeFamily.list", () => { + it.live("returns recorded sandboxes when git worktree listing fails", () => + Effect.gen(function* () { + yield* Effect.addFinalizer(() => Effect.promise(() => resetDatabase())) + const root = yield* tmpdirScoped() + const sandbox = yield* tmpdirScoped() + const project = yield* Project.Service + const info = (yield* project.fromDirectory(root)).project + yield* project.addSandbox(info.id, sandbox) + + const dirs = yield* WorktreeFamily.list().pipe( + Effect.provideService(InstanceRef, { + directory: root, + worktree: root, + project: { ...info, vcs: "git" }, + }), + ) + + expect(dirs).toEqual([root, sandbox]) + }), + ) +}) diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index 8081741aff6..bfe265b8f03 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -20,7 +20,6 @@ const allow: Record = { "cli/cmd/tui/config/tui.ts": "separately tracked TUI config facade", "installation/index.ts": "existing installation facade outside #10655", "permission/index.ts": "transitional facade removed by #10620", - "project/project.ts": "transitional facade removed by #10620", "project/vcs.ts": "transitional facade removed by #10620", "provider/provider.ts": "transitional facade tracked by #10655", "question/index.ts": "transitional facade deferred for upstream reconciliation in #10655", From 2f49c6adbc129def631dff845715e722746c6573 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 17:09:17 +0200 Subject: [PATCH 29/59] refactor(cli): remove legacy ToolRegistry facade --- packages/opencode/src/tool/registry.ts | 9 +-- packages/opencode/test/tool/registry.test.ts | 62 ++++++++++---------- script/check-opencode-promise-facades.ts | 1 - 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index f3b8cfe85b5..ebccd3a6ce7 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -24,10 +24,7 @@ import { Plugin } from "../plugin" import { Provider } from "@/provider/provider" import { ProviderID, type ModelID } from "../provider/schema" import { WebSearchTool } from "./websearch" -// kilocode_change start -import { KiloToolRegistry } from "../kilocode/tool/registry" -import { makeRuntime } from "@/effect/run-service" -// kilocode_change end +import { KiloToolRegistry } from "../kilocode/tool/registry" // kilocode_change import { Flag } from "@opencode-ai/core/flag/flag" import * as Log from "@opencode-ai/core/util/log" import { LspTool } from "./lsp" @@ -383,8 +380,4 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(SessionStatus.defaultLayer), // kilocode_change ), ) -// kilocode_change start -const { runPromise } = makeRuntime(Service, defaultLayer) -export const ids = () => runPromise((svc) => svc.ids()) -// kilocode_change end export * as ToolRegistry from "./registry" diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 599382fbe67..03bcaeee199 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test" +import { afterEach, describe, expect } from "bun:test" import path from "path" import fs from "fs/promises" import { Effect, Layer } from "effect" @@ -6,7 +6,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "@/tool/registry" import { Command } from "@/command" // kilocode_change import { Git } from "@/git" // kilocode_change -import { disposeAllInstances, provideTmpdirInstance, TestInstance, tmpdir } from "../fixture/fixture" // kilocode_change +import { disposeAllInstances, provideTmpdirInstance, TestInstance } from "../fixture/fixture" // kilocode_change import { testEffect } from "../lib/effect" import { TestConfig } from "../fixture/config" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -25,7 +25,6 @@ import { Format } from "@/format" import { Ripgrep } from "@/file/ripgrep" import * as Truncate from "@/tool/truncate" import { InstanceState } from "@/effect/instance-state" -import { WithInstance } from "@/project/with-instance" import { SessionStatus } from "@/session/status" // kilocode_change const node = CrossSpawnSpawner.defaultLayer @@ -89,34 +88,37 @@ describe("tool.registry", () => { // kilocode_change end // kilocode_change start - test("suggest is registered for cli and vscode only", async () => { - const original = process.env["KILO_CLIENT"] - const originalQuestion = process.env["KILO_ENABLE_QUESTION_TOOL"] - const originalConfig = process.env["KILO_CONFIG_DIR"] - try { - for (const client of ["cli", "vscode", "desktop", "app"]) { - process.env["KILO_CLIENT"] = client - process.env["KILO_ENABLE_QUESTION_TOOL"] = client === "vscode" ? "true" : "false" - await using tmp = await tmpdir({ git: true }) - process.env["KILO_CONFIG_DIR"] = tmp.path - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const ids = await ToolRegistry.ids() - if (client === "cli" || client === "vscode") expect(ids).toContain("suggest") - else expect(ids).not.toContain("suggest") - }, - }) + it.live("suggest is registered for cli and vscode only", () => + Effect.gen(function* () { + const original = process.env["KILO_CLIENT"] + const originalQuestion = process.env["KILO_ENABLE_QUESTION_TOOL"] + const originalConfig = process.env["KILO_CONFIG_DIR"] + try { + for (const client of ["cli", "vscode", "desktop", "app"]) { + process.env["KILO_CLIENT"] = client + process.env["KILO_ENABLE_QUESTION_TOOL"] = client === "vscode" ? "true" : "false" + yield* provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + process.env["KILO_CONFIG_DIR"] = dir + const registry = yield* ToolRegistry.Service + const ids = yield* registry.ids() + if (client === "cli" || client === "vscode") expect(ids).toContain("suggest") + else expect(ids).not.toContain("suggest") + }), + { git: true }, + ) + } + } finally { + if (original === undefined) delete process.env["KILO_CLIENT"] + else process.env["KILO_CLIENT"] = original + if (originalQuestion === undefined) delete process.env["KILO_ENABLE_QUESTION_TOOL"] + else process.env["KILO_ENABLE_QUESTION_TOOL"] = originalQuestion + if (originalConfig === undefined) delete process.env["KILO_CONFIG_DIR"] + else process.env["KILO_CONFIG_DIR"] = originalConfig } - } finally { - if (original === undefined) delete process.env["KILO_CLIENT"] - else process.env["KILO_CLIENT"] = original - if (originalQuestion === undefined) delete process.env["KILO_ENABLE_QUESTION_TOOL"] - else process.env["KILO_ENABLE_QUESTION_TOOL"] = originalQuestion - if (originalConfig === undefined) delete process.env["KILO_CONFIG_DIR"] - else process.env["KILO_CONFIG_DIR"] = originalConfig - } - }) + }), + ) // kilocode_change end it.instance("loads tools from .opencode/tool (singular)", () => diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index fe36cc5d33c..940e8b00df5 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -30,7 +30,6 @@ const allow: Record = { "session/summary.ts": "transitional facade removed by #10620", "storage/storage.ts": "transitional facade tracked by #10659", "sync/index.ts": "sync event runtime boundary", - "tool/registry.ts": "transitional facade removed by #10620", } const owned = (file: string) => file.startsWith("kilocode/") || file.startsWith("kilo-sessions/") From 13783d6f60c606ea35cae0a0727b76e1087b4d0f Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Thu, 28 May 2026 15:09:45 +0000 Subject: [PATCH 30/59] release: v7.3.14 --- .changeset/autocomplete-not-set-default.md | 5 - .changeset/center-history-delete.md | 5 - .changeset/dedupe-opentui-solid.md | 5 - .changeset/history-context-menu-size.md | 5 - .changeset/isolate-indexing-worker.md | 5 - .changeset/jetbrains-active-badges.md | 5 - .changeset/jetbrains-auto-approve.md | 5 - .changeset/jetbrains-history-refresh.md | 5 - .../jetbrains-question-scroll-overlay.md | 5 - .changeset/jetbrains-resizing-prompt.md | 5 - .changeset/jetbrains-session-hover.md | 5 - .changeset/jetbrains-session-links.md | 5 - .changeset/jetbrains-session-scroll.md | 5 - .changeset/kilo-embedding-model-presets.md | 8 - .changeset/mercury-next-edit.md | 5 - .changeset/nextedit-via-kilo-gateway.md | 6 - .changeset/quiet-agents-launch.md | 5 - .changeset/quiet-indexing-startup.md | 5 - .changeset/rename-session-titles.md | 5 - .changeset/soft-diff-highlights.md | 5 - .changeset/steady-indexing-provider.md | 5 - .changeset/steady-stream-scroll.md | 5 - .changeset/visible-turn-outcomes.md | 5 - .changeset/witty-cabin.md | 5 - bun.lock | 30 +-- package.json | 2 +- packages/core/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +- packages/kilo-docs/package.json | 2 +- packages/kilo-gateway/package.json | 2 +- packages/kilo-i18n/package.json | 2 +- packages/kilo-indexing/package.json | 2 +- packages/kilo-jetbrains/package.json | 2 +- packages/kilo-telemetry/package.json | 2 +- packages/kilo-ui/package.json | 2 +- packages/kilo-vscode/CHANGELOG.md | 35 +++ packages/kilo-vscode/package.json | 2 +- packages/kilo-vscode/tests/package.json | 2 +- packages/opencode/CHANGELOG.md | 21 ++ packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/script/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 +- packages/sdk/js/src/v2/gen/types.gen.ts | 202 +++++++++--------- packages/storybook/package.json | 2 +- packages/ui/package.json | 2 +- script/upstream/package.json | 2 +- 48 files changed, 197 insertions(+), 265 deletions(-) delete mode 100644 .changeset/autocomplete-not-set-default.md delete mode 100644 .changeset/center-history-delete.md delete mode 100644 .changeset/dedupe-opentui-solid.md delete mode 100644 .changeset/history-context-menu-size.md delete mode 100644 .changeset/isolate-indexing-worker.md delete mode 100644 .changeset/jetbrains-active-badges.md delete mode 100644 .changeset/jetbrains-auto-approve.md delete mode 100644 .changeset/jetbrains-history-refresh.md delete mode 100644 .changeset/jetbrains-question-scroll-overlay.md delete mode 100644 .changeset/jetbrains-resizing-prompt.md delete mode 100644 .changeset/jetbrains-session-hover.md delete mode 100644 .changeset/jetbrains-session-links.md delete mode 100644 .changeset/jetbrains-session-scroll.md delete mode 100644 .changeset/kilo-embedding-model-presets.md delete mode 100644 .changeset/mercury-next-edit.md delete mode 100644 .changeset/nextedit-via-kilo-gateway.md delete mode 100644 .changeset/quiet-agents-launch.md delete mode 100644 .changeset/quiet-indexing-startup.md delete mode 100644 .changeset/rename-session-titles.md delete mode 100644 .changeset/soft-diff-highlights.md delete mode 100644 .changeset/steady-indexing-provider.md delete mode 100644 .changeset/steady-stream-scroll.md delete mode 100644 .changeset/visible-turn-outcomes.md delete mode 100644 .changeset/witty-cabin.md diff --git a/.changeset/autocomplete-not-set-default.md b/.changeset/autocomplete-not-set-default.md deleted file mode 100644 index 68888282766..00000000000 --- a/.changeset/autocomplete-not-set-default.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Add a "Not set (use server default)" option to the autocomplete model picker so users can follow the recommended default automatically. Users who previously had the default model pinned only because it was the only thing visible in the dropdown are migrated to "Not set" once. diff --git a/.changeset/center-history-delete.md b/.changeset/center-history-delete.md deleted file mode 100644 index db671cea3b1..00000000000 --- a/.changeset/center-history-delete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Center local session history delete buttons within their rows. diff --git a/.changeset/dedupe-opentui-solid.md b/.changeset/dedupe-opentui-solid.md deleted file mode 100644 index fdd52f6cacf..00000000000 --- a/.changeset/dedupe-opentui-solid.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Fix packaged CLI startup crashes caused by duplicate OpenTUI/Solid renderer instances. diff --git a/.changeset/history-context-menu-size.md b/.changeset/history-context-menu-size.md deleted file mode 100644 index 80a0fb90dd8..00000000000 --- a/.changeset/history-context-menu-size.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Improve the size and readability of the local History session context menu. diff --git a/.changeset/isolate-indexing-worker.md b/.changeset/isolate-indexing-worker.md deleted file mode 100644 index 34f2c9fb109..00000000000 --- a/.changeset/isolate-indexing-worker.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Keep the extension responsive while semantic indexing processes large workspaces. diff --git a/.changeset/jetbrains-active-badges.md b/.changeset/jetbrains-active-badges.md deleted file mode 100644 index 89e19d5f707..00000000000 --- a/.changeset/jetbrains-active-badges.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Show question, permission, plan, and login-required badges for active JetBrains sessions in recent and history lists. diff --git a/.changeset/jetbrains-auto-approve.md b/.changeset/jetbrains-auto-approve.md deleted file mode 100644 index fbedb802c55..00000000000 --- a/.changeset/jetbrains-auto-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": minor ---- - -Support toggling auto-approve for permission prompts from the JetBrains chat input. diff --git a/.changeset/jetbrains-history-refresh.md b/.changeset/jetbrains-history-refresh.md deleted file mode 100644 index bf87fff84ec..00000000000 --- a/.changeset/jetbrains-history-refresh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Refresh JetBrains history and recent-session rows when active session titles change, and keep pending inactive sessions alive when switching views. diff --git a/.changeset/jetbrains-question-scroll-overlay.md b/.changeset/jetbrains-question-scroll-overlay.md deleted file mode 100644 index ae3d2a9633f..00000000000 --- a/.changeset/jetbrains-question-scroll-overlay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Show a question indicator on the JetBrains session scroll overlay when user input is needed. diff --git a/.changeset/jetbrains-resizing-prompt.md b/.changeset/jetbrains-resizing-prompt.md deleted file mode 100644 index 858eccf6a96..00000000000 --- a/.changeset/jetbrains-resizing-prompt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Resize the JetBrains prompt editor as prompt lines are added or removed. diff --git a/.changeset/jetbrains-session-hover.md b/.changeset/jetbrains-session-hover.md deleted file mode 100644 index 181ad76ac4a..00000000000 --- a/.changeset/jetbrains-session-hover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Refine JetBrains session transcript styling with subtler tool rows, prompt-styled user messages, and underlined read file links that open files in the IDE. diff --git a/.changeset/jetbrains-session-links.md b/.changeset/jetbrains-session-links.md deleted file mode 100644 index 63de324a815..00000000000 --- a/.changeset/jetbrains-session-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Support opening links in JetBrains session markdown transcripts. diff --git a/.changeset/jetbrains-session-scroll.md b/.changeset/jetbrains-session-scroll.md deleted file mode 100644 index d91e0ec1f90..00000000000 --- a/.changeset/jetbrains-session-scroll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Keep JetBrains chat scrolled to the latest prompt and question updates when following the bottom. diff --git a/.changeset/kilo-embedding-model-presets.md b/.changeset/kilo-embedding-model-presets.md deleted file mode 100644 index ae3e3f912e4..00000000000 --- a/.changeset/kilo-embedding-model-presets.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"kilo-code": patch -"@kilocode/cli": patch -"@kilocode/kilo-indexing": patch -"@kilocode/sdk": patch ---- - -Use supported hosted model presets for Kilo indexing and clear obsolete model and dimension overrides. diff --git a/.changeset/mercury-next-edit.md b/.changeset/mercury-next-edit.md deleted file mode 100644 index 976b7990df4..00000000000 --- a/.changeset/mercury-next-edit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Add Mercury Next Edit as an opt-in autocomplete mode. Predicts multi-line edits beyond the cursor (including off-cursor and pure-insertion edits) and surfaces them with a Tab-to-jump / Tab-to-apply affordance. Select "Mercury Next Edit" under the autocomplete model setting to enable it (requires an Inception API key). Thanks [@tfiras](https://github.com/tfiras)! diff --git a/.changeset/nextedit-via-kilo-gateway.md b/.changeset/nextedit-via-kilo-gateway.md deleted file mode 100644 index 7e6f8ad503c..00000000000 --- a/.changeset/nextedit-via-kilo-gateway.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@kilocode/kilo-gateway": minor -"kilo-code": minor ---- - -Support Mercury Next Edit through the Kilo Gateway. The new "Mercury Next Edit via Kilo Gateway" autocomplete model routes Next Edit predictions through your Kilo account (no separate Inception API key required). diff --git a/.changeset/quiet-agents-launch.md b/.changeset/quiet-agents-launch.md deleted file mode 100644 index 0fed5274876..00000000000 --- a/.changeset/quiet-agents-launch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Make the Agent Manager tool available by default in VS Code. diff --git a/.changeset/quiet-indexing-startup.md b/.changeset/quiet-indexing-startup.md deleted file mode 100644 index 5a0260139b7..00000000000 --- a/.changeset/quiet-indexing-startup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Keep the extension usable on fresh startup when semantic indexing is enabled globally. diff --git a/.changeset/rename-session-titles.md b/.changeset/rename-session-titles.md deleted file mode 100644 index 03d77bce3fb..00000000000 --- a/.changeset/rename-session-titles.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Allow renaming sessions with a consistent inline editor in the active chat header and History, using safe bounded titles. diff --git a/.changeset/soft-diff-highlights.md b/.changeset/soft-diff-highlights.md deleted file mode 100644 index 1687eb5e886..00000000000 --- a/.changeset/soft-diff-highlights.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Restore readable diff highlighting and collapsed unchanged sections in VS Code themes. diff --git a/.changeset/steady-indexing-provider.md b/.changeset/steady-indexing-provider.md deleted file mode 100644 index 9afaebdf819..00000000000 --- a/.changeset/steady-indexing-provider.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Prevent saved global indexing provider changes from temporarily reverting in active workspaces. diff --git a/.changeset/steady-stream-scroll.md b/.changeset/steady-stream-scroll.md deleted file mode 100644 index b2ee6950c11..00000000000 --- a/.changeset/steady-stream-scroll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Keep the VS Code chat position stable when reading earlier output during a streaming response. diff --git a/.changeset/visible-turn-outcomes.md b/.changeset/visible-turn-outcomes.md deleted file mode 100644 index 9697024077b..00000000000 --- a/.changeset/visible-turn-outcomes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Warn when a chat turn stops unexpectedly or ends while tracked to-dos remain unfinished. diff --git a/.changeset/witty-cabin.md b/.changeset/witty-cabin.md deleted file mode 100644 index 47dbc193d64..00000000000 --- a/.changeset/witty-cabin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Show running badges on active sessions in JetBrains recent and history lists. diff --git a/bun.lock b/bun.lock index 80be92af405..9efdf397bd2 100644 --- a/bun.lock +++ b/bun.lock @@ -33,7 +33,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.3.12", + "version": "7.3.14", "bin": { "opencode": "./bin/opencode", }, @@ -68,7 +68,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -98,7 +98,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", @@ -134,7 +134,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.3.12", + "version": "7.3.14", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -144,7 +144,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -176,11 +176,11 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.3.12", + "version": "7.3.14", }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -194,7 +194,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -231,7 +231,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -294,7 +294,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.3.12", + "version": "7.3.14", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -452,7 +452,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -477,7 +477,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "semver": "^7.6.3", }, @@ -488,7 +488,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "cross-spawn": "catalog:", }, @@ -503,7 +503,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.3.12", + "version": "7.3.14", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -526,7 +526,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.3.12", + "version": "7.3.14", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", diff --git a/package.json b/package.json index 6f91d0cbca9..c1f7070041a 100644 --- a/package.json +++ b/package.json @@ -149,6 +149,6 @@ "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch" }, - "version": "7.3.12", + "version": "7.3.14", "peerDependencies": {} } diff --git a/packages/core/package.json b/packages/core/package.json index 3936f230288..f778b34bb04 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.3.12", + "version": "7.3.14", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index ccdb1a18d60..c09d9006bb7 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.3.12" +version = "7.3.14" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.12/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.14/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.12/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.14/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.12/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.14/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.12/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.14/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.12/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.14/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 6a34ff6095c..6bac0781fd0 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.3.12", + "version": "7.3.14", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 86b8405d692..2ae78303918 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index d4a38188187..60f171db8f8 100644 --- a/packages/kilo-i18n/package.json +++ b/packages/kilo-i18n/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-i18n", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-indexing/package.json b/packages/kilo-indexing/package.json index 479f7b7dfe2..be0f68487af 100644 --- a/packages/kilo-indexing/package.json +++ b/packages/kilo-indexing/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-indexing", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "description": "Standalone indexing engine and host helpers for Kilo Code", diff --git a/packages/kilo-jetbrains/package.json b/packages/kilo-jetbrains/package.json index 3023fc9876a..00391a208fb 100644 --- a/packages/kilo-jetbrains/package.json +++ b/packages/kilo-jetbrains/package.json @@ -8,7 +8,7 @@ "test": "./gradlew test", "test:ci": "bun script/test-ci.ts" }, - "version": "7.3.12", + "version": "7.3.14", "dependencies": {}, "devDependencies": {}, "peerDependencies": {} diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index 8cb18274952..6a135baff37 100644 --- a/packages/kilo-telemetry/package.json +++ b/packages/kilo-telemetry/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-telemetry", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index 454e6768ad7..90c08b0589d 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "exports": { diff --git a/packages/kilo-vscode/CHANGELOG.md b/packages/kilo-vscode/CHANGELOG.md index 5ff97fa7d68..c651b09a990 100644 --- a/packages/kilo-vscode/CHANGELOG.md +++ b/packages/kilo-vscode/CHANGELOG.md @@ -1,5 +1,40 @@ # kilo-code +## 7.3.14 + +### Minor Changes + +- [#10650](https://github.com/Kilo-Org/kilocode/pull/10650) [`f18a452`](https://github.com/Kilo-Org/kilocode/commit/f18a452082c998aa9f699204cda1fbf49fb3486f) - Add a "Not set (use server default)" option to the autocomplete model picker so users can follow the recommended default automatically. Users who previously had the default model pinned only because it was the only thing visible in the dropdown are migrated to "Not set" once. + +- [#10621](https://github.com/Kilo-Org/kilocode/pull/10621) [`29c3798`](https://github.com/Kilo-Org/kilocode/commit/29c3798faae2b82cba8ce531304630fee10f23b3) - Add Mercury Next Edit as an opt-in autocomplete mode. Predicts multi-line edits beyond the cursor (including off-cursor and pure-insertion edits) and surfaces them with a Tab-to-jump / Tab-to-apply affordance. Select "Mercury Next Edit" under the autocomplete model setting to enable it (requires an Inception API key). Thanks [@tfiras](https://github.com/tfiras)! + +- [#10644](https://github.com/Kilo-Org/kilocode/pull/10644) [`db38888`](https://github.com/Kilo-Org/kilocode/commit/db388889e867021c6bae42cbd03df6b67941b208) - Support Mercury Next Edit through the Kilo Gateway. The new "Mercury Next Edit via Kilo Gateway" autocomplete model routes Next Edit predictions through your Kilo account (no separate Inception API key required). + +- [#10608](https://github.com/Kilo-Org/kilocode/pull/10608) [`3ffacc8`](https://github.com/Kilo-Org/kilocode/commit/3ffacc847b79c8cdd44c17c4d26476998f24c098) - Make the Agent Manager tool available by default in VS Code. + +- [#10641](https://github.com/Kilo-Org/kilocode/pull/10641) [`4869d87`](https://github.com/Kilo-Org/kilocode/commit/4869d8722b423815a29832c812cf8a766c965a94) - Allow renaming sessions with a consistent inline editor in the active chat header and History, using safe bounded titles. + +### Patch Changes + +- [#10643](https://github.com/Kilo-Org/kilocode/pull/10643) [`6d77d6b`](https://github.com/Kilo-Org/kilocode/commit/6d77d6bbf293ebca7f76d848264d48073d29a44f) - Center local session history delete buttons within their rows. + +- [#10646](https://github.com/Kilo-Org/kilocode/pull/10646) [`d5a8989`](https://github.com/Kilo-Org/kilocode/commit/d5a8989b81d2cb0dd3ea4f62f3ee4570a7725891) - Improve the size and readability of the local History session context menu. + +- [#10619](https://github.com/Kilo-Org/kilocode/pull/10619) [`117691e`](https://github.com/Kilo-Org/kilocode/commit/117691e4d6fe48f91223bb7d7e24103c67cde73f) - Use supported hosted model presets for Kilo indexing and clear obsolete model and dimension overrides. + +- [#10642](https://github.com/Kilo-Org/kilocode/pull/10642) [`5a8d6ae`](https://github.com/Kilo-Org/kilocode/commit/5a8d6ae5dc8ed5d22117da96e7ee713b1a6e567b) - Restore readable diff highlighting and collapsed unchanged sections in VS Code themes. + +- [#10656](https://github.com/Kilo-Org/kilocode/pull/10656) [`d25d5ff`](https://github.com/Kilo-Org/kilocode/commit/d25d5ff473cbac8e230042d746b440465a259f11) - Keep the VS Code chat position stable when reading earlier output during a streaming response. + +- [#10652](https://github.com/Kilo-Org/kilocode/pull/10652) [`3af4c7e`](https://github.com/Kilo-Org/kilocode/commit/3af4c7ebabc2b95ece1c60cabb07930f9d4f42e6) - Warn when a chat turn stops unexpectedly or ends while tracked to-dos remain unfinished. + +- Updated dependencies [[`117691e`](https://github.com/Kilo-Org/kilocode/commit/117691e4d6fe48f91223bb7d7e24103c67cde73f), [`db38888`](https://github.com/Kilo-Org/kilocode/commit/db388889e867021c6bae42cbd03df6b67941b208)]: + - @kilocode/kilo-indexing@7.3.13 + - @kilocode/sdk@7.3.13 + - @kilocode/kilo-gateway@7.4.0 + - @kilocode/kilo-ui@7.3.13 + - @opencode-ai/ui@7.3.13 + ## 7.3.11 ### Minor Changes diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index bbcff2d2d19..2ddf7c0d5ca 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -2,7 +2,7 @@ "name": "kilo-code", "displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete", "description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.", - "version": "7.3.12", + "version": "7.3.14", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/packages/kilo-vscode/tests/package.json b/packages/kilo-vscode/tests/package.json index 6974b0aa894..5eb8e64e604 100644 --- a/packages/kilo-vscode/tests/package.json +++ b/packages/kilo-vscode/tests/package.json @@ -1,6 +1,6 @@ { "type": "module", - "version": "7.3.12", + "version": "7.3.14", "dependencies": {}, "devDependencies": {}, "peerDependencies": {} diff --git a/packages/opencode/CHANGELOG.md b/packages/opencode/CHANGELOG.md index 27ffaa4199b..79eaf64cbae 100644 --- a/packages/opencode/CHANGELOG.md +++ b/packages/opencode/CHANGELOG.md @@ -1,5 +1,26 @@ # @kilocode/cli +## 7.3.14 + +### Patch Changes + +- [#8761](https://github.com/Kilo-Org/kilocode/pull/8761) [`74e01b1`](https://github.com/Kilo-Org/kilocode/commit/74e01b1d485ee77943d2d46f05dce1c7cd2daf82) Thanks [@brendandebeasi](https://github.com/brendandebeasi)! - Fix packaged CLI startup crashes caused by duplicate OpenTUI/Solid renderer instances. + +- [#10648](https://github.com/Kilo-Org/kilocode/pull/10648) [`9fbd547`](https://github.com/Kilo-Org/kilocode/commit/9fbd5479b09739b21ca636612a85501f0d0f548f) - Keep the extension responsive while semantic indexing processes large workspaces. + +- [#10619](https://github.com/Kilo-Org/kilocode/pull/10619) [`117691e`](https://github.com/Kilo-Org/kilocode/commit/117691e4d6fe48f91223bb7d7e24103c67cde73f) - Use supported hosted model presets for Kilo indexing and clear obsolete model and dimension overrides. + +- [#10657](https://github.com/Kilo-Org/kilocode/pull/10657) [`d883ad9`](https://github.com/Kilo-Org/kilocode/commit/d883ad96ab7bd1b31a83d227065ad231a225a4c4) - Keep the extension usable on fresh startup when semantic indexing is enabled globally. + +- [#10618](https://github.com/Kilo-Org/kilocode/pull/10618) [`dcfadac`](https://github.com/Kilo-Org/kilocode/commit/dcfadac83ed45a109a402a2f71f4d214347804f1) - Prevent saved global indexing provider changes from temporarily reverting in active workspaces. + +- Updated dependencies [[`117691e`](https://github.com/Kilo-Org/kilocode/commit/117691e4d6fe48f91223bb7d7e24103c67cde73f), [`db38888`](https://github.com/Kilo-Org/kilocode/commit/db388889e867021c6bae42cbd03df6b67941b208)]: + - @kilocode/kilo-indexing@7.3.13 + - @kilocode/sdk@7.3.13 + - @kilocode/kilo-gateway@7.4.0 + - @kilocode/plugin@7.3.13 + - @kilocode/kilo-telemetry@7.3.13 + ## 7.3.11 ### Patch Changes diff --git a/packages/opencode/package.json b/packages/opencode/package.json index c92bcd2ecbe..84521c8a852 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.3.12", + "version": "7.3.14", "name": "@kilocode/cli", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d0be257aa38..f361865871d 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/plugin", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/script/package.json b/packages/script/package.json index f9dbb610b3c..a637ad79b29 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -12,6 +12,6 @@ "exports": { ".": "./src/index.ts" }, - "version": "7.3.12", + "version": "7.3.14", "peerDependencies": {} } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 015a25d4978..01b8e47d162 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sdk", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index ce6ac869f9e..31297a92fdc 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -5802,7 +5802,7 @@ export class Kilo extends HeyApiClient { /** * Next Edit completion * - * Proxy a Mercury-style Next Edit request. The user supplies the already-templated sentinel-tagged prompt in `content`; the gateway forwards to the upstream edit endpoint (currently Inception's /v1/edit/completions) and returns the unwrapped reply. + * Proxy a Mercury-style Next Edit request. The client supplies structured editor context; the gateway assembles the sentinel-tagged prompt and forwards to the upstream edit endpoint. */ public edit( parameters?: { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 653ff749dbc..fdebfac185c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -8,18 +8,16 @@ export type Event = | EventServerConnected | EventGlobalDisposed | EventGlobalConfigUpdated - | EventTuiPromptAppend - | EventTuiCommandExecute - | EventTuiToastShow1 - | EventTuiSessionSelect - | EventKilocodeAgentManagerStart - | EventIndexingStatus | EventServerInstanceDisposed | EventLspClientDiagnostics | EventLspUpdated | EventQuestionAsked | EventQuestionReplied | EventQuestionRejected + | EventTuiPromptAppend + | EventTuiCommandExecute + | EventTuiToastShow1 + | EventTuiSessionSelect | EventMcpToolsChanged | EventMcpBrowserOpenFailed | EventSessionNetworkAsked @@ -48,6 +46,7 @@ export type Event = | EventSessionCompacted | EventCommandExecuted | EventProjectUpdated + | EventKilocodeAgentManagerStart | EventVcsBranchUpdated | EventKiloSessionsRemoteStatusChanged | EventWorkspaceReady @@ -92,6 +91,7 @@ export type Event = | EventSessionNextCompactionStarted | EventSessionNextCompactionDelta | EventSessionNextCompactionEnded + | EventIndexingStatus export type OAuth = { type: "oauth" @@ -118,71 +118,6 @@ export type WellKnownAuth = { export type Auth = OAuth | ApiAuth | WellKnownAuth -export type EventTuiPromptAppend = { - id: string - type: "tui.prompt.append" - properties: { - text: string - } -} - -export type EventTuiCommandExecute = { - id: string - type: "tui.command.execute" - properties: { - command: - | "session.list" - | "session.new" - | "session.share" - | "session.interrupt" - | "session.compact" - | "session.page.up" - | "session.page.down" - | "session.line.up" - | "session.line.down" - | "session.half.page.up" - | "session.half.page.down" - | "session.first" - | "session.last" - | "prompt.clear" - | "prompt.submit" - | "agent.cycle" - | string - } -} - -export type EventTuiToastShow = { - id: string - type: "tui.toast.show" - properties: { - title?: string - message: string - variant: "info" | "success" | "warning" | "error" - duration?: number - } -} - -export type EventTuiSessionSelect = { - id: string - type: "tui.session.select" - properties: { - /** - * Session ID to navigate to - */ - sessionID: string - } -} - -export type IndexingStatusState = "Disabled" | "In Progress" | "Complete" | "Error" | "Standby" - -export type IndexingStatus = { - state: IndexingStatusState - message: string - processedFiles: number - totalFiles: number - percent: number -} - export type QuestionOption = { /** * Display text (1-5 words, concise) @@ -245,6 +180,61 @@ export type QuestionRejected = { requestID: string } +export type EventTuiPromptAppend = { + id: string + type: "tui.prompt.append" + properties: { + text: string + } +} + +export type EventTuiCommandExecute = { + id: string + type: "tui.command.execute" + properties: { + command: + | "session.list" + | "session.new" + | "session.share" + | "session.interrupt" + | "session.compact" + | "session.page.up" + | "session.page.down" + | "session.line.up" + | "session.line.down" + | "session.half.page.up" + | "session.half.page.down" + | "session.first" + | "session.last" + | "prompt.clear" + | "prompt.submit" + | "agent.cycle" + | string + } +} + +export type EventTuiToastShow = { + id: string + type: "tui.toast.show" + properties: { + title?: string + message: string + variant: "info" | "success" | "warning" | "error" + duration?: number + } +} + +export type EventTuiSessionSelect = { + id: string + type: "tui.session.select" + properties: { + /** + * Session ID to navigate to + */ + sessionID: string + } +} + export type SessionNetworkWait = { id: string sessionID: string @@ -867,6 +857,16 @@ export type Prompt = { agents?: Array } +export type IndexingStatusState = "Disabled" | "In Progress" | "Complete" | "Error" | "Standby" + +export type IndexingStatus = { + state: IndexingStatusState + message: string + processedFiles: number + totalFiles: number + percent: number +} + export type GlobalEvent = { directory: string project?: string @@ -875,18 +875,16 @@ export type GlobalEvent = { | EventServerConnected | EventGlobalDisposed | EventGlobalConfigUpdated - | EventTuiPromptAppend - | EventTuiCommandExecute - | EventTuiToastShow - | EventTuiSessionSelect - | EventKilocodeAgentManagerStart - | EventIndexingStatus | EventServerInstanceDisposed | EventLspClientDiagnostics | EventLspUpdated | EventQuestionAsked | EventQuestionReplied | EventQuestionRejected + | EventTuiPromptAppend + | EventTuiCommandExecute + | EventTuiToastShow + | EventTuiSessionSelect | EventMcpToolsChanged | EventMcpBrowserOpenFailed | EventSessionNetworkAsked @@ -915,6 +913,7 @@ export type GlobalEvent = { | EventSessionCompacted | EventCommandExecuted | EventProjectUpdated + | EventKilocodeAgentManagerStart | EventVcsBranchUpdated | EventKiloSessionsRemoteStatusChanged | EventWorkspaceReady @@ -959,6 +958,7 @@ export type GlobalEvent = { | EventSessionNextCompactionStarted | EventSessionNextCompactionDelta | EventSessionNextCompactionEnded + | EventIndexingStatus | SyncEventMessageUpdated | SyncEventMessageRemoved | SyncEventMessagePartUpdated @@ -2544,30 +2544,6 @@ export type EventGlobalConfigUpdated = { } } -export type EventKilocodeAgentManagerStart = { - id: string - type: "kilocode.agent_manager.start" - properties: { - requestID: string - sessionID: string - mode: "worktree" | "local" - versions?: boolean - tasks: Array<{ - prompt?: string - name?: string - branchName?: string - }> - } -} - -export type EventIndexingStatus = { - id: string - type: "indexing.status" - properties: { - status: IndexingStatus - } -} - export type EventServerInstanceDisposed = { id: string type: "server.instance.disposed" @@ -2869,6 +2845,22 @@ export type EventProjectUpdated = { properties: Project } +export type EventKilocodeAgentManagerStart = { + id: string + type: "kilocode.agent_manager.start" + properties: { + requestID: string + sessionID: string + mode: "worktree" | "local" + versions?: boolean + tasks: Array<{ + prompt?: string + name?: string + branchName?: string + }> + } +} + export type EventVcsBranchUpdated = { id: string type: "vcs.branch.updated" @@ -3395,6 +3387,14 @@ export type EventSessionNextCompactionEnded = { } } +export type EventIndexingStatus = { + id: string + type: "indexing.status" + properties: { + status: IndexingStatus + } +} + export type SessionInfo = { id: string parentID?: string diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 3b276592d35..e1e6b0a1438 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -26,7 +26,7 @@ "typescript": "catalog:", "vite": "catalog:" }, - "version": "7.3.12", + "version": "7.3.14", "dependencies": {}, "peerDependencies": {} } diff --git a/packages/ui/package.json b/packages/ui/package.json index 5f7ad23c864..9521953808a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "7.3.12", + "version": "7.3.14", "type": "module", "license": "MIT", "exports": { diff --git a/script/upstream/package.json b/script/upstream/package.json index 1ff1f9bc0be..88461767c66 100644 --- a/script/upstream/package.json +++ b/script/upstream/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/upstream-merge", - "version": "7.3.12", + "version": "7.3.14", "private": true, "type": "module", "description": "Scripts for automating upstream opencode merges into Kilo", From 6ee4ed91b53d9032364ea76239c4896e9c9db08f Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 12:37:59 -0400 Subject: [PATCH 31/59] fix(jetbrains): construct prompt panel without blocking read --- .../kotlin/ai/kilocode/client/session/SessionUi.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 7f502d36877..19628c239a0 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -40,7 +40,6 @@ import com.intellij.ide.BrowserUtil import com.intellij.ide.ui.LafManagerListener import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ReadAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.colors.EditorColorsListener import com.intellij.openapi.editor.colors.EditorColorsManager @@ -257,13 +256,11 @@ class SessionUi( scroll = SessionScroll(root, sessionContent, messageBody, blankBody) connection = ConnectionPanel(this, controller) - prompt = ReadAction.computeBlocking { - PromptPanel( - project = project, - onSend = { text -> sendPrompt(text) }, - onAbort = { controller.abort() }, - ) - } + prompt = PromptPanel( + project = project, + onSend = { text -> sendPrompt(text) }, + onAbort = { controller.abort() }, + ) sessionContent.add(header, BorderLayout.NORTH) sessionContent.add(scroll.component, BorderLayout.CENTER) From 5dd59fb5447fbd7353084c0ad0e0059317358b14 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 28 May 2026 12:39:17 -0400 Subject: [PATCH 32/59] test(vscode): update DeepSeek provider priority expectation --- packages/kilo-vscode/tests/unit/model-selector-utils.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/model-selector-utils.test.ts b/packages/kilo-vscode/tests/unit/model-selector-utils.test.ts index 0af708775af..d0bf5ff2254 100644 --- a/packages/kilo-vscode/tests/unit/model-selector-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/model-selector-utils.test.ts @@ -38,9 +38,9 @@ describe("providerSortKey", () => { }) it("sorts providers correctly when used with sort", () => { - const ids = ["google", "anthropic", "kilo", "openai", "github-copilot"] + const ids = ["google", "anthropic", "kilo", "openai", "deepseek"] const sorted = ids.slice().sort((a, b) => providerSortKey(a) - providerSortKey(b)) - expect(sorted).toEqual(["kilo", "anthropic", "github-copilot", "openai", "google"]) + expect(sorted).toEqual(["kilo", "anthropic", "deepseek", "openai", "google"]) }) }) From 7d8ec095c0d7d05b4c3f91149b873f9944716b23 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 28 May 2026 13:17:58 -0400 Subject: [PATCH 33/59] chore(vscode): add DeepSeek provider changeset --- .changeset/deepseek-popular-providers.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/deepseek-popular-providers.md diff --git a/.changeset/deepseek-popular-providers.md b/.changeset/deepseek-popular-providers.md new file mode 100644 index 00000000000..5fd53632235 --- /dev/null +++ b/.changeset/deepseek-popular-providers.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show DeepSeek in the Popular Providers list instead of GitHub Copilot. From c75456d2cb7c9ef084f7a01387ab12aca2428893 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 13:29:36 -0400 Subject: [PATCH 34/59] fix(jetbrains): use static SVG icon variants --- .changeset/jetbrains-static-svg-icons.md | 5 + .../client/session/scroll/ScrollButtonIcon.kt | 24 +--- .../client/session/ui/style/SessionUiStyle.kt | 8 -- .../ai/kilocode/client/ui/SvgIconColorizer.kt | 112 ------------------ .../main/resources/icons/scroll-bottom.svg | 2 +- .../resources/icons/scroll-bottom_dark.svg | 2 +- 6 files changed, 10 insertions(+), 143 deletions(-) create mode 100644 .changeset/jetbrains-static-svg-icons.md delete mode 100644 packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt diff --git a/.changeset/jetbrains-static-svg-icons.md b/.changeset/jetbrains-static-svg-icons.md new file mode 100644 index 00000000000..c16d1814bf8 --- /dev/null +++ b/.changeset/jetbrains-static-svg-icons.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Use static light and dark SVG icon assets in the JetBrains plugin instead of runtime SVG recoloring. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt index d50d4dfe501..8d64f6fa8ff 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt @@ -1,29 +1,11 @@ package ai.kilocode.client.session.scroll -import ai.kilocode.client.session.ui.style.SessionUiStyle -import ai.kilocode.client.ui.UiStyle -import ai.kilocode.client.ui.colorizedSvgIcon -import com.intellij.util.ui.JBUI -import java.awt.Color +import com.intellij.openapi.util.IconLoader import javax.swing.Icon internal object ScrollButtonIcon { - private val bottom = colorizedSvgIcon( - path = "/icons/scroll-bottom.svg", - owner = ScrollButtonIcon::class.java, - fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(), - borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(), - fillColors = listOf(SessionUiStyle.ScrollIcon.BOTTOM_LIGHT, SessionUiStyle.ScrollIcon.BOTTOM_DARK), - borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), - ) - private val prompt = colorizedSvgIcon( - path = "/icons/scroll-question.svg", - owner = ScrollButtonIcon::class.java, - fillColor = UiStyle.Colors.warningLabelForeground(), - borderColor = Color.WHITE, - fillColors = listOf(SessionUiStyle.ScrollIcon.QUESTION), - borderColors = listOf(SessionUiStyle.ScrollIcon.FOREGROUND), - ) + private val bottom: Icon = IconLoader.getIcon("/icons/scroll-bottom.svg", ScrollButtonIcon::class.java) + private val prompt: Icon = IconLoader.getIcon("/icons/scroll-question.svg", ScrollButtonIcon::class.java) fun create(question: Boolean = false): Icon { if (question) return prompt diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt index 4287314ca71..433ad686227 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt @@ -17,14 +17,6 @@ object SessionUiStyle { const val SCROLL_INCREMENT = 16 } - /** Literal source palette values used by session scroll SVG assets before runtime colorization. */ - object ScrollIcon { - const val BOTTOM_LIGHT = 0x384F6B - const val BOTTOM_DARK = 0x233143 - const val QUESTION = 0xE08800 - const val FOREGROUND = 0xFFFFFF - } - /** Shared tokens for individual transcript views and cards. */ object View { const val CARD_LAYOUT_GAP = 6 diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt deleted file mode 100644 index b2e40be3477..00000000000 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt +++ /dev/null @@ -1,112 +0,0 @@ -package ai.kilocode.client.ui - -import com.intellij.ui.JBColor -import com.intellij.util.SVGLoader -import java.awt.Color -import java.awt.Component -import java.awt.Graphics -import java.awt.Graphics2D -import java.awt.Image -import java.awt.RenderingHints -import java.io.ByteArrayInputStream -import kotlin.math.ceil -import javax.swing.Icon - -private const val RGB_MASK = 0x00FFFFFF - -internal fun colorizedSvgIcon( - path: String, - owner: Class<*>, - fillColor: Color, - borderColor: Color = fillColor, - fillColors: Collection, - borderColors: Collection, -): Icon = SvgIcon( - path = path, - owner = owner, - fill = fillColor, - border = borderColor, - fills = fillColors.map { it and RGB_MASK }.toSet(), - borders = borderColors.map { it and RGB_MASK }.toSet(), -) - -private data class Key( - val fill: Int, - val border: Int, - val bright: Boolean, - val scale: Double, -) - -private class SvgIcon( - private val path: String, - private val owner: Class<*>, - private val fill: Color, - private val border: Color, - private val fills: Set, - private val borders: Set, -) : Icon { - private val cache = mutableMapOf() - private val data by lazy { - owner.getResourceAsStream(path)?.use { it.readBytes() } - ?: error("SVG icon not found: $path") - } - private val size by lazy { size(data.toString(Charsets.UTF_8)) } - - override fun getIconWidth(): Int = size.first - - override fun getIconHeight(): Int = size.second - - override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { - val g2 = g as? Graphics2D - if (g2 != null) { - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) - g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY) - } - g.drawImage(image(g), x, y, iconWidth, iconHeight, null) - } - - private fun image(g: Graphics): Image { - val scale = scale(g) - val key = Key(fill.rgb, border.rgb, JBColor.isBright(), scale) - return cache.getOrPut(key) { - SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat()) - } - } - - private fun patch(): ByteArray { - val svg = data.toString(Charsets.UTF_8) - val patched = ATTR.replace(svg) { - val attr = it.groupValues[1] - val rgb = parse(it.groupValues[2]) ?: return@replace it.value - val color = when { - fills.contains(rgb) -> fill - borders.contains(rgb) -> border - else -> return@replace it.value - } - "$attr=\"${hex(color)}\"" - } - return patched.toByteArray(Charsets.UTF_8) - } - - private fun size(svg: String): Pair { - val width = SIZE.find(svg)?.groupValues?.get(1)?.toFloatOrNull() - val height = SIZE.find(svg)?.groupValues?.get(2)?.toFloatOrNull() - return Pair(ceil(width ?: 16f).toInt(), ceil(height ?: 16f).toInt()) - } - - private fun scale(g: Graphics): Double { - if (g !is Graphics2D) return 1.0 - return g.deviceConfiguration.defaultTransform.scaleX.coerceAtLeast(1.0) - } -} - -private val ATTR = Regex("""\b(fill|stroke)=["'](#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8}))["']""") -private val SIZE = Regex("""]*\bwidth=["']([0-9.]+)["'][^>]*\bheight=["']([0-9.]+)["']""") - -private fun parse(value: String): Int? { - return value.removePrefix("#").take(6).toIntOrNull(16)?.and(RGB_MASK) -} - -private fun hex(color: Color): String { - return "#%02X%02X%02X".format(color.red, color.green, color.blue) -} diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom.svg b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom.svg index d1dd8bd8cd7..dad71aa60b5 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom.svg +++ b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom_dark.svg b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom_dark.svg index a9ede4c7320..12fe7ad9f85 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom_dark.svg +++ b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom_dark.svg @@ -1,4 +1,4 @@ - + From f48519be02325c639dd7d44d0970fbd3ea781c74 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 28 May 2026 13:37:51 -0400 Subject: [PATCH 35/59] feat(vscode): describe DeepSeek in popular providers --- .../webview-ui/src/components/settings/provider-catalog.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/br.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/da.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/de.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/en.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/es.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/no.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/th.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 1 + 20 files changed, 20 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts b/packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts index 8594dc3d8ec..d68f3e267e5 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts +++ b/packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts @@ -34,6 +34,7 @@ export function providerNoteKey(providerID: string) { if (providerID === "kilo") return "dialog.provider.kilo.note" if (providerID === "opencode") return "dialog.provider.opencode.note" if (providerID === "anthropic") return "dialog.provider.anthropic.note" + if (providerID === "deepseek") return "dialog.provider.deepseek.note" if (providerID.startsWith("github-copilot")) return "dialog.provider.copilot.note" if (providerID === "openai") return "dialog.provider.openai.note" if (providerID === "google") return "dialog.provider.google.note" diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 553c6899993..e1013e6e187 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "موصى به", "dialog.provider.opencode.note": "نماذج مختارة تشمل Claude وGPT وGemini والمزيد", "dialog.provider.anthropic.note": "اتصل باستخدام Claude Pro/Max أو مفتاح API", + "dialog.provider.deepseek.note": "نماذج DeepSeek لمهام الاستدلال والبرمجة", "dialog.provider.openai.note": "اتصل باستخدام ChatGPT Pro/Plus أو مفتاح API", "dialog.provider.google.note": "نماذج Gemini للاستجابات السريعة والمنظمة", "dialog.provider.openrouter.note": "الوصول إلى جميع النماذج المدعومة من موفر واحد", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 7bed3e1dcd7..7ac6ec20e8f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Recomendado", "dialog.provider.opencode.note": "Modelos selecionados incluindo Claude, GPT, Gemini e mais", "dialog.provider.anthropic.note": "Conectar com Claude Pro/Max ou chave de API", + "dialog.provider.deepseek.note": "Modelos DeepSeek para tarefas de raciocínio e programação", "dialog.provider.openai.note": "Conectar com ChatGPT Pro/Plus ou chave de API", "dialog.provider.google.note": "Modelos Gemini para respostas rápidas e estruturadas", "dialog.provider.openrouter.note": "Acesse todos os modelos suportados a partir de um único provedor", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index c8b61cc97c9..bc5ff8ce4f1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Preporučeno", "dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge", "dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max", + "dialog.provider.deepseek.note": "DeepSeek modeli za zadatke zaključivanja i kodiranja", "dialog.provider.copilot.note": "Claude modeli za pomoć pri kodiranju", "dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke", "dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index c056dd85c69..fde428d5bc3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Anbefalet", "dialog.provider.opencode.note": "Udvalgte modeller inkl. Claude, GPT, Gemini og flere", "dialog.provider.anthropic.note": "Forbind med Claude Pro/Max eller API-nøgle", + "dialog.provider.deepseek.note": "DeepSeek-modeller til ræsonnering og kodningsopgaver", "dialog.provider.openai.note": "Forbind med ChatGPT Pro/Plus eller API-nøgle", "dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar", "dialog.provider.openrouter.note": "Adgang til alle understøttede modeller fra én udbyder", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index e2088604d6d..2ad4ad21ff8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -110,6 +110,7 @@ export const dict = { "dialog.provider.tag.recommended": "Empfohlen", "dialog.provider.opencode.note": "Kuratierte Modelle wie Claude, GPT, Gemini und mehr", "dialog.provider.anthropic.note": "Mit Claude Pro/Max oder API-Schlüssel verbinden", + "dialog.provider.deepseek.note": "DeepSeek-Modelle für Reasoning- und Programmieraufgaben", "dialog.provider.openai.note": "Mit ChatGPT Pro/Plus oder API-Schlüssel verbinden", "dialog.provider.google.note": "Gemini-Modelle für schnelle, strukturierte Antworten", "dialog.provider.openrouter.note": "Zugriff auf alle unterstützten Modelle über einen Anbieter", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index f6d7b5aa10e..0a1e41f8c66 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Recommended", "dialog.provider.opencode.note": "Curated models including Claude, GPT, Gemini and more", "dialog.provider.anthropic.note": "Direct access to Claude models, including Pro and Max", + "dialog.provider.deepseek.note": "DeepSeek models for reasoning and coding tasks", "dialog.provider.copilot.note": "Claude models for coding assistance", "dialog.provider.openai.note": "GPT and Codex models with API key or ChatGPT login", "dialog.provider.google.note": "Gemini models for fast, structured responses", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 0f75096aa2c..3579ef09ca5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Recomendado", "dialog.provider.opencode.note": "Modelos curados incluyendo Claude, GPT, Gemini y más", "dialog.provider.anthropic.note": "Conectar con Claude Pro/Max o clave API", + "dialog.provider.deepseek.note": "Modelos DeepSeek para tareas de razonamiento y programación", "dialog.provider.openai.note": "Conectar con ChatGPT Pro/Plus o clave API", "dialog.provider.google.note": "Modelos Gemini para respuestas rápidas y estructuradas", "dialog.provider.openrouter.note": "Accede a todos los modelos soportados desde un solo proveedor", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 77c1ccde6b1..48d95398357 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -107,6 +107,7 @@ export const dict = { "dialog.provider.tag.recommended": "Recommandé", "dialog.provider.opencode.note": "Modèles sélectionnés incluant Claude, GPT, Gemini et plus", "dialog.provider.anthropic.note": "Connectez-vous avec Claude Pro/Max ou une clé API", + "dialog.provider.deepseek.note": "Modèles DeepSeek pour les tâches de raisonnement et de codage", "dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clé API", "dialog.provider.google.note": "Modèles Gemini pour des réponses rapides et structurées", "dialog.provider.openrouter.note": "Accédez à tous les modèles supportés depuis un seul fournisseur", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 511862955a1..0ad1d4948bc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "推奨", "dialog.provider.opencode.note": "Claude、GPT、Geminiなどの厳選されたモデル", "dialog.provider.anthropic.note": "Claude Pro/MaxまたはAPIキーで接続", + "dialog.provider.deepseek.note": "推論とコーディングタスク向けのDeepSeekモデル", "dialog.provider.openai.note": "ChatGPT Pro/PlusまたはAPIキーで接続", "dialog.provider.google.note": "高速で構造化された応答のためのGeminiモデル", "dialog.provider.openrouter.note": "1つのプロバイダーからすべてのモデルにアクセス", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index f88e3c9eb99..e5595606849 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -110,6 +110,7 @@ export const dict = { "dialog.provider.tag.recommended": "추천", "dialog.provider.opencode.note": "Claude, GPT, Gemini 등 엄선된 모델", "dialog.provider.anthropic.note": "Claude Pro/Max 또는 API 키로 연결", + "dialog.provider.deepseek.note": "추론 및 코딩 작업을 위한 DeepSeek 모델", "dialog.provider.openai.note": "ChatGPT Pro/Plus 또는 API 키로 연결", "dialog.provider.google.note": "빠르고 구조화된 응답을 위한 Gemini 모델", "dialog.provider.openrouter.note": "하나의 공급자에서 모든 지원 모델에 액세스", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 605747baed7..d1e17a4f894 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Aanbevolen", "dialog.provider.opencode.note": "Geselecteerde modellen waaronder Claude, GPT, Gemini en meer", "dialog.provider.anthropic.note": "Directe toegang tot Claude-modellen, inclusief Pro en Max", + "dialog.provider.deepseek.note": "DeepSeek-modellen voor redeneer- en programmeertaken", "dialog.provider.copilot.note": "Claude-modellen voor programmeerhulp", "dialog.provider.openai.note": "GPT-modellen voor snelle, capabele algemene AI-taken", "dialog.provider.google.note": "Gemini-modellen voor snelle, gestructureerde antwoorden", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 6c4561ca297..a7bfc8aef72 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -109,6 +109,7 @@ export const dict = { "dialog.provider.tag.recommended": "Anbefalt", "dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og flere", "dialog.provider.anthropic.note": "Koble til med Claude Pro/Max eller API-nøkkel", + "dialog.provider.deepseek.note": "DeepSeek-modeller for resonnering og kodeoppgaver", "dialog.provider.openai.note": "Koble til med ChatGPT Pro/Plus eller API-nøkkel", "dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar", "dialog.provider.openrouter.note": "Tilgang til alle støttede modeller fra én leverandør", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 09002a2470a..a33d3292ba7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Zalecane", "dialog.provider.opencode.note": "Wybrane modele, w tym Claude, GPT, Gemini i więcej", "dialog.provider.anthropic.note": "Połącz z Claude Pro/Max lub kluczem API", + "dialog.provider.deepseek.note": "Modele DeepSeek do zadań wymagających rozumowania i kodowania", "dialog.provider.openai.note": "Połącz z ChatGPT Pro/Plus lub kluczem API", "dialog.provider.google.note": "Modele Gemini do szybkich, strukturalnych odpowiedzi", "dialog.provider.openrouter.note": "Dostęp do wszystkich obsługiwanych modeli od jednego dostawcy", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index c304e17b2ec..9423a566bf8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Рекомендуемые", "dialog.provider.opencode.note": "Отобранные модели, включая Claude, GPT, Gemini и другие", "dialog.provider.anthropic.note": "Подключитесь с помощью Claude Pro/Max или API ключа", + "dialog.provider.deepseek.note": "Модели DeepSeek для задач рассуждения и программирования", "dialog.provider.openai.note": "Подключитесь с помощью ChatGPT Pro/Plus или API ключа", "dialog.provider.google.note": "Модели Gemini для быстрых структурированных ответов", "dialog.provider.openrouter.note": "Доступ ко всем поддерживаемым моделям через одного провайдера", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 81520ed9ab6..47a8313068d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "แนะนำ", "dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ", "dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max", + "dialog.provider.deepseek.note": "โมเดล DeepSeek สำหรับงานการให้เหตุผลและการเขียนโค้ด", "dialog.provider.copilot.note": "โมเดล Claude สำหรับการช่วยเหลือในการเขียนโค้ด", "dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ", "dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 60862af5843..e0965be67d5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Önerilen", "dialog.provider.opencode.note": "Claude, GPT, Gemini ve daha fazlasını içeren seçilmiş modeller", "dialog.provider.anthropic.note": "Pro ve Max dahil Claude modellerine doğrudan erişim", + "dialog.provider.deepseek.note": "Muhakeme ve kodlama görevleri için DeepSeek modelleri", "dialog.provider.copilot.note": "Kodlama yardımı için Claude modelleri", "dialog.provider.openai.note": "Hızlı ve yetenekli genel yapay zeka görevleri için GPT modelleri", "dialog.provider.google.note": "Hızlı ve yapılandırılmış yanıtlar için Gemini modelleri", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 8912a98ff32..dc299403ee4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -106,6 +106,7 @@ export const dict = { "dialog.provider.tag.recommended": "Рекомендовано", "dialog.provider.opencode.note": "Добірка моделей включаючи Claude, GPT, Gemini та інші", "dialog.provider.anthropic.note": "Прямий доступ до моделей Claude включаючи Pro та Max", + "dialog.provider.deepseek.note": "Моделі DeepSeek для завдань міркування та програмування", "dialog.provider.copilot.note": "Моделі Claude для допомоги з кодуванням", "dialog.provider.openai.note": "Моделі GPT для швидких і потужних загальних завдань ШІ", "dialog.provider.google.note": "Моделі Gemini для швидких і структурованих відповідей", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 02c94b7352f..e4ba2334bb7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -109,6 +109,7 @@ export const dict = { "dialog.provider.group.other": "其他", "dialog.provider.tag.recommended": "推荐", "dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接", + "dialog.provider.deepseek.note": "用于推理和编程任务的 DeepSeek 模型", "dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接", "dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接", "dialog.provider.opencode.note": "使用 OpenCode Zen 或 API 密钥连接", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 1228a29c8b5..68055114a8c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -110,6 +110,7 @@ export const dict = { "dialog.provider.tag.recommended": "推薦", "dialog.provider.opencode.note": "精選模型,包含 Claude、GPT、Gemini 等", "dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 金鑰連線", + "dialog.provider.deepseek.note": "用於推理和程式設計任務的 DeepSeek 模型", "dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 金鑰連線", "dialog.provider.copilot.note": "使用 Copilot 或 API 金鑰連線", "dialog.provider.google.note": "Gemini 模型,提供快速且結構化的回應", From d114406c1f085b6d83aeb6e47de61bd6fd3c4b6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 17:59:52 +0000 Subject: [PATCH 36/59] chore: update kilo-vscode visual regression baselines --- .../chat/turn-outcome-failed-chromium-linux.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-failed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-failed-chromium-linux.png index f011a8544ec..757e3d3a669 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-failed-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-failed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6d0ccfb86ad9a6eaab64e1afe455a96cd4c1443f12fbd47698a4730724712b6 -size 931 +oid sha256:5b8208c98b0ab3551094af455bf2c7a947c2973f0adb6fe6fa99f16da970454a +size 1184 From ac1fedabd19796d7a6014593fa6efdd6aa96e4a9 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 14:24:53 -0400 Subject: [PATCH 37/59] build check fixes --- packages/kilo-jetbrains/build.gradle.kts | 3 ++- packages/kilo-jetbrains/script/build-sign-check.sh | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kilo-jetbrains/build.gradle.kts b/packages/kilo-jetbrains/build.gradle.kts index af2d807656c..1bfb678c3f3 100644 --- a/packages/kilo-jetbrains/build.gradle.kts +++ b/packages/kilo-jetbrains/build.gradle.kts @@ -39,7 +39,8 @@ fun gitTag(): String? { } val release = providers.gradleProperty("production").map { it.toBoolean() }.orElse(false).get() -val ver = if (release) checked( +val override = providers.gradleProperty("kilo.version").orNull?.trim()?.takeIf { it.isNotEmpty() } +val ver = override?.let(::checked) ?: if (release) checked( gitTag()?.removePrefix("jetbrains/v") ?: error("Missing JetBrains plugin version. Publish builds must run from a jetbrains/v tag."), ) else checked(gitTag()?.removePrefix("jetbrains/v") ?: "0.0.0-dev") diff --git a/packages/kilo-jetbrains/script/build-sign-check.sh b/packages/kilo-jetbrains/script/build-sign-check.sh index 87825b9143d..9c4ca5c1c06 100755 --- a/packages/kilo-jetbrains/script/build-sign-check.sh +++ b/packages/kilo-jetbrains/script/build-sign-check.sh @@ -49,9 +49,8 @@ if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then fi script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -root="$(git -C "$script" rev-parse --show-toplevel)" plugin="$(cd "${script}/.." && pwd)" -secrets="${root}/.secrets" +secrets="${HOME}/.secrets/jetbrains" chain="${secrets}/chain.crt" key="${secrets}/private.pem" encrypted_key="${secrets}/private_encrypted.pem" From 81eb3f31523320852c35acd9d1c71a96830e1b95 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 14:52:33 -0400 Subject: [PATCH 38/59] fix(jetbrains): preserve session scroll for question text --- .../client/session/scroll/SessionScroll.kt | 17 +++++++++++++++++ .../session/views/base/BaseQuestionView.kt | 3 +++ .../views/question/QuestionResultView.kt | 3 +++ .../session/views/question/QuestionView.kt | 3 +++ .../client/session/SessionScrollTest.kt | 17 +++++++++++++++++ 5 files changed, 43 insertions(+) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt index 7a6a503e56f..fe9f0b0241c 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt @@ -53,6 +53,7 @@ internal class SessionScroll( private var user = false private var value = 0 private var question = false + private var restoring = false init { jump = JBLabel(ScrollButtonIcon.create()).apply { @@ -71,6 +72,7 @@ internal class SessionScroll( user = true } }) + component.viewport.addChangeListener { onViewport() } component.verticalScrollBar.addAdjustmentListener { onScroll() } root.addOverlay(jump) { _, child -> val size = child.preferredSize @@ -266,6 +268,21 @@ internal class SessionScroll( bar.value = bottom() } + @RequiresEdt + private fun onViewport() { + if (restoring || auto || opening || user || tail || component.viewport.view !== messages) return + val y = value.coerceIn(0, bottom()) + if (component.viewport.viewPosition.y == y && bar.value == y) return + restoring = true + try { + component.viewport.viewPosition = Point(0, y) + bar.value = y + } finally { + restoring = false + } + updateJump() + } + @RequiresEdt private fun bottom(): Int { val bar = component.verticalScrollBar diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt index 96ee96512aa..15e27932ebc 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt @@ -15,6 +15,7 @@ import java.awt.BorderLayout import java.awt.Color import java.awt.Component import java.awt.Dimension +import java.awt.Rectangle import javax.swing.JButton import javax.swing.Icon import javax.swing.JComponent @@ -315,6 +316,8 @@ class BaseQuestionView : RoundedContentPanel( return Dimension(Int.MAX_VALUE, size.height) } + override fun scrollRectToVisible(aRect: Rectangle) {} + private fun withWidth(fallback: Int): Dimension { val w = availableWidth() if (w <= 0) return Dimension(super.getPreferredSize().width, fallback) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultView.kt index 93150bad7a4..9c0886aab36 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultView.kt @@ -18,6 +18,7 @@ import java.awt.Component import java.awt.Cursor import java.awt.Dimension import java.awt.Font +import java.awt.Rectangle import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.BoxLayout @@ -213,6 +214,8 @@ class QuestionResultView(tool: Tool) : PartView() { return Dimension(Int.MAX_VALUE, size.height) } + override fun scrollRectToVisible(aRect: Rectangle) {} + private fun withWidth(fallback: Int): Dimension { val width = space() if (width <= 0) return Dimension(super.getPreferredSize().width, fallback) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt index 832ee5484f7..1c8a77c28db 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt @@ -27,6 +27,7 @@ import java.awt.Color import java.awt.Component import java.awt.Dimension import java.awt.GridBagLayout +import java.awt.Rectangle import java.awt.event.FocusAdapter import java.awt.event.FocusEvent import java.awt.event.MouseAdapter @@ -657,6 +658,8 @@ class QuestionView( return Dimension(Int.MAX_VALUE, size.height) } + override fun scrollRectToVisible(aRect: Rectangle) {} + private fun withWidth(fallback: Int): Dimension { val width = space() if (width <= 0) return Dimension(super.getPreferredSize().width, fallback) diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt index 1f7777576a1..a5c6fbcacd3 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt @@ -17,6 +17,7 @@ import com.intellij.util.ui.JBUI import java.awt.Container import javax.swing.AbstractButton import javax.swing.JButton +import javax.swing.JTextArea import kotlinx.coroutines.CompletableDeferred @Suppress("UnstableApiUsage") @@ -778,6 +779,22 @@ class SessionScrollTest : SessionUiTestBase() { assertTrue(jumpButton().isVisible) } + fun `test question text caret visibility cannot move middle scroll position`() { + showMessages() + fillTranscript(24) + val bar = scrollBar() + emit(ChatEventDto.QuestionAsked("ses_test", largeQuestion("q_text_caret_middle"))) + drainScroll() + setValue(bar, bottom(bar) / 2) + val value = bar.value + + findAll(ui).first { !it.isEditable }.scrollRectToVisible(java.awt.Rectangle(0, 10_000, 1, 1)) + drainScroll() + + assertEquals(value, bar.value) + assertTrue(jumpButton().isVisible) + } + fun `test question option selection in middle does not resume following`() { showMessages() fillTranscript(24) From 3f3b0171b737c6770d09651c9d687e68099c7632 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 14:58:46 -0400 Subject: [PATCH 39/59] fix(jetbrains): tune question scroll icon colors --- .../frontend/src/main/resources/icons/scroll-question.svg | 2 +- .../frontend/src/main/resources/icons/scroll-question_dark.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question.svg b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question.svg index b05479827e8..5230d4344ff 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question.svg +++ b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question.svg @@ -1,5 +1,5 @@ - + diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question_dark.svg b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question_dark.svg index b05479827e8..f4733a8aed4 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question_dark.svg +++ b/packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question_dark.svg @@ -1,5 +1,5 @@ - + From 7b6b0dceca2390608010f55e96ab8a62e60fea39 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 28 May 2026 15:01:43 -0400 Subject: [PATCH 40/59] fix(jetbrains): refresh session style when shown --- .kilo/plans/1779979921672-mighty-river.md | 37 +++++++++ .kilo/plans/1779979972859-crisp-comet.md | 38 +++++++++ .kilo/plans/1779980012659-happy-circuit.md | 37 +++++++++ .kilo/plans/1779980572375-proud-river.md | 35 ++++++++ .kilo/plans/1779987162764-misty-rocket.md | 69 ++++++++++++++++ .kilo/plans/1779987392284-clever-river.md | 60 ++++++++++++++ .kilo/plans/1779989384508-mighty-cabin.md | 49 +++++++++++ .kilo/plans/1779990397226-quiet-canyon.md | 81 +++++++++++++++++++ .../ai/kilocode/client/session/SessionUi.kt | 9 ++- 9 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 .kilo/plans/1779979921672-mighty-river.md create mode 100644 .kilo/plans/1779979972859-crisp-comet.md create mode 100644 .kilo/plans/1779980012659-happy-circuit.md create mode 100644 .kilo/plans/1779980572375-proud-river.md create mode 100644 .kilo/plans/1779987162764-misty-rocket.md create mode 100644 .kilo/plans/1779987392284-clever-river.md create mode 100644 .kilo/plans/1779989384508-mighty-cabin.md create mode 100644 .kilo/plans/1779990397226-quiet-canyon.md diff --git a/.kilo/plans/1779979921672-mighty-river.md b/.kilo/plans/1779979921672-mighty-river.md new file mode 100644 index 00000000000..0e67f21ce0b --- /dev/null +++ b/.kilo/plans/1779979921672-mighty-river.md @@ -0,0 +1,37 @@ +# Sample Plan With Todos + +## Goal +Create a clear sample plan that breaks work into actionable todos. + +## Todos + +1. Define the objective + - Clarify the desired outcome. + - Identify the target audience. + - List success criteria. + +2. Gather requirements + - Collect relevant inputs. + - Note constraints and assumptions. + - Identify dependencies. + +3. Break down the work + - Split the goal into smaller tasks. + - Prioritize tasks by importance. + - Estimate effort for each task. + +4. Execute the tasks + - Complete the highest-priority items first. + - Track progress as todos are finished. + - Adjust the plan if new information appears. + +5. Review and validate + - Check that all todos are complete. + - Verify the result matches the goal. + - Document any follow-up work. + +## Acceptance Criteria +- The plan has a clear goal. +- The plan includes a list of actionable todos. +- The todos are ordered logically. +- The plan includes a way to confirm completion. diff --git a/.kilo/plans/1779979972859-crisp-comet.md b/.kilo/plans/1779979972859-crisp-comet.md new file mode 100644 index 00000000000..444d71b8dad --- /dev/null +++ b/.kilo/plans/1779979972859-crisp-comet.md @@ -0,0 +1,38 @@ +# Sample Plan With Todos + +## Goal +Create a simple sample plan that demonstrates how to organize work into clear, actionable todos. + +## Implementation Steps + +1. Define the objective + - State the desired outcome in one sentence. + - Identify who the plan is for. + - List the criteria that indicate success. + +2. Gather requirements + - Capture relevant inputs and context. + - Note constraints, assumptions, and dependencies. + - Identify any missing information that may affect execution. + +3. Break down the work + - Split the objective into smaller tasks. + - Order tasks by dependency and priority. + - Keep each todo specific enough to complete independently. + +4. Execute the todos + - Work through the highest-priority tasks first. + - Mark todos complete as progress is made. + - Update the plan if new information changes the scope. + +5. Review the result + - Confirm every todo has been addressed. + - Verify the result matches the original objective. + - Record any follow-up work separately. + +## Acceptance Criteria + +- The plan has a clear goal. +- The plan includes actionable todos. +- The todos are ordered logically. +- The plan includes a review step to confirm completion. diff --git a/.kilo/plans/1779980012659-happy-circuit.md b/.kilo/plans/1779980012659-happy-circuit.md new file mode 100644 index 00000000000..9256b415dd1 --- /dev/null +++ b/.kilo/plans/1779980012659-happy-circuit.md @@ -0,0 +1,37 @@ +# Sample Plan: Add Todo-Based Workflow + +## Goal +Create a small, traceable workflow for implementing a feature with clear todos, validation steps, and completion criteria. + +## Scope +- Identify the affected package or feature area. +- Make the smallest correct code change. +- Add or update tests only where they verify behavior. +- Run the smallest relevant checks before completion. + +## Todos +- [ ] Inspect the relevant files and existing patterns. +- [ ] Confirm whether the change belongs in Kilo-owned code or shared upstream code. +- [ ] Implement the minimal code change. +- [ ] Add or update targeted tests if behavior changes. +- [ ] Run formatting or linting if the touched package requires it. +- [ ] Run the smallest relevant typecheck or test command. +- [ ] Fix any failures introduced by the change. +- [ ] Summarize changed files and verification results. + +## Implementation Notes +- Prefer Kilo-owned directories for Kilo-specific behavior. +- If shared upstream files must be edited, keep the change narrow and add `kilocode_change` markers where required. +- Avoid broad refactors unless they are necessary for the requested behavior. +- Preserve existing style, naming, and package conventions. + +## Verification +- Run the targeted test for the changed behavior when available. +- Run the package typecheck if TypeScript code changes. +- Run any repo-specific guard required by touched files, such as annotation or source-link checks. + +## Completion Criteria +- The requested behavior is implemented. +- Tests or checks relevant to the change pass, or any inability to run them is documented. +- No unrelated files are modified. +- The final response includes a concise summary and verification status. diff --git a/.kilo/plans/1779980572375-proud-river.md b/.kilo/plans/1779980572375-proud-river.md new file mode 100644 index 00000000000..bb790b5fdb7 --- /dev/null +++ b/.kilo/plans/1779980572375-proud-river.md @@ -0,0 +1,35 @@ +# Sample Plan + +## Goal +Create a small example feature or change using a clear implementation workflow and tracked todo list. + +## Scope +- Add or update a minimal code path. +- Add focused tests for the changed behavior. +- Run the smallest relevant verification command. +- Summarize the result and any follow-up work. + +## Todo +- [ ] Inspect the relevant files and existing patterns. +- [ ] Identify the smallest safe implementation approach. +- [ ] Make the code change. +- [ ] Add or update tests for the behavior. +- [ ] Run targeted tests or typecheck. +- [ ] Fix any failures introduced by the change. +- [ ] Provide a concise final summary with verification results. + +## Implementation Notes +- Prefer the smallest correct change over broad refactors. +- Preserve existing style and naming conventions. +- Avoid touching unrelated files. +- If shared upstream-owned files are involved, use the repository’s `kilocode_change` marker guidance. + +## Verification +Run the most specific applicable check for the touched area, such as: +- CLI: `bun test ./path/to/test.ts` from `packages/opencode/` +- VS Code extension: `bun run test:unit` from `packages/kilo-vscode/` +- Cross-package changes: `bun run typecheck` from the repo root + +## Risks +- The sample scope may need adjustment once the real target files are known. +- Verification commands depend on which package is touched. diff --git a/.kilo/plans/1779987162764-misty-rocket.md b/.kilo/plans/1779987162764-misty-rocket.md new file mode 100644 index 00000000000..441e2dedde1 --- /dev/null +++ b/.kilo/plans/1779987162764-misty-rocket.md @@ -0,0 +1,69 @@ +# Plan: Replace Internal SVGLoader Usage + +## Context + +The JetBrains plugin currently imports `com.intellij.util.SVGLoader` in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt` and calls `SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat())` from `SvgIcon.image(Graphics)`. + +Inspection reports this as an internal API usage because `SVGLoader` and its `load(InputStream, float)` method are marked `@ApiStatus.Internal`. + +I checked the local IntelliJ source reference via `$INTELLIJ_REPO=/Users/kirillk/products/intellij-community`: + +- `platform/util/ui/src/com/intellij/util/SVGLoader.kt` +- `platform/util/ui/src/com/intellij/ui/svg/svg.kt` +- `platform/util/ui/src/com/intellij/ui/svg/jsvg.kt` +- `libraries/jsvg/intellij.libraries.jsvg.iml` + +Important findings: + +- IntelliJ `SVGLoader.load(stream, scale)` delegates to internal `loadSvg(...)` in `com.intellij.ui.svg`. +- The actual renderer uses `com.github.weisj:jsvg:2.1.0` and renders into a `BufferedImage` with antialiasing, bicubic interpolation, and pure stroke control. +- Copying `SVGLoader.kt` directly would keep references to other internal IntelliJ APIs (`IconLoader`, `ScaleContext`, `ImageUtil`, `SvgAttributePatcher`, `createJSvgDocument`, `renderSvgWithSize`, cache classes, etc.) and would be broader than needed. + +## Implementation Plan + +1. Add Kilo-owned SVG rendering code under the frontend UI package, for example: + - `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/SvgLoader.kt` + - Package: `ai.kilocode.client.ui` + - Suggested internal object name: `SvgLoader` + +2. Keep the copied implementation minimal and purpose-specific: + - Accept `InputStream` and `scale: Float`. + - Parse the SVG with `com.github.weisj.jsvg.parser.SVGLoader().load(stream)` or the equivalent public jsvg API available in version `2.1.0`. + - Determine image dimensions from the root SVG `width` and `height` already parsed by existing `SvgIconColorizer.size(...)`, or by passing dimensions into the loader if the public jsvg API does not expose reliable intrinsic size. + - Render into `BufferedImage(width * scale, height * scale, BufferedImage.TYPE_INT_ARGB)` using `Graphics2D` rendering hints matching IntelliJ’s `jsvg.kt`: + - `KEY_ANTIALIASING = VALUE_ANTIALIAS_ON` + - `KEY_INTERPOLATION = VALUE_INTERPOLATION_BICUBIC` + - `KEY_STROKE_CONTROL = VALUE_STROKE_PURE` + - Return `Image`/`BufferedImage`. + +3. If jsvg does not expose intrinsic size cleanly from public API, adjust the API to avoid duplicating SVG parsing: + - Add `width` and `height` parameters to the Kilo loader. + - In `SvgIcon.image(Graphics)`, call the loader with the already-computed `size.first` and `size.second`. + - This is the likely smallest stable solution because current icons already require explicit `width` and `height` for `SvgIconColorizer.size(...)`. + +4. Add the jsvg dependency explicitly to the plugin frontend module instead of relying on the IntelliJ-bundled library: + - In `packages/kilo-jetbrains/gradle/libs.versions.toml`, add `jsvg = "2.1.0"` and `jsvg = { module = "com.github.weisj:jsvg", version.ref = "jsvg" }`. + - In `packages/kilo-jetbrains/frontend/build.gradle.kts`, add `implementation(libs.jsvg)`. + - This follows the package instruction to bundle third-party libraries with the plugin rather than relying on IDE-bundled versions. + +5. Update `SvgIconColorizer.kt`: + - Remove `import com.intellij.util.SVGLoader`. + - Replace `SVGLoader.load(ByteArrayInputStream(patch()), scale.toFloat())` with the Kilo-owned loader call. + - Keep the existing color patching, cache key, scale calculation, and size parsing intact. + +6. Add a focused frontend unit test: + - New test file: `frontend/src/test/kotlin/ai/kilocode/client/ui/SvgIconColorizerTest.kt` or `SvgLoaderTest.kt`. + - Exercise the actual loader/colorizer with a small SVG resource or inline SVG bytes. + - Assert that rendering returns a non-empty image at scale `1f` and `2f`, and that scaled dimensions are correct. + - If testing the colorizer, paint the icon into a `BufferedImage` and verify at least one expected patched pixel color appears. + +7. Run verification from `packages/kilo-jetbrains/`: + - `./gradlew typecheck` + - A targeted frontend test if Gradle supports one cleanly, otherwise `./gradlew test`. + - The requested DevKit internal API inspection is IDE-based; note in final response that the code no longer references `com.intellij.util.SVGLoader`, and typecheck/tests were run. + +## Notes And Risks + +- The user said “copy SaveLoaer”; I interpret this as “copy `SVGLoader`”. The implementation should copy the relevant rendering behavior, not IntelliJ’s full internal `SVGLoader` object, because the full source depends on multiple other internal APIs and cache infrastructure. +- This change is in `packages/kilo-jetbrains/`, a Kilo-owned package, so `kilocode_change` markers are not needed. +- jsvg package names and constructors should be confirmed during implementation against the downloaded dependency/API available to Gradle. If the API differs from IntelliJ source usage, inspect the source jar or compile error and adapt the minimal wrapper accordingly. diff --git a/.kilo/plans/1779987392284-clever-river.md b/.kilo/plans/1779987392284-clever-river.md new file mode 100644 index 00000000000..bc4180b51f9 --- /dev/null +++ b/.kilo/plans/1779987392284-clever-river.md @@ -0,0 +1,60 @@ +# Remove JetBrains SVG Runtime Colorization + +## Goal +Stop runtime SVG recoloring in the JetBrains plugin and rely on IntelliJ's standard light/dark icon asset resolution. Ensure every SVG asset used by the plugin has a light/dark pair. + +## Findings +- Runtime colorization exists only in `frontend/src/main/kotlin/ai/kilocode/client/ui/SvgIconColorizer.kt`. +- The only production caller is `frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt`. +- `ScrollButtonIcon` colorizes two icons at runtime: + - `/icons/scroll-bottom.svg` + - `/icons/scroll-question.svg` +- Both already have dark variants: + - `scroll-bottom_dark.svg` + - `scroll-question_dark.svg` +- Other used frontend icons already have light/dark pairs: + - `send.svg` / `send_dark.svg` + - `stop.svg` / `stop_dark.svg` + - `shield.svg` / `shield_dark.svg` + - `shield-filled.svg` / `shield-filled_dark.svg` + - `compress.svg` / `compress_dark.svg` + - `chevron-down.svg` / `chevron-down_dark.svg` + - `arrow-up.svg` / `arrow-up_dark.svg` + - `arrow-down-to-line.svg` / `arrow-down-to-line_dark.svg` + - `kilo.svg` / `kilo_dark.svg` + - `kilo-content.svg` / `kilo-content_dark.svg` + - `plus.svg` / `plus_dark.svg` exists, though no current direct source reference was found. + - `kilo@20x20.svg` / `kilo@20x20_dark.svg` exists, though no current direct source reference was found. +- `src/main/resources/META-INF/pluginIcon.svg` is present and has no `pluginIcon_dark.svg`. It is not referenced in source/XML, but JetBrains treats this filename conventionally as plugin metadata/marketplace icon, so it should be paired for completeness. +- The SVG assets use literal colors and no `currentColor`, `