From 34d382fb520ab082575ebc5876810ad459941020 Mon Sep 17 00:00:00 2001 From: ops Date: Wed, 4 Feb 2026 03:27:53 +0100 Subject: [PATCH 01/47] fix: add length constraint in question tool prompt --- packages/opencode/src/tool/question.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/tool/question.txt b/packages/opencode/src/tool/question.txt index 03cd496d60a..35199e6aff4 100644 --- a/packages/opencode/src/tool/question.txt +++ b/packages/opencode/src/tool/question.txt @@ -8,3 +8,4 @@ Usage notes: - When `custom` is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options - Answers are returned as arrays of labels; set `multiple: true` to allow selecting more than one - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label +- Header must be 30 characters or less (maxLength: 30) \ No newline at end of file From e3b9da27df22dedd7a2100ed8894cc024b9e91b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20ceylan?= <33789758+haliliceylan@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:11:15 +0000 Subject: [PATCH 02/47] fix(ui): remove redundant copy button from TextPartDisplay --- packages/ui/src/components/message-part.tsx | 30 +-------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 42dc37abc5c..6da22168fb5 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -668,42 +668,14 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { PART_MAPPING["text"] = function TextPartDisplay(props) { const data = useData() - const i18n = useI18n() const part = props.part as TextPart const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory) const throttledText = createThrottledValue(displayText) - const [copied, setCopied] = createSignal(false) - - const handleCopy = async () => { - const content = displayText() - if (!content) return - await navigator.clipboard.writeText(content) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } return (
-
- -
- - e.preventDefault()} - onClick={handleCopy} - aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")} - /> - -
-
+
) From 4fd19a1cd7a160f52dce1256dfd20eba643afe07 Mon Sep 17 00:00:00 2001 From: Olusammytee Date: Thu, 19 Feb 2026 09:32:03 -0500 Subject: [PATCH 03/47] docs(cloud-agent): clarify Setup Commands vs .kilocode/setup-script --- .../pages/code-with-ai/platforms/cloud-agent.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md b/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md index 708e51e81fd..967ec981698 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md @@ -85,6 +85,15 @@ You can customize each Cloud Agent session by also defining env vars and startup - Bootstrapping tooling - Running setup scripts +### Setup Commands vs `.kilocode/setup-script` + +- Cloud Agent executes **Setup Commands** configured in the Cloud UI/profile. +- Cloud Agent does **not** automatically discover or run `.kilocode/setup-script`. +- If you want to use `.kilocode/setup-script` in Cloud Agent, call it explicitly from Setup Commands, for example: `bash .kilocode/setup-script`. +- If both are present, execution order is: + 1. Setup Commands (in the order you define them) + 2. Anything those commands invoke (such as `.kilocode/setup-script`) + ## Skills Cloud Agents support project-level [skills](/docs/code-with-ai/platforms/cli#skills) stored in your repository. When your repo is cloned, any skills in `.kilocode/skills/` are automatically available. From db43f2bccdbee9e090c9a225d917bb31e4a93525 Mon Sep 17 00:00:00 2001 From: Olusammytee Date: Thu, 19 Feb 2026 09:38:31 -0500 Subject: [PATCH 04/47] docs: update mode-specific custom instruction file guidance --- .../pages/customize/custom-instructions.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/kilo-docs/pages/customize/custom-instructions.md b/packages/kilo-docs/pages/customize/custom-instructions.md index a28f8b2bdf3..3f475a247e7 100644 --- a/packages/kilo-docs/pages/customize/custom-instructions.md +++ b/packages/kilo-docs/pages/customize/custom-instructions.md @@ -21,7 +21,7 @@ Custom Instructions are IDE-wide and are applied across all workspaces and maint {% image src="/docs/img/custom-instructions/custom-instructions.png" alt="Kilo Code Modes tab showing global custom instructions interface" width="600" caption="Kilo Code Modes tab showing global custom instructions interface" /%} -1. **Open Modes Tab:** Click the icon in the Kilo Code top menu bar +1. **Open Modes Tab:** Click the icon in the Kilo Code top menu bar 2. **Find Section:** Find the "Custom Instructions for All Modes" section 3. **Enter Instructions:** Enter your instructions in the text area 4. **Save Changes:** Click "Done" to save your changes @@ -31,7 +31,7 @@ Custom Instructions are IDE-wide and are applied across all workspaces and maint Mode-specific instructions can be set using the Modes Tab {% image src="/docs/img/custom-instructions/custom-instructions-3.png" alt="Kilo Code Modes tab showing mode-specific custom instructions interface" width="600" caption="Kilo Code Modes tab showing mode-specific custom instructions interface" /%} - * **Open Tab:** Click the icon in the Kilo Code top menu bar + * **Open Tab:** Click the icon in the Kilo Code top menu bar * **Select Mode:** Under the Modes heading, click the button for the mode you want to customize * **Enter Instructions:** Enter your instructions in the text area under "Mode-specific Custom Instructions (optional)" * **Save Changes:** Click "Done" to save your changes @@ -40,6 +40,17 @@ Mode-specific instructions can be set using the Modes Tab If the mode itself is global (not workspace-specific), any custom instructions you set for it will also apply globally for that mode across all workspaces. {% /callout %} +## Mode-Specific Instructions from Files + +For version-controlled mode instructions, use the mode rules file paths documented in [Custom Modes](/docs/customize/custom-modes#mode-specific-instructions-via-filesdirectories): + +- Preferred: `.kilo/rules-{mode-slug}/` (directory) +- Fallback: `.kilorules-{mode-slug}` (single file) + +{% callout type="info" title="Legacy Naming Note" %} +Older naming like `.clinerules-{mode-slug}` is not the recommended path for current Kilo mode-specific instructions. +{% /callout %} + ## Related Features - [Custom Modes](/docs/customize/custom-modes) From 25f3eac9a617db55aa440df767b31d486ca4077e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Thu, 19 Feb 2026 15:14:34 +0100 Subject: [PATCH 05/47] feat(vscode): make filenames in chat clickable to open editor tab (#461, #404) Add onOpenFile callback to shared DataProvider so tool renderers can trigger file-open in the host environment. In the VS Code extension, clicking a filename posts an openFile message to the extension host which resolves the path and opens the document in a preview tab. Tool call headers (read, edit, write, apply_patch) now show pointer cursor and underline on hover for file paths. Inline code in markdown prose that looks like a file path (relative or absolute, with optional :line or :line:col suffix) is wrapped with a file-link class and opens on click via delegated event handling. When a line number is present, the editor scrolls to that line. Also adds cursor:pointer to user-message attachments to fix the general clickable-items discoverability issue. --- packages/kilo-vscode/src/KiloProvider.ts | 27 ++++++ packages/kilo-vscode/webview-ui/src/App.tsx | 9 +- .../webview-ui/src/types/messages.ts | 8 ++ packages/ui/src/components/markdown.css | 13 +++ packages/ui/src/components/message-part.css | 40 +++++++++ packages/ui/src/components/message-part.tsx | 83 +++++++++++++++++-- packages/ui/src/context/data.tsx | 4 + packages/ui/src/context/marked.tsx | 27 ++++++ 8 files changed, 202 insertions(+), 9 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 7a43b8d1bb4..4a74fa38f67 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -316,6 +316,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper vscode.env.openExternal(vscode.Uri.parse(message.url)) } break + case "openFile": + if (message.filePath) { + this.handleOpenFile(message.filePath, message.line, message.column) + } + break case "requestProviders": await this.fetchAndSendProviders() break @@ -1201,6 +1206,28 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + /** + * Handle openFile request from the webview — open a file in the VS Code editor. + */ + private handleOpenFile(filePath: string, line?: number, column?: number): void { + const absolute = /^(?:\/|[a-zA-Z]:[\\/])/.test(filePath) + const uri = absolute + ? vscode.Uri.file(filePath) + : vscode.Uri.joinPath(vscode.Uri.file(this.getWorkspaceDirectory()), filePath) + vscode.workspace.openTextDocument(uri).then( + (doc) => { + const options: vscode.TextDocumentShowOptions = { preview: true } + if (line !== undefined && line > 0) { + const col = column !== undefined && column > 0 ? column - 1 : 0 + const pos = new vscode.Position(line - 1, col) + options.selection = new vscode.Range(pos, pos) + } + vscode.window.showTextDocument(doc, options) + }, + (err) => console.error("[Kilo New] KiloProvider: Failed to open file:", uri.fsPath, err), + ) + } + /** * Handle logout request from the webview. */ diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index f940c36e468..c7961f25ba5 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -10,7 +10,7 @@ import { DataProvider } from "@kilocode/kilo-ui/context/data" import { Toast } from "@kilocode/kilo-ui/toast" import Settings from "./components/Settings" import ProfileView from "./components/ProfileView" -import { VSCodeProvider } from "./context/vscode" +import { VSCodeProvider, useVSCode } from "./context/vscode" import { ServerProvider, useServer } from "./context/server" import { ProviderProvider } from "./context/provider" import { ConfigProvider } from "./context/config" @@ -47,6 +47,7 @@ const DummyView: Component<{ title: string }> = (props) => { */ export const DataBridge: Component<{ children: any }> = (props) => { const session = useSession() + const vscode = useVSCode() const data = createMemo(() => { const id = session.currentSessionID() @@ -70,8 +71,12 @@ export const DataBridge: Component<{ children: any }> = (props) => { session.syncSession(sessionID) } + const open = (filePath: string, line?: number, column?: number) => { + vscode.postMessage({ type: "openFile", filePath, line, column }) + } + return ( - + {props.children} ) diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 9d9cef885e1..429012b291e 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -651,6 +651,13 @@ export interface OpenExternalRequest { url: string } +export interface OpenFileRequest { + type: "openFile" + filePath: string + line?: number + column?: number +} + export interface CancelLoginRequest { type: "cancelLogin" } @@ -825,6 +832,7 @@ export type WebviewMessage = | LogoutRequest | RefreshProfileRequest | OpenExternalRequest + | OpenFileRequest | CancelLoginRequest | SetOrganizationRequest | WebviewReadyRequest diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css index 68ae93bda4a..1a5c709d659 100644 --- a/packages/ui/src/components/markdown.css +++ b/packages/ui/src/components/markdown.css @@ -173,6 +173,19 @@ /* border-radius: 2px; */ /* background: var(--surface-base); */ /* box-shadow: 0 0 0 0.5px var(--border-weak-base); */ + + &.file-link { + cursor: pointer; + text-decoration-line: underline; + text-decoration-style: dotted; + text-underline-offset: 2px; + transition: color 0.15s ease; + + &:hover { + color: var(--text-interactive-base); + text-decoration-style: solid; + } + } } /* Tables */ diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 44db4f9aa52..c1bf0a263bf 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -34,6 +34,7 @@ overflow: hidden; background: var(--surface-weak); border: 1px solid var(--border-weak-base); + cursor: pointer; transition: border-color 0.15s ease; &:hover { @@ -274,6 +275,16 @@ [data-slot="message-part-title-filename"] { /* No text-transform - preserve original filename casing */ + + &.clickable { + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + text-decoration: underline; + color: var(--text-base); + } + } } [data-slot="message-part-path"] { @@ -289,6 +300,16 @@ white-space: nowrap; direction: rtl; text-align: left; + + &.clickable { + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + text-decoration: underline; + color: var(--text-base); + } + } } [data-slot="message-part-filename"] { @@ -797,6 +818,16 @@ text-overflow: ellipsis; white-space: nowrap; flex-grow: 1; + + &.clickable { + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + text-decoration: underline; + color: var(--text-base); + } + } } [data-slot="apply-patch-deletion-count"] { @@ -833,4 +864,13 @@ flex-shrink: 0; color: var(--icon-weak); } + + &.clickable { + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + color: var(--text-base); + } + } } diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 42dc37abc5c..f50595b1a0e 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -682,11 +682,26 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { setTimeout(() => setCopied(false), 2000) } + const handleMarkdownClick = (e: MouseEvent) => { + if (!data.openFile) return + const target = e.target + if (!(target instanceof HTMLElement)) return + const link = target.closest(".file-link[data-file-path]") + if (!link) return + const path = link.getAttribute("data-file-path") + if (!path) return + const lineAttr = link.getAttribute("data-file-line") + const colAttr = link.getAttribute("data-file-col") + const line = lineAttr ? parseInt(lineAttr, 10) : undefined + const column = colAttr ? parseInt(colAttr, 10) : undefined + data.openFile(path, line, column) + } + return (
- +
data.openFile!(props.input.filePath) : undefined + } /> {(filepath) => ( -
+
data.openFile?.(filepath)} + > {i18n.t("ui.tool.loaded")} {relativizeProjectPaths(filepath, data.directory)} @@ -1106,10 +1128,16 @@ ToolRegistry.register({ ToolRegistry.register({ name: "edit", render(props) { + const data = useData() const i18n = useI18n() const diffComponent = useDiffComponent() const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const filename = () => getFilename(props.input.filePath ?? "") + const handleFileClick = (e: MouseEvent) => { + if (!data.openFile || !props.input.filePath) return + e.stopPropagation() + data.openFile(props.input.filePath) + } return (
{i18n.t("ui.messagePart.title.edit")} - {filename()} + + {filename()} +
- {getDirectory(props.input.filePath!)} + + {getDirectory(props.input.filePath!)} +
@@ -1159,10 +1199,16 @@ ToolRegistry.register({ ToolRegistry.register({ name: "write", render(props) { + const data = useData() const i18n = useI18n() const codeComponent = useCodeComponent() const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const filename = () => getFilename(props.input.filePath ?? "") + const handleFileClick = (e: MouseEvent) => { + if (!data.openFile || !props.input.filePath) return + e.stopPropagation() + data.openFile(props.input.filePath) + } return (
{i18n.t("ui.messagePart.title.write")} - {filename()} + + {filename()} +
- {getDirectory(props.input.filePath!)} + + {getDirectory(props.input.filePath!)} +
@@ -1218,6 +1276,7 @@ interface ApplyPatchFile { ToolRegistry.register({ name: "apply_patch", render(props) { + const data = useData() const i18n = useI18n() const diffComponent = useDiffComponent() const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[]) @@ -1265,7 +1324,17 @@ ToolRegistry.register({ - {file.relativePath} + { + if (!data.openFile) return + e.stopPropagation() + data.openFile(file.filePath) + }} + > + {file.relativePath} + diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx index 137ea05656c..815c2d3d4a7 100644 --- a/packages/ui/src/context/data.tsx +++ b/packages/ui/src/context/data.tsx @@ -52,6 +52,8 @@ export type SessionHrefFn = (sessionID: string) => string export type SyncSessionFn = (sessionID: string) => void | Promise +export type OpenFileFn = (filePath: string, line?: number, column?: number) => void + export const { use: useData, provider: DataProvider } = createSimpleContext({ name: "Data", init: (props: { @@ -63,6 +65,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ onNavigateToSession?: NavigateToSessionFn onSessionHref?: SessionHrefFn onSyncSession?: SyncSessionFn + onOpenFile?: OpenFileFn }) => { return { get store() { @@ -77,6 +80,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ navigateToSession: props.onNavigateToSession, sessionHref: props.onSessionHref, syncSession: props.onSyncSession, + openFile: props.onOpenFile, } }, }) diff --git a/packages/ui/src/context/marked.tsx b/packages/ui/src/context/marked.tsx index 1ea1999f8de..a2697892b2f 100644 --- a/packages/ui/src/context/marked.tsx +++ b/packages/ui/src/context/marked.tsx @@ -461,6 +461,24 @@ async function highlightCodeBlocks(html: string): Promise { export type NativeMarkdownParser = (markdown: string) => Promise +// Matches text that looks like a file path: contains "/" and ends with a file extension, +// or starts with "./" or "../" or "/". Supports optional :line or :line:col suffix. +const FILE_PATH_RE = + /^((?:\/|\.\.?\/)?(?:[a-zA-Z0-9_@-][a-zA-Z0-9_@./-]*\/)*[a-zA-Z0-9_@.-]+\.[a-zA-Z0-9]+)(?::(\d+)(?::(\d+))?)?$/ + +function parseFilePath(text: string): { path: string; line?: number; column?: number } | undefined { + if (text.includes("://")) return undefined + if (text.includes(" ")) return undefined + const match = FILE_PATH_RE.exec(text) + if (!match) return undefined + if (!match[1].includes("/")) return undefined + return { + path: match[1], + line: match[2] ? parseInt(match[2], 10) : undefined, + column: match[3] ? parseInt(match[3], 10) : undefined, + } +} + export const { use: useMarked, provider: MarkedProvider } = createSimpleContext({ name: "Marked", init: (props: { nativeParser?: NativeMarkdownParser }) => { @@ -471,6 +489,15 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext( const titleAttr = title ? ` title="${title}"` : "" return `${text}` }, + codespan({ text }) { + const file = parseFilePath(text) + if (file) { + const lineAttr = file.line ? ` data-file-line="${file.line}"` : "" + const colAttr = file.column ? ` data-file-col="${file.column}"` : "" + return `${text}` + } + return `${text}` + }, }, }, markedKatex({ From 42e70cbdef1901350808ec04ac5a729496ea3e4a Mon Sep 17 00:00:00 2001 From: Olusammytee Date: Thu, 19 Feb 2026 09:44:20 -0500 Subject: [PATCH 06/47] docs: clarify YAML-first custom modes behavior --- packages/kilo-docs/pages/customize/custom-modes.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/kilo-docs/pages/customize/custom-modes.md b/packages/kilo-docs/pages/customize/custom-modes.md index a7fdda9fd1a..86bcc17a2c4 100644 --- a/packages/kilo-docs/pages/customize/custom-modes.md +++ b/packages/kilo-docs/pages/customize/custom-modes.md @@ -74,7 +74,7 @@ Modes are managed from the Modes area in Kilo Code. Depending on your UI layout, 1. Open the Modes area from the mode selector in the chat panel (or via the icon if shown) 2. Click the Import Mode button (upload icon) -3. Select the mode's YAML file +3. Select the mode's YAML file (`.yaml`) 4. Choose the import level: - **Project:** Available only in current workspace (saved to `.kilocodemodes` file) - **Global:** Available in all projects (saved to global settings) @@ -121,11 +121,16 @@ The interface provides fields for Name, Slug, Description, Save Location, Role D You can directly edit the configuration files to create or modify custom modes. This method offers the most control over all properties. Kilo Code now supports both YAML (preferred) and JSON formats. -- **Global Modes:** Edit the `custom_modes.yaml` (preferred) or `custom_modes.json` file. Open the Modes area, click next to Global Modes, then choose "Edit Global Modes" -- **Project Modes:** Edit the `.kilocodemodes` file (which can be YAML or JSON) in your project root. Open the Modes area, click next to Project Modes, then choose "Edit Project Modes" +- **Global Modes:** Edit `custom_modes.yaml` (primary). `custom_modes.json` is a legacy fallback and may still exist in older setups. +- **Project Modes:** Edit `.kilocodemodes` in your project root (YAML preferred; JSON still supported for compatibility). +- **Open from UI:** Open the Modes area, click next to Global or Project Modes, then choose **Edit Global Modes** or **Edit Project Modes**. These files define an array/list of custom modes. +{% callout type="info" title="Why JSON Files May Still Exist" %} +If you see both YAML and JSON mode files, this is usually from legacy configuration. Kilo Code reads YAML first and does not keep both files synchronized line-by-line. In practice, edit YAML unless you have a specific reason to stay on JSON. +{% /callout %} + ## YAML Configuration Format (Preferred) YAML is now the preferred format for defining custom modes due to better readability, comment support, and cleaner multi-line strings. From cf6997c109f9bad8345b124ef89adfd20b27ae89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Fri, 20 Feb 2026 12:19:06 +0100 Subject: [PATCH 07/47] chore: add kilocode_change markers to PR #479 changes in packages/ui --- packages/ui/src/components/markdown.css | 2 ++ packages/ui/src/components/message-part.css | 10 ++++++- packages/ui/src/components/message-part.tsx | 30 ++++++++++++++++----- packages/ui/src/context/data.tsx | 6 ++--- packages/ui/src/context/marked.tsx | 4 +++ 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css index 1a5c709d659..53ae7d2b5ac 100644 --- a/packages/ui/src/components/markdown.css +++ b/packages/ui/src/components/markdown.css @@ -174,6 +174,7 @@ /* background: var(--surface-base); */ /* box-shadow: 0 0 0 0.5px var(--border-weak-base); */ + /* kilocode_change start */ &.file-link { cursor: pointer; text-decoration-line: underline; @@ -186,6 +187,7 @@ text-decoration-style: solid; } } + /* kilocode_change end */ } /* Tables */ diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index c1bf0a263bf..c18f7a72637 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -34,7 +34,7 @@ overflow: hidden; background: var(--surface-weak); border: 1px solid var(--border-weak-base); - cursor: pointer; + cursor: pointer; /* kilocode_change */ transition: border-color 0.15s ease; &:hover { @@ -276,6 +276,7 @@ [data-slot="message-part-title-filename"] { /* No text-transform - preserve original filename casing */ + /* kilocode_change start */ &.clickable { cursor: pointer; transition: color 0.15s ease; @@ -285,6 +286,7 @@ color: var(--text-base); } } + /* kilocode_change end */ } [data-slot="message-part-path"] { @@ -301,6 +303,7 @@ direction: rtl; text-align: left; + /* kilocode_change start */ &.clickable { cursor: pointer; transition: color 0.15s ease; @@ -310,6 +313,7 @@ color: var(--text-base); } } + /* kilocode_change end */ } [data-slot="message-part-filename"] { @@ -819,6 +823,7 @@ white-space: nowrap; flex-grow: 1; + /* kilocode_change start */ &.clickable { cursor: pointer; transition: color 0.15s ease; @@ -828,6 +833,7 @@ color: var(--text-base); } } + /* kilocode_change end */ } [data-slot="apply-patch-deletion-count"] { @@ -865,6 +871,7 @@ color: var(--icon-weak); } + /* kilocode_change start */ &.clickable { cursor: pointer; transition: color 0.15s ease; @@ -873,4 +880,5 @@ color: var(--text-base); } } + /* kilocode_change end */ } diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index f50595b1a0e..3cd29c611fe 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -682,6 +682,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { setTimeout(() => setCopied(false), 2000) } + // kilocode_change start const handleMarkdownClick = (e: MouseEvent) => { if (!data.openFile) return const target = e.target @@ -696,12 +697,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { const column = colAttr ? parseInt(colAttr, 10) : undefined data.openFile(path, line, column) } + // kilocode_change end return (
- + {/* kilocode_change */}
data.openFile!(props.input.filePath) : undefined } + // kilocode_change end /> {(filepath) => (
data.openFile?.(filepath)} + classList={{ clickable: !!data.openFile }} // kilocode_change + onClick={() => data.openFile?.(filepath)} // kilocode_change > @@ -1128,16 +1132,18 @@ ToolRegistry.register({ ToolRegistry.register({ name: "edit", render(props) { - const data = useData() + const data = useData() // kilocode_change const i18n = useI18n() const diffComponent = useDiffComponent() const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const filename = () => getFilename(props.input.filePath ?? "") + // kilocode_change start const handleFileClick = (e: MouseEvent) => { if (!data.openFile || !props.input.filePath) return e.stopPropagation() data.openFile(props.input.filePath) } + // kilocode_change end return (
{i18n.t("ui.messagePart.title.edit")} + {/* kilocode_change start */} {filename()} + {/* kilocode_change end */}
+ {/* kilocode_change start */} {getDirectory(props.input.filePath!)} + {/* kilocode_change end */}
@@ -1199,16 +1209,18 @@ ToolRegistry.register({ ToolRegistry.register({ name: "write", render(props) { - const data = useData() + const data = useData() // kilocode_change const i18n = useI18n() const codeComponent = useCodeComponent() const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const filename = () => getFilename(props.input.filePath ?? "") + // kilocode_change start const handleFileClick = (e: MouseEvent) => { if (!data.openFile || !props.input.filePath) return e.stopPropagation() data.openFile(props.input.filePath) } + // kilocode_change end return (
{i18n.t("ui.messagePart.title.write")} + {/* kilocode_change start */} {filename()} + {/* kilocode_change end */}
+ {/* kilocode_change start */} {getDirectory(props.input.filePath!)} + {/* kilocode_change end */}
@@ -1276,7 +1292,7 @@ interface ApplyPatchFile { ToolRegistry.register({ name: "apply_patch", render(props) { - const data = useData() + const data = useData() // kilocode_change const i18n = useI18n() const diffComponent = useDiffComponent() const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[]) @@ -1324,6 +1340,7 @@ ToolRegistry.register({ + {/* kilocode_change start */} {file.relativePath} + {/* kilocode_change end */} diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx index 815c2d3d4a7..26dcc2a7593 100644 --- a/packages/ui/src/context/data.tsx +++ b/packages/ui/src/context/data.tsx @@ -52,7 +52,7 @@ export type SessionHrefFn = (sessionID: string) => string export type SyncSessionFn = (sessionID: string) => void | Promise -export type OpenFileFn = (filePath: string, line?: number, column?: number) => void +export type OpenFileFn = (filePath: string, line?: number, column?: number) => void // kilocode_change export const { use: useData, provider: DataProvider } = createSimpleContext({ name: "Data", @@ -65,7 +65,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ onNavigateToSession?: NavigateToSessionFn onSessionHref?: SessionHrefFn onSyncSession?: SyncSessionFn - onOpenFile?: OpenFileFn + onOpenFile?: OpenFileFn // kilocode_change }) => { return { get store() { @@ -80,7 +80,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ navigateToSession: props.onNavigateToSession, sessionHref: props.onSessionHref, syncSession: props.onSyncSession, - openFile: props.onOpenFile, + openFile: props.onOpenFile, // kilocode_change } }, }) diff --git a/packages/ui/src/context/marked.tsx b/packages/ui/src/context/marked.tsx index a2697892b2f..315cf55aab0 100644 --- a/packages/ui/src/context/marked.tsx +++ b/packages/ui/src/context/marked.tsx @@ -461,6 +461,7 @@ async function highlightCodeBlocks(html: string): Promise { export type NativeMarkdownParser = (markdown: string) => Promise +// kilocode_change start // Matches text that looks like a file path: contains "/" and ends with a file extension, // or starts with "./" or "../" or "/". Supports optional :line or :line:col suffix. const FILE_PATH_RE = @@ -478,6 +479,7 @@ function parseFilePath(text: string): { path: string; line?: number; column?: nu column: match[3] ? parseInt(match[3], 10) : undefined, } } +// kilocode_change end export const { use: useMarked, provider: MarkedProvider } = createSimpleContext({ name: "Marked", @@ -489,6 +491,7 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext( const titleAttr = title ? ` title="${title}"` : "" return `${text}` }, + // kilocode_change start codespan({ text }) { const file = parseFilePath(text) if (file) { @@ -498,6 +501,7 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext( } return `${text}` }, + // kilocode_change end }, }, markedKatex({ From d4231c20e303b91cc5606aacb31ae42c1f27855b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 20 Feb 2026 13:49:51 +0200 Subject: [PATCH 08/47] fix: Close sub-agents, preventing them from becoming orphan processes --- packages/opencode/src/cli/cmd/serve.ts | 15 +++++++++++++-- packages/opencode/src/cli/cmd/tui/thread.ts | 7 +++++++ packages/opencode/src/cli/cmd/web.ts | 15 +++++++++++++-- packages/opencode/src/index.ts | 5 ++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 54189753e92..90c8a1d37af 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -2,6 +2,7 @@ import { Server } from "../../server/server" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "../../flag/flag" +import { Instance } from "../../project/instance" // kilocode_change export const ServeCommand = cmd({ command: "serve", @@ -14,7 +15,17 @@ export const ServeCommand = cmd({ const opts = await resolveNetworkOptions(args) const server = Server.listen(opts) console.log(`kilo server listening on http://${server.hostname}:${server.port}`) // kilocode_change - await new Promise(() => {}) - await server.stop() + // kilocode_change start - graceful signal shutdown + const abort = new AbortController() + const shutdown = async () => { + await Instance.disposeAll() + await server.stop(true) + abort.abort() + } + process.on("SIGTERM", shutdown) + process.on("SIGINT", shutdown) + process.on("SIGHUP", shutdown) + await new Promise((resolve) => abort.signal.addEventListener("abort", resolve)) + // kilocode_change end }, }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 31fc583f066..3b839f65865 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -127,6 +127,13 @@ export const TuiThreadCommand = cmd({ process.on("SIGUSR2", async () => { await client.call("reload", undefined) }) + // kilocode_change start - graceful shutdown on external signals + const shutdown = async () => { + await client.call("shutdown", undefined).catch(() => {}) + } + process.on("SIGHUP", shutdown) + process.on("SIGTERM", shutdown) + // kilocode_change end const prompt = await iife(async () => { const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 76841293277..21493f4c04a 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -3,6 +3,7 @@ import { UI } from "../ui" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "../../flag/flag" +import { Instance } from "../../project/instance" // kilocode_change import open from "open" import { networkInterfaces } from "os" @@ -75,7 +76,17 @@ export const WebCommand = cmd({ open(displayUrl).catch(() => {}) } - await new Promise(() => {}) - await server.stop() + // kilocode_change start - graceful signal shutdown + const abort = new AbortController() + const shutdown = async () => { + await Instance.disposeAll() + await server.stop(true) + abort.abort() + } + process.on("SIGTERM", shutdown) + process.on("SIGINT", shutdown) + process.on("SIGHUP", shutdown) + await new Promise((resolve) => abort.signal.addEventListener("abort", resolve)) + // kilocode_change end }, }) diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index cc79306bc26..737c0d6f72a 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -26,8 +26,9 @@ import { EOL } from "os" import { WebCommand } from "./cli/cmd/web" import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" -// kilocode_change start - Import telemetry and legacy migration +// kilocode_change start - Import telemetry, instance disposal, and legacy migration import { Telemetry } from "@kilocode/kilo-telemetry" +import { Instance } from "./project/instance" // kilocode_change import { migrateLegacyKiloAuth, ENV_FEATURE } from "@kilocode/kilo-gateway" // kilocode_change - set feature for tracking. 'serve' is spawned by other services @@ -197,6 +198,8 @@ try { await Telemetry.shutdown() // kilocode_change end + await Instance.disposeAll() // kilocode_change - safety net disposal (no-op if already disposed) + // Some subprocesses don't react properly to SIGTERM and similar signals. // Most notably, some docker-container-based MCP servers don't handle such signals unless // run using `docker run --init`. From 43ae6b242557fe0c4fc40302c3e05a6479adaf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Fri, 20 Feb 2026 15:27:42 +0100 Subject: [PATCH 09/47] feat(vscode): add session-lifetime hint to permission auto-approve dialog Clarifies that 'Allow always' only applies for the duration of the current session, not globally. Adds translated hint text below the permission action buttons in all supported locales. --- .../webview-ui/src/components/chat/ChatView.tsx | 1 + packages/ui/src/components/message-part.css | 7 +++++++ packages/ui/src/i18n/ar.ts | 1 + packages/ui/src/i18n/br.ts | 2 ++ packages/ui/src/i18n/bs.ts | 2 ++ packages/ui/src/i18n/da.ts | 2 ++ packages/ui/src/i18n/de.ts | 2 ++ packages/ui/src/i18n/en.ts | 1 + packages/ui/src/i18n/es.ts | 2 ++ packages/ui/src/i18n/fr.ts | 2 ++ packages/ui/src/i18n/ja.ts | 2 ++ packages/ui/src/i18n/ko.ts | 1 + packages/ui/src/i18n/no.ts | 2 ++ packages/ui/src/i18n/pl.ts | 2 ++ packages/ui/src/i18n/ru.ts | 2 ++ packages/ui/src/i18n/th.ts | 1 + packages/ui/src/i18n/zh.ts | 1 + packages/ui/src/i18n/zht.ts | 1 + 18 files changed, 34 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 355e77a4772..fce02449996 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -99,6 +99,7 @@ export const ChatView: Component = (props) => { {language.t("ui.permission.allowOnce")}
+

{language.t("ui.permission.sessionHint")}

)} diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 44db4f9aa52..6cf9bdc0254 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -554,6 +554,13 @@ gap: 8px; justify-content: flex-end; } + + [data-slot="permission-hint"] { + font-size: 11px; + color: var(--text-dimmed-base); + margin: 4px 0 0 0; + text-align: right; + } } [data-component="question-prompt"] { diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 7ee17e2e010..36030998bfe 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -85,6 +85,7 @@ export const dict = { "ui.permission.deny": "رفض", "ui.permission.allowAlways": "السماح دائمًا", "ui.permission.allowOnce": "السماح مرة واحدة", + "ui.permission.sessionHint": '"السماح دائمًا" ينطبق على هذه الجلسة فقط. استخدم الإعدادات للأذونات العامة.', "ui.message.expand": "توسيع الرسالة", "ui.message.collapse": "طي الرسالة", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 6d7449d8457..36679c5c716 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -85,6 +85,8 @@ export const dict = { "ui.permission.deny": "Negar", "ui.permission.allowAlways": "Permitir sempre", "ui.permission.allowOnce": "Permitir uma vez", + "ui.permission.sessionHint": + '"Sempre permitir" aplica-se apenas a esta sessão. Use as configurações para permissões globais.', "ui.message.expand": "Expandir mensagem", "ui.message.collapse": "Recolher mensagem", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 24e4c12068e..6bdae375d0b 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -89,6 +89,8 @@ export const dict = { "ui.permission.deny": "Zabrani", "ui.permission.allowAlways": "Uvijek dozvoli", "ui.permission.allowOnce": "Dozvoli jednom", + "ui.permission.sessionHint": + '"Uvijek dozvoli" primjenjuje se samo na ovu sesiju. Koristite postavke za globalne dozvole.', "ui.message.expand": "Proširi poruku", "ui.message.collapse": "Sažmi poruku", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 218f3b26a49..799249e8a8f 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -84,6 +84,8 @@ export const dict = { "ui.permission.deny": "Afvis", "ui.permission.allowAlways": "Tillad altid", "ui.permission.allowOnce": "Tillad én gang", + "ui.permission.sessionHint": + '"Tillad altid" gælder kun for denne session. Brug indstillinger til globale tilladelser.', "ui.message.expand": "Udvid besked", "ui.message.collapse": "Skjul besked", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 921a12c9967..ef03895b382 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -88,6 +88,8 @@ export const dict = { "ui.permission.deny": "Verweigern", "ui.permission.allowAlways": "Immer erlauben", "ui.permission.allowOnce": "Einmal erlauben", + "ui.permission.sessionHint": + '"Immer erlauben" gilt nur für diese Sitzung. Globale Berechtigungen in den Einstellungen ändern.', "ui.message.expand": "Nachricht erweitern", "ui.message.collapse": "Nachricht reduzieren", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 631bc660a65..d44d1d3d87c 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -85,6 +85,7 @@ export const dict = { "ui.permission.deny": "Deny", "ui.permission.allowAlways": "Allow always", "ui.permission.allowOnce": "Allow once", + "ui.permission.sessionHint": '"Allow always" applies to this session only. Use settings for global permissions.', "ui.message.expand": "Expand message", "ui.message.collapse": "Collapse message", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 4fd921b606b..67f1e63ac85 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -85,6 +85,8 @@ export const dict = { "ui.permission.deny": "Denegar", "ui.permission.allowAlways": "Permitir siempre", "ui.permission.allowOnce": "Permitir una vez", + "ui.permission.sessionHint": + '"Permitir siempre" se aplica solo a esta sesión. Usa la configuración para permisos globales.', "ui.message.expand": "Expandir mensaje", "ui.message.collapse": "Colapsar mensaje", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 537d01bba94..72b9f15ffd7 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -85,6 +85,8 @@ export const dict = { "ui.permission.deny": "Refuser", "ui.permission.allowAlways": "Toujours autoriser", "ui.permission.allowOnce": "Autoriser une fois", + "ui.permission.sessionHint": + '"Toujours autoriser" s\'applique uniquement à cette session. Utilisez les paramètres pour les autorisations globales.', "ui.message.expand": "Développer le message", "ui.message.collapse": "Réduire le message", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 6086070bdb2..a0fef4500a2 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -84,6 +84,8 @@ export const dict = { "ui.permission.deny": "拒否", "ui.permission.allowAlways": "常に許可", "ui.permission.allowOnce": "今回のみ許可", + "ui.permission.sessionHint": + "「常に許可」はこのセッションにのみ適用されます。グローバルな権限は設定で変更してください。", "ui.message.expand": "メッセージを展開", "ui.message.collapse": "メッセージを折りたたむ", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index fd394dbb7b5..6d37e883b11 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -85,6 +85,7 @@ export const dict = { "ui.permission.deny": "거부", "ui.permission.allowAlways": "항상 허용", "ui.permission.allowOnce": "한 번만 허용", + "ui.permission.sessionHint": '"항상 허용"은 현재 세션에만 적용됩니다. 전역 권한은 설정에서 변경하세요.', "ui.message.expand": "메시지 펼치기", "ui.message.collapse": "메시지 접기", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index dcb353614d3..b8cc230d46a 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -88,6 +88,8 @@ export const dict: Record = { "ui.permission.deny": "Avslå", "ui.permission.allowAlways": "Tillat alltid", "ui.permission.allowOnce": "Tillat én gang", + "ui.permission.sessionHint": + '"Tillat alltid" gjelder bare for denne økten. Bruk innstillinger for globale tillatelser.', "ui.message.expand": "Utvid melding", "ui.message.collapse": "Skjul melding", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index fb10debbb92..c9f43daada0 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -84,6 +84,8 @@ export const dict = { "ui.permission.deny": "Odmów", "ui.permission.allowAlways": "Zezwalaj zawsze", "ui.permission.allowOnce": "Zezwól raz", + "ui.permission.sessionHint": + '"Zawsze zezwalaj" dotyczy tylko tej sesji. Użyj ustawień, aby zmienić globalne uprawnienia.', "ui.message.expand": "Rozwiń wiadomość", "ui.message.collapse": "Zwiń wiadomość", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 417fe0ce8bf..144cbee1fd4 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -84,6 +84,8 @@ export const dict = { "ui.permission.deny": "Запретить", "ui.permission.allowAlways": "Разрешить всегда", "ui.permission.allowOnce": "Разрешить один раз", + "ui.permission.sessionHint": + "«Всегда разрешать» применяется только к текущей сессии. Для глобальных разрешений используйте настройки.", "ui.message.expand": "Развернуть сообщение", "ui.message.collapse": "Свернуть сообщение", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 68bb0d733d9..09f529c25a0 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -85,6 +85,7 @@ export const dict = { "ui.permission.deny": "ปฏิเสธ", "ui.permission.allowAlways": "อนุญาตเสมอ", "ui.permission.allowOnce": "อนุญาตครั้งเดียว", + "ui.permission.sessionHint": '"อนุญาตเสมอ" ใช้ได้เฉพาะในเซสชันนี้เท่านั้น ใช้การตั้งค่าสำหรับสิทธิ์ส่วนกลาง', "ui.message.expand": "ขยายข้อความ", "ui.message.collapse": "ย่อข้อความ", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 53beeb1e4f0..fe572cb5d3f 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -89,6 +89,7 @@ export const dict = { "ui.permission.deny": "拒绝", "ui.permission.allowAlways": "始终允许", "ui.permission.allowOnce": "允许一次", + "ui.permission.sessionHint": '"始终允许" 仅适用于本次会话。如需全局权限设置,请使用设置。', "ui.message.expand": "展开消息", "ui.message.collapse": "收起消息", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 1449b0530ac..9fa7cd0b332 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -89,6 +89,7 @@ export const dict = { "ui.permission.deny": "拒絕", "ui.permission.allowAlways": "永遠允許", "ui.permission.allowOnce": "允許一次", + "ui.permission.sessionHint": '"一律允許" 僅適用於此工作階段。如需全域權限設定,請使用設定。', "ui.message.expand": "展開訊息", "ui.message.collapse": "收合訊息", From 776402da138bd06ee7418f8a39fc551281dad310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Fri, 20 Feb 2026 15:38:47 +0100 Subject: [PATCH 10/47] fix: correct translation mismatches and add hint to message-part.tsx permission prompts - Fix quoted button label in br, pl, ru, zht to match their allowAlways values - Add permission-hint to both permission prompt locations in message-part.tsx --- packages/ui/src/components/message-part.tsx | 2 ++ packages/ui/src/i18n/br.ts | 2 +- packages/ui/src/i18n/pl.ts | 2 +- packages/ui/src/i18n/ru.ts | 2 +- packages/ui/src/i18n/zht.ts | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 42dc37abc5c..84995f3cd94 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -659,6 +659,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { {i18n.t("ui.permission.allowOnce")}
+

{i18n.t("ui.permission.sessionHint")}

{(request) => } @@ -1038,6 +1039,7 @@ ToolRegistry.register({ {i18n.t("ui.permission.allowOnce")} +

{i18n.t("ui.permission.sessionHint")}

diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 36679c5c716..3092c9735bf 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -86,7 +86,7 @@ export const dict = { "ui.permission.allowAlways": "Permitir sempre", "ui.permission.allowOnce": "Permitir uma vez", "ui.permission.sessionHint": - '"Sempre permitir" aplica-se apenas a esta sessão. Use as configurações para permissões globais.', + '"Permitir sempre" aplica-se apenas a esta sessão. Use as configurações para permissões globais.', "ui.message.expand": "Expandir mensagem", "ui.message.collapse": "Recolher mensagem", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index c9f43daada0..79a818f44e3 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -85,7 +85,7 @@ export const dict = { "ui.permission.allowAlways": "Zezwalaj zawsze", "ui.permission.allowOnce": "Zezwól raz", "ui.permission.sessionHint": - '"Zawsze zezwalaj" dotyczy tylko tej sesji. Użyj ustawień, aby zmienić globalne uprawnienia.', + '"Zezwalaj zawsze" dotyczy tylko tej sesji. Użyj ustawień, aby zmienić globalne uprawnienia.', "ui.message.expand": "Rozwiń wiadomość", "ui.message.collapse": "Zwiń wiadomość", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 144cbee1fd4..fbec348d3c8 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -85,7 +85,7 @@ export const dict = { "ui.permission.allowAlways": "Разрешить всегда", "ui.permission.allowOnce": "Разрешить один раз", "ui.permission.sessionHint": - "«Всегда разрешать» применяется только к текущей сессии. Для глобальных разрешений используйте настройки.", + "«Разрешить всегда» применяется только к текущей сессии. Для глобальных разрешений используйте настройки.", "ui.message.expand": "Развернуть сообщение", "ui.message.collapse": "Свернуть сообщение", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 9fa7cd0b332..dab0e48abd0 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -89,7 +89,7 @@ export const dict = { "ui.permission.deny": "拒絕", "ui.permission.allowAlways": "永遠允許", "ui.permission.allowOnce": "允許一次", - "ui.permission.sessionHint": '"一律允許" 僅適用於此工作階段。如需全域權限設定,請使用設定。', + "ui.permission.sessionHint": '"永遠允許" 僅適用於此工作階段。如需全域權限設定,請使用設定。', "ui.message.expand": "展開訊息", "ui.message.collapse": "收合訊息", From 00fa743efa782621abca7dafb7ec8a3bdae09013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Fri, 20 Feb 2026 15:43:51 +0100 Subject: [PATCH 11/47] fix(vscode): absolutely position notification bar to not push content down --- .../webview-ui/src/components/chat/ChatView.tsx | 12 +++++++----- .../kilo-vscode/webview-ui/src/styles/chat.css | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 355e77a4772..8307afa1e82 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -55,11 +55,13 @@ export const ChatView: Component = (props) => { return (
- - - -
- +
+ + + +
+ +
diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 121e38353bb..1ade18106bf 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -14,12 +14,19 @@ overflow: hidden; } -.chat-messages { +.chat-messages-wrapper { + position: relative; flex: 1; overflow: hidden; min-height: 0; } +.chat-messages { + height: 100%; + overflow: hidden; + min-height: 0; +} + .chat-input { flex-shrink: 0; border-top: 1px solid var(--vscode-panel-border); @@ -706,8 +713,11 @@ ============================================ */ .kilo-notifications { - flex-shrink: 0; + position: absolute; + top: 0; + left: 0; width: 100%; + z-index: 10; display: flex; flex-direction: column; border-bottom: 1px solid var(--vscode-panel-border); From 7f94b83893e40b89dc59f3632e24feb3d177675d Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 20 Feb 2026 17:38:35 +0200 Subject: [PATCH 12/47] fix: ensure abort fires even if shutdown throws in serve.ts Wrap Instance.disposeAll() and server.stop() in try/finally so abort.abort() always fires, preventing the process from hanging if either call rejects. --- packages/opencode/src/cli/cmd/serve.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 90c8a1d37af..5b2a867e99f 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -18,9 +18,12 @@ export const ServeCommand = cmd({ // kilocode_change start - graceful signal shutdown const abort = new AbortController() const shutdown = async () => { - await Instance.disposeAll() - await server.stop(true) - abort.abort() + try { + await Instance.disposeAll() + await server.stop(true) + } finally { + abort.abort() + } } process.on("SIGTERM", shutdown) process.on("SIGINT", shutdown) From a514530139675a2606d3571d68877ad0757046f5 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 20 Feb 2026 17:38:40 +0200 Subject: [PATCH 13/47] fix: ensure abort fires even if shutdown throws in web.ts Wrap Instance.disposeAll() and server.stop() in try/finally so abort.abort() always fires, preventing the process from hanging if either call rejects. --- packages/opencode/src/cli/cmd/web.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 21493f4c04a..ffd9395dc54 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -79,9 +79,12 @@ export const WebCommand = cmd({ // kilocode_change start - graceful signal shutdown const abort = new AbortController() const shutdown = async () => { - await Instance.disposeAll() - await server.stop(true) - abort.abort() + try { + await Instance.disposeAll() + await server.stop(true) + } finally { + abort.abort() + } } process.on("SIGTERM", shutdown) process.on("SIGINT", shutdown) From 57ea8f11853d59e376eff56eb4ba8803932662d2 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 20 Feb 2026 16:39:06 +0100 Subject: [PATCH 14/47] fix(agent-manager): show "not a git repository" instead of infinite loading spinner (#529) When the Agent Manager opens in a non-git folder, initializeState() returned early without pushing state to the webview, leaving the skeleton loader spinning forever. Now pushes empty state with isGitRepo: false so the UI can display a clear message and hide worktree action buttons. --- .../src/agent-manager/AgentManagerProvider.ts | 16 +- .../agent-manager/AgentManagerApp.tsx | 302 +++++++++--------- .../agent-manager/agent-manager.css | 11 + .../webview-ui/src/types/messages.ts | 1 + 4 files changed, 184 insertions(+), 146 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 584d048874d..c1eba76d2a6 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -94,7 +94,10 @@ export class AgentManagerProvider implements vscode.Disposable { private async initializeState(): Promise { const manager = this.getWorktreeManager() const state = this.getStateManager() - if (!manager || !state) return + if (!manager || !state) { + this.pushEmptyState() + return + } await state.load() @@ -655,6 +658,17 @@ export class AgentManagerProvider implements vscode.Disposable { sessions: state.getSessions(), tabOrder: state.getTabOrder(), sessionsCollapsed: state.getSessionsCollapsed(), + isGitRepo: true, + }) + } + + /** Push empty state when the workspace is not a git repo or has no workspace folder. */ + private pushEmptyState(): void { + this.postToWebview({ + type: "agentManager.state", + worktrees: [], + sessions: [], + isGitRepo: false, }) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 9bf340d7041..a6da1cacb8d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -236,6 +236,7 @@ const AgentManagerContent: Component = () => { const [loadingWorktrees, setLoadingWorktrees] = createSignal>(new Set()) const [worktreesLoaded, setWorktreesLoaded] = createSignal(false) const [sessionsLoaded, setSessionsLoaded] = createSignal(false) + const [isGitRepo, setIsGitRepo] = createSignal(true) const DEFAULT_SIDEBAR_WIDTH = 260 const MIN_SIDEBAR_WIDTH = 200 @@ -620,6 +621,7 @@ const AgentManagerContent: Component = () => { const state = msg as AgentManagerStateMessage setWorktrees(state.worktrees) setManagedSessions(state.sessions) + if (state.isGitRepo !== undefined) setIsGitRepo(state.isGitRepo) if (!worktreesLoaded()) setWorktreesLoaded(true) if (state.tabOrder) setWorktreeTabOrder(state.tabOrder) const current = session.currentSessionID() @@ -1004,54 +1006,56 @@ const AgentManagerContent: Component = () => {
-
- - - - - - Keyboard Shortcuts - - - - Worktree Setup Script - - - - -
- - - - - + +
+ + - - - New Worktree + + + Keyboard Shortcuts - - - New with Versions... + + Worktree Setup Script +
+ + + + + + + + + New Worktree + + + + + New with Versions... + + + + +
-
+
{
} > - {(() => { - const [hoveredWt, setHoveredWt] = createSignal(null) - const [overClose, setOverClose] = createSignal(false) - return ( - - {(wt, idx) => { - const grouped = () => isGrouped(wt) - const start = () => isGroupStart(wt, idx()) - const end = () => isGroupEnd(wt, idx()) - const busy = () => deletingWorktrees().has(wt.id) || loadingWorktrees().has(wt.id) - const groupSize = () => { - if (!wt.groupId) return 0 - return sortedWorktrees().filter((w) => w.groupId === wt.groupId).length - } - const sessions = createMemo(() => managedSessions().filter((ms) => ms.worktreeId === wt.id)) - const navHint = () => { - const flat = [ - LOCAL as string, - ...sortedWorktrees().map((w) => w.id), - ...unassignedSessions().map((s) => s.id), - ] - const active = selection() ?? session.currentSessionID() ?? "" - return adjacentHint(wt.id, active, flat, kb().previousSession ?? "", kb().nextSession ?? "") - } - return ( - <> - -
- - {groupSize()} versions -
-
- setHoveredWt(open ? wt.id : null)} - trigger={ -
selectWorktree(wt.id)} - > - - {worktreeLabel(wt)} - }> -
setOverClose(true)} - onMouseLeave={() => setOverClose(false)} - > - +
+ + Not a git repository +
+ + + {(() => { + const [hoveredWt, setHoveredWt] = createSignal(null) + const [overClose, setOverClose] = createSignal(false) + return ( + + {(wt, idx) => { + const grouped = () => isGrouped(wt) + const start = () => isGroupStart(wt, idx()) + const end = () => isGroupEnd(wt, idx()) + const busy = () => deletingWorktrees().has(wt.id) || loadingWorktrees().has(wt.id) + const groupSize = () => { + if (!wt.groupId) return 0 + return sortedWorktrees().filter((w) => w.groupId === wt.groupId).length + } + const sessions = createMemo(() => managedSessions().filter((ms) => ms.worktreeId === wt.id)) + const navHint = () => { + const flat = [ + LOCAL as string, + ...sortedWorktrees().map((w) => w.id), + ...unassignedSessions().map((s) => s.id), + ] + const active = selection() ?? session.currentSessionID() ?? "" + return adjacentHint(wt.id, active, flat, kb().previousSession ?? "", kb().nextSession ?? "") + } + return ( + <> + +
+ + {groupSize()} versions +
+
+ setHoveredWt(open ? wt.id : null)} + trigger={ +
selectWorktree(wt.id)} + > + + {worktreeLabel(wt)} + }> +
setOverClose(true)} + onMouseLeave={() => setOverClose(false)} > - handleDeleteWorktree(wt.id, e)} - /> - + + handleDeleteWorktree(wt.id, e)} + /> + +
+
+
+ } + > +
+
+
+
BRANCH
+
{wt.branch}
+
{formatRelativeDate(wt.createdAt)}
+
+ + {navHint()} + +
+ +
+
+ Base + {wt.parentBranch}
-
- } - > -
-
-
-
BRANCH
-
{wt.branch}
-
{formatRelativeDate(wt.createdAt)}
-
- - {navHint()} - -
-
- Base - {wt.parentBranch} + Sessions + {sessions().length}
- -
-
- Sessions - {sessions().length}
-
- - - ) - }} - - ) - })()} - - + + + ) + }} + + ) + })()} + + +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index ce992839904..5e8f12e817e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -1027,6 +1027,17 @@ button.am-section-toggle:hover .am-section-label { } } +/* Not a git repo notice */ + +.am-not-git-notice { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + font-size: 12px; + color: var(--text-weak); +} + .am-skeleton-list { display: flex; flex-direction: column; diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index d1a2af31451..3fd765011d8 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -587,6 +587,7 @@ export interface AgentManagerStateMessage { sessions: ManagedSessionState[] tabOrder?: Record sessionsCollapsed?: boolean + isGitRepo?: boolean } // Resolved keybindings for agent manager actions From 3b7d0721f3b0e7c9842f6e6d9e4b365d9c9ccb59 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 20 Feb 2026 16:40:09 +0100 Subject: [PATCH 15/47] fix(agent-manager): reset stale body styles when webview regains focus (#530) Kobalte modal dropdowns set pointer-events:none and overflow:hidden on document.body. When the user opens the setup script editor from the settings dropdown, VS Code steals focus before the dropdown cleanup runs, leaving the body permanently unclickable. Clear these stale styles in the existing window focus handler so the UI recovers when the user returns to the agent manager tab. --- .../webview-ui/agent-manager/AgentManagerApp.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index a6da1cacb8d..cfa1b502cf6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -560,7 +560,14 @@ const AgentManagerContent: Component = () => { window.addEventListener("keydown", preventDefaults) // When the panel regains focus (e.g. returning from terminal), focus the prompt - const onWindowFocus = () => window.dispatchEvent(new Event("focusPrompt")) + // and clear any stale body styles left by Kobalte modal overlays (dropdowns/dialogs + // set pointer-events:none and overflow:hidden on body, but cleanup never runs if + // focus leaves the webview before the overlay closes). + const onWindowFocus = () => { + document.body.style.pointerEvents = "" + document.body.style.overflow = "" + window.dispatchEvent(new Event("focusPrompt")) + } window.addEventListener("focus", onWindowFocus) // When a session is created while on local, replace the current pending tab with the real session. From c520e4523a822a151dcb493242a487875a1b1f14 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 20 Feb 2026 16:48:50 +0100 Subject: [PATCH 16/47] fix(agent-manager): add am- prefix to prompt-input CSS classes (#535) The agent-manager-arch test enforces that all CSS class selectors in agent-manager.css use the am- prefix. The NewWorktreeDialog was reusing unprefixed prompt-input-* classes from the sidebar chat CSS, which violated this rule and broke the test-vscode CI pipeline. --- .../webview-ui/agent-manager/AgentManagerApp.tsx | 8 ++++---- .../webview-ui/agent-manager/agent-manager.css | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index cfa1b502cf6..d4c620ae30d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -1495,12 +1495,12 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
{/* Prompt input — reuses the same CSS as the sidebar chat input */} -
-
-
+
+
+