From d047224302041a5388f9ad1a04ce3a7f2e652581 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 13:56:04 +0200 Subject: [PATCH 1/6] fix(vscode): show approval reason for outside-workspace reads and writes --- .../show-outside-workspace-approval-reason.md | 5 +++ .../kilo-ui/src/components/message-part.tsx | 7 ++-- .../src/components/tool-approval.test.ts | 12 +++++++ .../kilo-ui/src/components/tool-approval.tsx | 7 ++++ .../kilo-vscode/webview-ui/src/i18n/en.ts | 1 + .../src/kilocode/permission/provenance.ts | 20 +++++++++-- packages/opencode/src/session/tools.ts | 23 +++++++----- .../kilocode/permission/provenance.test.ts | 36 +++++++++++++++++++ 8 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 .changeset/show-outside-workspace-approval-reason.md diff --git a/.changeset/show-outside-workspace-approval-reason.md b/.changeset/show-outside-workspace-approval-reason.md new file mode 100644 index 0000000000..1475ae34cd --- /dev/null +++ b/.changeset/show-outside-workspace-approval-reason.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show the permission approval reason for reads and writes outside the workspace, matching other tools diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 4281174d12..b22fed5b0a 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -48,7 +48,7 @@ import { checksum } from "@opencode-ai/core/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { TextShimmer } from "@opencode-ai/ui/text-shimmer" -import { ToolApprovalProvider, resolveToolApproval } from "./tool-approval" +import { ToolApprovalProvider, resolveToolApproval, useToolApproval } from "./tool-approval" export { ToolApprovalProvider, resolveToolApproval } from "./tool-approval" import { GrowBox } from "./grow-box" import { COLLAPSIBLE_SPRING } from "./motion" @@ -1888,10 +1888,13 @@ ToolRegistry.register({ const pending = createMemo(() => busy(props.status)) const images = createMemo(() => (props.attachments ?? []).filter((f) => f.mime.startsWith("image/") && f.url)) const preview = (url: string, alt?: string) => dialog.show(() => ) + // Read is high-frequency and low-risk, so details stay hidden unless the target was outside + // the workspace, in which case the approval reason explains what looks like an "agent escape". + const approval = useToolApproval() return ( <> { expect(out?.source).toBe("ui.approval.source.agent(agent=code)") expect(out?.rule).toBeUndefined() }) + + test("adds the outsideWorkspace text when the approval was marked outside the workspace", () => { + const approval = { source: "agent" as const, agent: "code", outsideWorkspace: true } + const out = resolveToolApproval({ approval }, t) + expect(out?.outsideWorkspace).toBe("ui.approval.outsideWorkspace") + }) + + test("omits the outsideWorkspace text for an ordinary in-workspace approval", () => { + const approval = { source: "agent" as const, agent: "code" } + const out = resolveToolApproval({ approval }, t) + expect(out?.outsideWorkspace).toBeUndefined() + }) }) diff --git a/packages/kilo-ui/src/components/tool-approval.tsx b/packages/kilo-ui/src/components/tool-approval.tsx index d555e105df..1bb2b9f759 100644 --- a/packages/kilo-ui/src/components/tool-approval.tsx +++ b/packages/kilo-ui/src/components/tool-approval.tsx @@ -11,6 +11,8 @@ export type ToolApproval = { source: "agent" | "global" | "project" | "yolo" | "session" | "manual" | "default" agent?: string rule?: { permission: string; pattern: string; action: string } + /** True when the tool call's target path was outside the workspace/worktree. */ + outsideWorkspace?: boolean } /** Pre-resolved, localized text plus the raw approval, supplied by the caller. */ @@ -19,6 +21,7 @@ export type ToolApprovalDisplay = { decision: string source?: string rule?: string + outsideWorkspace?: string } const SOURCE_KEYS = ["agent", "global", "project", "yolo", "session", "manual", "default"] as const @@ -71,6 +74,7 @@ export function resolveToolApproval( decision: approval.source === "manual" ? t("ui.approval.manual") : t("ui.approval.auto"), source: sourceText(), rule: ruleText, + outsideWorkspace: approval.outsideWorkspace ? t("ui.approval.outsideWorkspace") : undefined, } } @@ -84,6 +88,9 @@ export function ToolApprovalLine(props: { display: ToolApprovalDisplay }) { {(text) => {text()}} {(text) => {text()}} + + {(text) => {text()}} + ) } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 6a53383af8..7ba0c65aa8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -302,6 +302,7 @@ export const dict = { "ui.approval.source.yolo": "by auto-approve (YOLO) mode", "ui.approval.source.session": "by a session auto-approve rule", "ui.approval.source.default": "by default", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Review", "session.review.filesChanged": "{{count}} Files Changed", diff --git a/packages/opencode/src/kilocode/permission/provenance.ts b/packages/opencode/src/kilocode/permission/provenance.ts index 73f7fec4e0..896db47f1f 100644 --- a/packages/opencode/src/kilocode/permission/provenance.ts +++ b/packages/opencode/src/kilocode/permission/provenance.ts @@ -27,6 +27,13 @@ export namespace PermissionProvenance { agent?: string /** The winning rule, omitted for manual replies and the ask fallback. */ rule?: { permission: string; pattern: string; action: Permission.Action } + /** True when the ask's target path was outside the workspace/worktree (an `external_directory` ask). */ + outsideWorkspace?: boolean + } + + /** Tag an approval as outside-workspace when it answers an `external_directory` ask. */ + export function tagOutsideWorkspace(approval: Approval, permission: string): Approval { + return permission === "external_directory" ? { ...approval, outsideWorkspace: true } : approval } export type Scope = "global" | "local" @@ -71,13 +78,22 @@ export namespace PermissionProvenance { * The approval is written once during `ask()`, but tools freely overwrite `state.metadata` * during execution and on completion. Carry the prior `approval` onto the replacement unless * the replacement sets its own. + * + * A file tool that crosses the workspace boundary issues *two* asks for one call: the generic + * `external_directory` ask first, then its own `read`/`write`/`edit` ask. Both write `approval` + * metadata, so the second ask's `outsideWorkspace` marker would otherwise clobber the first's + * even though `"approval" in next` is true. Merge that marker forward so it survives. */ export function carryApproval( prev: Record | undefined, next: Record | undefined, ) { - if (!next || !prev?.approval || "approval" in next) return next - return { ...next, approval: prev.approval } + if (!next) return next + const prior = prev?.approval as Approval | undefined + if (!("approval" in next)) return prior ? { ...next, approval: prior } : next + const current = next.approval as Approval | undefined + if (!prior?.outsideWorkspace || !current || current.outsideWorkspace) return next + return { ...next, approval: { ...current, outsideWorkspace: true } } } /** diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index 2d7f45fbf2..fa2a827ebd 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -97,18 +97,25 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { }, }).pipe( // record why the call was allowed onto the tool part, then discard the outcome for the tool-facing ask - Effect.tap((approval) => input.processor.metadata(options.toolCallId, { metadata: { approval } })), + Effect.tap((approval) => + input.processor.metadata(options.toolCallId, { + metadata: { approval: PermissionProvenance.tagOutsideWorkspace(approval, req.permission) }, + }), + ), // record why the call was denied too, so JSON exports and clients can explain the denial Effect.tapErrorTag("PermissionDeniedError", (err) => input.processor.metadata(options.toolCallId, { metadata: { - approval: PermissionProvenance.classifyDenial({ - ruleset: err.ruleset, - permission: req.permission, - patterns: req.patterns, - agent: input.agent.name, - origins: permissionOrigins, - }), + approval: PermissionProvenance.tagOutsideWorkspace( + PermissionProvenance.classifyDenial({ + ruleset: err.ruleset, + permission: req.permission, + patterns: req.patterns, + agent: input.agent.name, + origins: permissionOrigins, + }), + req.permission, + ), }, }), ), diff --git a/packages/opencode/test/kilocode/permission/provenance.test.ts b/packages/opencode/test/kilocode/permission/provenance.test.ts index ad179e7e71..816d577d99 100644 --- a/packages/opencode/test/kilocode/permission/provenance.test.ts +++ b/packages/opencode/test/kilocode/permission/provenance.test.ts @@ -121,6 +121,42 @@ describe("PermissionProvenance.carryApproval", () => { test("returns the replacement as-is when it is undefined", () => { expect(PermissionProvenance.carryApproval({ approval }, undefined)).toBeUndefined() }) + + test("merges outsideWorkspace onto a replacement's own approval instead of clobbering it", () => { + // A file tool crossing the workspace boundary asks twice: external_directory first, then its + // own read/write/edit ask. The second ask's approval must not lose the outsideWorkspace marker. + const outside = { source: "manual" as const, outsideWorkspace: true } + const next = { approval: { source: "agent" as const, agent: "build" } } + expect(PermissionProvenance.carryApproval({ approval: outside }, next)).toEqual({ + approval: { source: "agent", agent: "build", outsideWorkspace: true }, + }) + }) + + test("does not add outsideWorkspace when the prior approval was not outside the workspace", () => { + const next = { approval: { source: "agent" as const, agent: "build" } } + expect(PermissionProvenance.carryApproval({ approval }, next)).toBe(next) + }) + + test("leaves a replacement's own outsideWorkspace marker untouched", () => { + const outside = { source: "manual" as const, outsideWorkspace: true } + const next = { approval: { source: "agent" as const, agent: "build", outsideWorkspace: true } } + expect(PermissionProvenance.carryApproval({ approval: outside }, next)).toBe(next) + }) +}) + +describe("PermissionProvenance.tagOutsideWorkspace", () => { + test("marks an external_directory approval as outsideWorkspace", () => { + const approval = { source: "manual" as const } + expect(PermissionProvenance.tagOutsideWorkspace(approval, "external_directory")).toEqual({ + source: "manual", + outsideWorkspace: true, + }) + }) + + test("leaves other permissions' approvals untouched", () => { + const approval = { source: "manual" as const } + expect(PermissionProvenance.tagOutsideWorkspace(approval, "read")).toBe(approval) + }) }) describe("askPermission returns provenance", () => { From 0c9b640331d05012bad4b98527bcbae86b3d80f2 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 14:53:39 +0200 Subject: [PATCH 2/6] fix(vscode): add missing outsideWorkspace i18n key to all locales --- 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/es.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fa.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/it.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/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 86f2eef279..f7c71435f1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -297,6 +297,7 @@ export const dict = { "ui.approval.source.yolo": "بواسطة وضع الموافقة التلقائية (YOLO)", "ui.approval.source.session": "بواسطة قاعدة موافقة تلقائية للجلسة", "ui.approval.source.default": "افتراضيًا", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "مراجعة", "session.review.filesChanged": "تم تغيير {{count}} ملفات", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index d29e57f2ce..d8c6015721 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -307,6 +307,7 @@ export const dict = { "ui.approval.source.yolo": "pelo modo de aprovação automática (YOLO)", "ui.approval.source.session": "por uma regra de aprovação automática da sessão", "ui.approval.source.default": "por padrão", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Revisão", "session.review.filesChanged": "{{count}} Arquivos Alterados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index cf67d75e52..f7e39f6bfa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -305,6 +305,7 @@ export const dict = { "ui.approval.source.yolo": "režimom automatskog odobravanja (YOLO)", "ui.approval.source.session": "pravilom automatskog odobravanja sesije", "ui.approval.source.default": "podrazumevano", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Pregled", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index e432d83977..97f238fcf0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -304,6 +304,7 @@ export const dict = { "ui.approval.source.yolo": "af automatisk godkendelse (YOLO)", "ui.approval.source.session": "af en session-autogodkendelsesregel", "ui.approval.source.default": "som standard", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Gennemgang", "session.review.filesChanged": "{{count}} Filer ændret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 3d7058a7ad..dd334cb8b6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -313,6 +313,7 @@ export const dict = { "ui.approval.source.yolo": "durch den Auto-Genehmigungsmodus (YOLO)", "ui.approval.source.session": "durch eine Sitzungs-Auto-Genehmigungsregel", "ui.approval.source.default": "standardmäßig", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Überprüfung", "session.review.filesChanged": "{{count}} Dateien geändert", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 3af575f0c5..556e4d950b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -308,6 +308,7 @@ export const dict = { "ui.approval.source.yolo": "por el modo de aprobación automática (YOLO)", "ui.approval.source.session": "por una regla de aprobación automática de sesión", "ui.approval.source.default": "de forma predeterminada", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Revisión", "session.review.filesChanged": "{{count}} Archivos Cambiados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts index 97f0b6b457..6e0b77e3c3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts @@ -302,6 +302,7 @@ export const dict = { "ui.approval.source.yolo": "توسط حالت تأیید خودکار (YOLO)", "ui.approval.source.session": "توسط قانون تأیید خودکار جلسه", "ui.approval.source.default": "به‌طور پیش‌فرض", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "بررسی", "session.review.filesChanged": "{{count}} فایل تغییر یافته", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index be9791bc66..3349663f68 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -307,6 +307,7 @@ export const dict = { "ui.approval.source.yolo": "par le mode d'approbation automatique (YOLO)", "ui.approval.source.session": "par une règle d'approbation automatique de session", "ui.approval.source.default": "par défaut", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Revue", "session.review.filesChanged": "{{count}} fichiers modifiés", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index f1d977e188..5600b4b533 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -219,6 +219,7 @@ export const dict = { "ui.approval.source.yolo": "dalla modalità di approvazione automatica (YOLO)", "ui.approval.source.session": "da una regola di approvazione automatica della sessione", "ui.approval.source.default": "per impostazione predefinita", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Revisione", "session.review.filesChanged": "{{count}} file modificati", "session.review.loadingChanges": "Caricamento modifiche...", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 279600efc2..7f38cab2ec 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -304,6 +304,7 @@ export const dict = { "ui.approval.source.yolo": "自動承認(YOLO)モードによって", "ui.approval.source.session": "セッションの自動承認ルールによって", "ui.approval.source.default": "デフォルトで", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "レビュー", "session.review.filesChanged": "{{count}} ファイル変更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index bfc1ae50ae..6c88e3d17a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -305,6 +305,7 @@ export const dict = { "ui.approval.source.yolo": "자동 승인(YOLO) 모드에 의해", "ui.approval.source.session": "세션 자동 승인 규칙에 의해", "ui.approval.source.default": "기본값으로", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "검토", "session.review.filesChanged": "{{count}}개 파일 변경됨", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index c3ad428f56..52ebf8e878 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -308,6 +308,7 @@ export const dict = { "ui.approval.source.yolo": "door de automatische goedkeuringsmodus (YOLO)", "ui.approval.source.session": "door een sessie-automatische-goedkeuringsregel", "ui.approval.source.default": "standaard", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Beoordelen", "session.review.filesChanged": "{{count}} bestanden gewijzigd", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 519b8ebc2c..a0e4809dcd 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -311,6 +311,7 @@ export const dict = { "ui.approval.source.yolo": "av automatisk godkjenning (YOLO)", "ui.approval.source.session": "av en økt-autogodkjenningsregel", "ui.approval.source.default": "som standard", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Gjennomgang", "session.review.filesChanged": "{{count}} filer endret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index dc7f924e6a..8f655adac4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -305,6 +305,7 @@ export const dict = { "ui.approval.source.yolo": "przez tryb automatycznego zatwierdzania (YOLO)", "ui.approval.source.session": "przez regułę automatycznego zatwierdzania sesji", "ui.approval.source.default": "domyślnie", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Przegląd", "session.review.filesChanged": "Zmieniono {{count}} plików", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 0ecea51604..9731d5a152 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -303,6 +303,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автоодобрения (YOLO)", "ui.approval.source.session": "правилом автоодобрения сессии", "ui.approval.source.default": "по умолчанию", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Обзор", "session.review.filesChanged": "{{count}} файлов изменено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 1f2cc4ac42..2d6f0cac2b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -302,6 +302,7 @@ export const dict = { "ui.approval.source.yolo": "โดยโหมดอนุมัติอัตโนมัติ (YOLO)", "ui.approval.source.session": "โดยกฎอนุมัติอัตโนมัติของเซสชัน", "ui.approval.source.default": "ตามค่าเริ่มต้น", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "ตรวจสอบ", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index e5748071b2..0671e4a0fa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -303,6 +303,7 @@ export const dict = { "ui.approval.source.yolo": "otomatik onay (YOLO) modu tarafından", "ui.approval.source.session": "bir oturum otomatik onay kuralı tarafından", "ui.approval.source.default": "varsayılan olarak", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "İnceleme", "session.review.filesChanged": "{{count}} Dosya Değişti", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 183912721f..a763b3b8d3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -307,6 +307,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автосхвалення (YOLO)", "ui.approval.source.session": "правилом автосхвалення сесії", "ui.approval.source.default": "за замовчуванням", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "Огляд", "session.review.filesChanged": "{{count}} файлів змінено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index dd35db0e2d..f2037d9404 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -292,6 +292,7 @@ export const dict = { "ui.approval.source.yolo": "由自动批准(YOLO)模式", "ui.approval.source.session": "由会话自动批准规则", "ui.approval.source.default": "默认", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "审查", "session.review.filesChanged": "{{count}} 个文件变更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index aabed9a3e8..d78c088530 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -290,6 +290,7 @@ export const dict = { "ui.approval.source.yolo": "由自動核准(YOLO)模式", "ui.approval.source.session": "由工作階段自動核准規則", "ui.approval.source.default": "預設", + "ui.approval.outsideWorkspace": "path is outside your workspace", "session.tab.review": "審查", "session.review.filesChanged": "{{count}} 個檔案變更", From d1630fcbded568a60c5abc956381edb7ae410c17 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 14:58:16 +0200 Subject: [PATCH 3/6] fix(vscode): translate outsideWorkspace approval string for all locales --- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/br.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/da.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/de.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/es.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/fa.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/it.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/no.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/th.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 2 +- packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index f7c71435f1..9d59467f9e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -297,7 +297,7 @@ export const dict = { "ui.approval.source.yolo": "بواسطة وضع الموافقة التلقائية (YOLO)", "ui.approval.source.session": "بواسطة قاعدة موافقة تلقائية للجلسة", "ui.approval.source.default": "افتراضيًا", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "المسار خارج مساحة العمل الخاصة بك", "session.tab.review": "مراجعة", "session.review.filesChanged": "تم تغيير {{count}} ملفات", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index d8c6015721..4ea354d7d1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "pelo modo de aprovação automática (YOLO)", "ui.approval.source.session": "por uma regra de aprovação automática da sessão", "ui.approval.source.default": "por padrão", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "o caminho está fora do seu espaço de trabalho", "session.tab.review": "Revisão", "session.review.filesChanged": "{{count}} Arquivos Alterados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index f7e39f6bfa..953b137832 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "režimom automatskog odobravanja (YOLO)", "ui.approval.source.session": "pravilom automatskog odobravanja sesije", "ui.approval.source.default": "podrazumevano", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "putanja je izvan vašeg radnog prostora", "session.tab.review": "Pregled", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 97f238fcf0..d0433fdff8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -304,7 +304,7 @@ export const dict = { "ui.approval.source.yolo": "af automatisk godkendelse (YOLO)", "ui.approval.source.session": "af en session-autogodkendelsesregel", "ui.approval.source.default": "som standard", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "stien er uden for dit arbejdsområde", "session.tab.review": "Gennemgang", "session.review.filesChanged": "{{count}} Filer ændret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index dd334cb8b6..67ca6ef3bc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -313,7 +313,7 @@ export const dict = { "ui.approval.source.yolo": "durch den Auto-Genehmigungsmodus (YOLO)", "ui.approval.source.session": "durch eine Sitzungs-Auto-Genehmigungsregel", "ui.approval.source.default": "standardmäßig", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "der Pfad liegt außerhalb deines Arbeitsbereichs", "session.tab.review": "Überprüfung", "session.review.filesChanged": "{{count}} Dateien geändert", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 556e4d950b..a8de2c15da 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -308,7 +308,7 @@ export const dict = { "ui.approval.source.yolo": "por el modo de aprobación automática (YOLO)", "ui.approval.source.session": "por una regla de aprobación automática de sesión", "ui.approval.source.default": "de forma predeterminada", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "la ruta está fuera de tu espacio de trabajo", "session.tab.review": "Revisión", "session.review.filesChanged": "{{count}} Archivos Cambiados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts index 6e0b77e3c3..45974a628f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts @@ -302,7 +302,7 @@ export const dict = { "ui.approval.source.yolo": "توسط حالت تأیید خودکار (YOLO)", "ui.approval.source.session": "توسط قانون تأیید خودکار جلسه", "ui.approval.source.default": "به‌طور پیش‌فرض", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "مسیر خارج از فضای کاری شما است", "session.tab.review": "بررسی", "session.review.filesChanged": "{{count}} فایل تغییر یافته", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 3349663f68..f911da4bf0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "par le mode d'approbation automatique (YOLO)", "ui.approval.source.session": "par une règle d'approbation automatique de session", "ui.approval.source.default": "par défaut", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "le chemin se trouve en dehors de votre espace de travail", "session.tab.review": "Revue", "session.review.filesChanged": "{{count}} fichiers modifiés", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index 5600b4b533..fa24e22e0a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -219,7 +219,7 @@ export const dict = { "ui.approval.source.yolo": "dalla modalità di approvazione automatica (YOLO)", "ui.approval.source.session": "da una regola di approvazione automatica della sessione", "ui.approval.source.default": "per impostazione predefinita", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "il percorso si trova fuori dall'area di lavoro", "session.tab.review": "Revisione", "session.review.filesChanged": "{{count}} file modificati", "session.review.loadingChanges": "Caricamento modifiche...", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 7f38cab2ec..14337d35c9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -304,7 +304,7 @@ export const dict = { "ui.approval.source.yolo": "自動承認(YOLO)モードによって", "ui.approval.source.session": "セッションの自動承認ルールによって", "ui.approval.source.default": "デフォルトで", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "パスがワークスペースの外にあります", "session.tab.review": "レビュー", "session.review.filesChanged": "{{count}} ファイル変更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 6c88e3d17a..e9cc284d1d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "자동 승인(YOLO) 모드에 의해", "ui.approval.source.session": "세션 자동 승인 규칙에 의해", "ui.approval.source.default": "기본값으로", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "경로가 작업 영역 외부에 있습니다", "session.tab.review": "검토", "session.review.filesChanged": "{{count}}개 파일 변경됨", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 52ebf8e878..682a87ec9b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -308,7 +308,7 @@ export const dict = { "ui.approval.source.yolo": "door de automatische goedkeuringsmodus (YOLO)", "ui.approval.source.session": "door een sessie-automatische-goedkeuringsregel", "ui.approval.source.default": "standaard", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "het pad ligt buiten je werkruimte", "session.tab.review": "Beoordelen", "session.review.filesChanged": "{{count}} bestanden gewijzigd", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index a0e4809dcd..1e2cc66adc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -311,7 +311,7 @@ export const dict = { "ui.approval.source.yolo": "av automatisk godkjenning (YOLO)", "ui.approval.source.session": "av en økt-autogodkjenningsregel", "ui.approval.source.default": "som standard", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "stien er utenfor arbeidsområdet ditt", "session.tab.review": "Gjennomgang", "session.review.filesChanged": "{{count}} filer endret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 8f655adac4..0e0beb91ea 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "przez tryb automatycznego zatwierdzania (YOLO)", "ui.approval.source.session": "przez regułę automatycznego zatwierdzania sesji", "ui.approval.source.default": "domyślnie", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "ścieżka znajduje się poza obszarem roboczym", "session.tab.review": "Przegląd", "session.review.filesChanged": "Zmieniono {{count}} plików", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 9731d5a152..f95a4330dd 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -303,7 +303,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автоодобрения (YOLO)", "ui.approval.source.session": "правилом автоодобрения сессии", "ui.approval.source.default": "по умолчанию", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "путь находится за пределами вашей рабочей области", "session.tab.review": "Обзор", "session.review.filesChanged": "{{count}} файлов изменено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 2d6f0cac2b..b53e8e257e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -302,7 +302,7 @@ export const dict = { "ui.approval.source.yolo": "โดยโหมดอนุมัติอัตโนมัติ (YOLO)", "ui.approval.source.session": "โดยกฎอนุมัติอัตโนมัติของเซสชัน", "ui.approval.source.default": "ตามค่าเริ่มต้น", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "พาธอยู่นอกพื้นที่ทำงานของคุณ", "session.tab.review": "ตรวจสอบ", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 0671e4a0fa..fb724d7558 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -303,7 +303,7 @@ export const dict = { "ui.approval.source.yolo": "otomatik onay (YOLO) modu tarafından", "ui.approval.source.session": "bir oturum otomatik onay kuralı tarafından", "ui.approval.source.default": "varsayılan olarak", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "yol çalışma alanınızın dışında", "session.tab.review": "İnceleme", "session.review.filesChanged": "{{count}} Dosya Değişti", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index a763b3b8d3..57cd805aee 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автосхвалення (YOLO)", "ui.approval.source.session": "правилом автосхвалення сесії", "ui.approval.source.default": "за замовчуванням", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "шлях знаходиться за межами вашого робочого простору", "session.tab.review": "Огляд", "session.review.filesChanged": "{{count}} файлів змінено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index f2037d9404..a6c0236772 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -292,7 +292,7 @@ export const dict = { "ui.approval.source.yolo": "由自动批准(YOLO)模式", "ui.approval.source.session": "由会话自动批准规则", "ui.approval.source.default": "默认", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "路径在你的工作区之外", "session.tab.review": "审查", "session.review.filesChanged": "{{count}} 个文件变更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index d78c088530..b1ac1b008a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -290,7 +290,7 @@ export const dict = { "ui.approval.source.yolo": "由自動核准(YOLO)模式", "ui.approval.source.session": "由工作階段自動核准規則", "ui.approval.source.default": "預設", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "路徑在你的工作區之外", "session.tab.review": "審查", "session.review.filesChanged": "{{count}} 個檔案變更", From 9c36685c0f7ff218f8733f0ada1807ba5149b7d5 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 15:10:28 +0200 Subject: [PATCH 4/6] fix(vscode): show filename inline in outside-workspace approval note --- .../src/components/tool-approval.test.ts | 18 +++++++-- .../kilo-ui/src/components/tool-approval.tsx | 9 ++++- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/br.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/bs.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/da.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/de.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/en.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/es.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/fa.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/fr.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/it.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/ja.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/ko.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/nl.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/no.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/pl.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/ru.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/th.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/tr.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/uk.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/zh.ts | 2 +- .../kilo-vscode/webview-ui/src/i18n/zht.ts | 2 +- .../src/kilocode/permission/provenance.ts | 21 ++++++++-- packages/opencode/src/session/tools.ts | 9 ++++- .../kilocode/permission/provenance.test.ts | 38 +++++++++++++++++++ 26 files changed, 108 insertions(+), 29 deletions(-) diff --git a/packages/kilo-ui/src/components/tool-approval.test.ts b/packages/kilo-ui/src/components/tool-approval.test.ts index 414e18ee34..f61b7c5c8c 100644 --- a/packages/kilo-ui/src/components/tool-approval.test.ts +++ b/packages/kilo-ui/src/components/tool-approval.test.ts @@ -55,10 +55,15 @@ describe("resolveToolApproval", () => { expect(out?.rule).toBeUndefined() }) - test("adds the outsideWorkspace text when the approval was marked outside the workspace", () => { - const approval = { source: "agent" as const, agent: "code", outsideWorkspace: true } + test("adds the outsideWorkspace text with just the filename when a path is known", () => { + const approval = { + source: "agent" as const, + agent: "code", + outsideWorkspace: true, + outsideWorkspacePath: "/etc/secrets/hello.txt", + } const out = resolveToolApproval({ approval }, t) - expect(out?.outsideWorkspace).toBe("ui.approval.outsideWorkspace") + expect(out?.outsideWorkspace).toBe("ui.approval.outsideWorkspace(file=hello.txt)") }) test("omits the outsideWorkspace text for an ordinary in-workspace approval", () => { @@ -66,4 +71,11 @@ describe("resolveToolApproval", () => { const out = resolveToolApproval({ approval }, t) expect(out?.outsideWorkspace).toBeUndefined() }) + + test("omits the outsideWorkspace text when outsideWorkspace is set but no path is known", () => { + // e.g. a bash command scanning multiple external directories has no single filepath to show. + const approval = { source: "agent" as const, agent: "code", outsideWorkspace: true } + const out = resolveToolApproval({ approval }, t) + expect(out?.outsideWorkspace).toBeUndefined() + }) }) diff --git a/packages/kilo-ui/src/components/tool-approval.tsx b/packages/kilo-ui/src/components/tool-approval.tsx index a245e145b9..aa9d6c322a 100644 --- a/packages/kilo-ui/src/components/tool-approval.tsx +++ b/packages/kilo-ui/src/components/tool-approval.tsx @@ -1,4 +1,5 @@ import { createContext, useContext, Show, type Accessor, type ParentProps } from "solid-js" +import { getFilename } from "@opencode-ai/core/util/path" import { Icon } from "./icon" /** @@ -14,6 +15,8 @@ export type ToolApproval = { rule?: { permission: string; pattern: string; action: string } /** True when the tool call's target path was outside the workspace/worktree. */ outsideWorkspace?: boolean + /** The target file path, when known, for display as a filename next to the note above. */ + outsideWorkspacePath?: string } /** Pre-resolved, localized text plus the raw approval, supplied by the caller. */ @@ -85,12 +88,16 @@ export function resolveToolApproval( rule && !(rule.permission === "*" && rule.pattern === "*") ? t("ui.approval.rule", { permission: rule.permission, pattern: rule.pattern }) : undefined + // Only worth calling out when we know which file it was; a bare "outside your workspace" note + // without a filename (e.g. a bash command touching several directories) isn't actionable. + const filename = approval.outsideWorkspacePath ? getFilename(approval.outsideWorkspacePath) : undefined return { approval, decision: approval.source === "manual" ? t("ui.approval.manual") : t("ui.approval.auto"), source: sourceText(), rule: ruleText, - outsideWorkspace: approval.outsideWorkspace ? t("ui.approval.outsideWorkspace") : undefined, + outsideWorkspace: + approval.outsideWorkspace && filename ? t("ui.approval.outsideWorkspace", { file: filename }) : undefined, } } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 9d59467f9e..9a3310b655 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -297,7 +297,7 @@ export const dict = { "ui.approval.source.yolo": "بواسطة وضع الموافقة التلقائية (YOLO)", "ui.approval.source.session": "بواسطة قاعدة موافقة تلقائية للجلسة", "ui.approval.source.default": "افتراضيًا", - "ui.approval.outsideWorkspace": "المسار خارج مساحة العمل الخاصة بك", + "ui.approval.outsideWorkspace": "(خارج مساحة العمل: {{file}})", "session.tab.review": "مراجعة", "session.review.filesChanged": "تم تغيير {{count}} ملفات", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 4ea354d7d1..0df91b651c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "pelo modo de aprovação automática (YOLO)", "ui.approval.source.session": "por uma regra de aprovação automática da sessão", "ui.approval.source.default": "por padrão", - "ui.approval.outsideWorkspace": "o caminho está fora do seu espaço de trabalho", + "ui.approval.outsideWorkspace": "(fora do seu espaço de trabalho: {{file}})", "session.tab.review": "Revisão", "session.review.filesChanged": "{{count}} Arquivos Alterados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 953b137832..db0f459014 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "režimom automatskog odobravanja (YOLO)", "ui.approval.source.session": "pravilom automatskog odobravanja sesije", "ui.approval.source.default": "podrazumevano", - "ui.approval.outsideWorkspace": "putanja je izvan vašeg radnog prostora", + "ui.approval.outsideWorkspace": "(izvan vašeg radnog prostora: {{file}})", "session.tab.review": "Pregled", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index d0433fdff8..6f36adc3d3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -304,7 +304,7 @@ export const dict = { "ui.approval.source.yolo": "af automatisk godkendelse (YOLO)", "ui.approval.source.session": "af en session-autogodkendelsesregel", "ui.approval.source.default": "som standard", - "ui.approval.outsideWorkspace": "stien er uden for dit arbejdsområde", + "ui.approval.outsideWorkspace": "(uden for dit arbejdsområde: {{file}})", "session.tab.review": "Gennemgang", "session.review.filesChanged": "{{count}} Filer ændret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 67ca6ef3bc..4066d9396d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -313,7 +313,7 @@ export const dict = { "ui.approval.source.yolo": "durch den Auto-Genehmigungsmodus (YOLO)", "ui.approval.source.session": "durch eine Sitzungs-Auto-Genehmigungsregel", "ui.approval.source.default": "standardmäßig", - "ui.approval.outsideWorkspace": "der Pfad liegt außerhalb deines Arbeitsbereichs", + "ui.approval.outsideWorkspace": "(außerhalb deines Arbeitsbereichs: {{file}})", "session.tab.review": "Überprüfung", "session.review.filesChanged": "{{count}} Dateien geändert", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index af4d5972c9..80f804d969 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -302,7 +302,7 @@ export const dict = { "ui.approval.source.yolo": "by auto-approve (YOLO) mode", "ui.approval.source.session": "by a session auto-approve rule", "ui.approval.source.default": "by default", - "ui.approval.outsideWorkspace": "path is outside your workspace", + "ui.approval.outsideWorkspace": "(outside your workspace: {{file}})", "session.tab.review": "Review", "session.review.filesChanged": "{{count}} Files Changed", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index a8de2c15da..558eb4a26a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -308,7 +308,7 @@ export const dict = { "ui.approval.source.yolo": "por el modo de aprobación automática (YOLO)", "ui.approval.source.session": "por una regla de aprobación automática de sesión", "ui.approval.source.default": "de forma predeterminada", - "ui.approval.outsideWorkspace": "la ruta está fuera de tu espacio de trabajo", + "ui.approval.outsideWorkspace": "(fuera de tu espacio de trabajo: {{file}})", "session.tab.review": "Revisión", "session.review.filesChanged": "{{count}} Archivos Cambiados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts index 45974a628f..cda714862b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fa.ts @@ -302,7 +302,7 @@ export const dict = { "ui.approval.source.yolo": "توسط حالت تأیید خودکار (YOLO)", "ui.approval.source.session": "توسط قانون تأیید خودکار جلسه", "ui.approval.source.default": "به‌طور پیش‌فرض", - "ui.approval.outsideWorkspace": "مسیر خارج از فضای کاری شما است", + "ui.approval.outsideWorkspace": "(خارج از فضای کاری شما: {{file}})", "session.tab.review": "بررسی", "session.review.filesChanged": "{{count}} فایل تغییر یافته", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index f911da4bf0..0292d8767a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "par le mode d'approbation automatique (YOLO)", "ui.approval.source.session": "par une règle d'approbation automatique de session", "ui.approval.source.default": "par défaut", - "ui.approval.outsideWorkspace": "le chemin se trouve en dehors de votre espace de travail", + "ui.approval.outsideWorkspace": "(hors de votre espace de travail : {{file}})", "session.tab.review": "Revue", "session.review.filesChanged": "{{count}} fichiers modifiés", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index fa24e22e0a..10ef2fd2dc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -219,7 +219,7 @@ export const dict = { "ui.approval.source.yolo": "dalla modalità di approvazione automatica (YOLO)", "ui.approval.source.session": "da una regola di approvazione automatica della sessione", "ui.approval.source.default": "per impostazione predefinita", - "ui.approval.outsideWorkspace": "il percorso si trova fuori dall'area di lavoro", + "ui.approval.outsideWorkspace": "(fuori dall'area di lavoro: {{file}})", "session.tab.review": "Revisione", "session.review.filesChanged": "{{count}} file modificati", "session.review.loadingChanges": "Caricamento modifiche...", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 14337d35c9..67c60731ef 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -304,7 +304,7 @@ export const dict = { "ui.approval.source.yolo": "自動承認(YOLO)モードによって", "ui.approval.source.session": "セッションの自動承認ルールによって", "ui.approval.source.default": "デフォルトで", - "ui.approval.outsideWorkspace": "パスがワークスペースの外にあります", + "ui.approval.outsideWorkspace": "(ワークスペース外:{{file}})", "session.tab.review": "レビュー", "session.review.filesChanged": "{{count}} ファイル変更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index e9cc284d1d..e7d0664e7b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "자동 승인(YOLO) 모드에 의해", "ui.approval.source.session": "세션 자동 승인 규칙에 의해", "ui.approval.source.default": "기본값으로", - "ui.approval.outsideWorkspace": "경로가 작업 영역 외부에 있습니다", + "ui.approval.outsideWorkspace": "(작업 영역 외부: {{file}})", "session.tab.review": "검토", "session.review.filesChanged": "{{count}}개 파일 변경됨", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 682a87ec9b..40169d8672 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -308,7 +308,7 @@ export const dict = { "ui.approval.source.yolo": "door de automatische goedkeuringsmodus (YOLO)", "ui.approval.source.session": "door een sessie-automatische-goedkeuringsregel", "ui.approval.source.default": "standaard", - "ui.approval.outsideWorkspace": "het pad ligt buiten je werkruimte", + "ui.approval.outsideWorkspace": "(buiten je werkruimte: {{file}})", "session.tab.review": "Beoordelen", "session.review.filesChanged": "{{count}} bestanden gewijzigd", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 1e2cc66adc..bea79dee72 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -311,7 +311,7 @@ export const dict = { "ui.approval.source.yolo": "av automatisk godkjenning (YOLO)", "ui.approval.source.session": "av en økt-autogodkjenningsregel", "ui.approval.source.default": "som standard", - "ui.approval.outsideWorkspace": "stien er utenfor arbeidsområdet ditt", + "ui.approval.outsideWorkspace": "(utenfor arbeidsområdet ditt: {{file}})", "session.tab.review": "Gjennomgang", "session.review.filesChanged": "{{count}} filer endret", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 0e0beb91ea..701c94122f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -305,7 +305,7 @@ export const dict = { "ui.approval.source.yolo": "przez tryb automatycznego zatwierdzania (YOLO)", "ui.approval.source.session": "przez regułę automatycznego zatwierdzania sesji", "ui.approval.source.default": "domyślnie", - "ui.approval.outsideWorkspace": "ścieżka znajduje się poza obszarem roboczym", + "ui.approval.outsideWorkspace": "(poza obszarem roboczym: {{file}})", "session.tab.review": "Przegląd", "session.review.filesChanged": "Zmieniono {{count}} plików", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index f95a4330dd..9f64e84a17 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -303,7 +303,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автоодобрения (YOLO)", "ui.approval.source.session": "правилом автоодобрения сессии", "ui.approval.source.default": "по умолчанию", - "ui.approval.outsideWorkspace": "путь находится за пределами вашей рабочей области", + "ui.approval.outsideWorkspace": "(за пределами вашей рабочей области: {{file}})", "session.tab.review": "Обзор", "session.review.filesChanged": "{{count}} файлов изменено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index b53e8e257e..5b0e38ddf1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -302,7 +302,7 @@ export const dict = { "ui.approval.source.yolo": "โดยโหมดอนุมัติอัตโนมัติ (YOLO)", "ui.approval.source.session": "โดยกฎอนุมัติอัตโนมัติของเซสชัน", "ui.approval.source.default": "ตามค่าเริ่มต้น", - "ui.approval.outsideWorkspace": "พาธอยู่นอกพื้นที่ทำงานของคุณ", + "ui.approval.outsideWorkspace": "(นอกพื้นที่ทำงานของคุณ: {{file}})", "session.tab.review": "ตรวจสอบ", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index fb724d7558..83a9d8f16c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -303,7 +303,7 @@ export const dict = { "ui.approval.source.yolo": "otomatik onay (YOLO) modu tarafından", "ui.approval.source.session": "bir oturum otomatik onay kuralı tarafından", "ui.approval.source.default": "varsayılan olarak", - "ui.approval.outsideWorkspace": "yol çalışma alanınızın dışında", + "ui.approval.outsideWorkspace": "(çalışma alanınızın dışında: {{file}})", "session.tab.review": "İnceleme", "session.review.filesChanged": "{{count}} Dosya Değişti", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 57cd805aee..e773ee2d54 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -307,7 +307,7 @@ export const dict = { "ui.approval.source.yolo": "режимом автосхвалення (YOLO)", "ui.approval.source.session": "правилом автосхвалення сесії", "ui.approval.source.default": "за замовчуванням", - "ui.approval.outsideWorkspace": "шлях знаходиться за межами вашого робочого простору", + "ui.approval.outsideWorkspace": "(за межами вашого робочого простору: {{file}})", "session.tab.review": "Огляд", "session.review.filesChanged": "{{count}} файлів змінено", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index a6c0236772..249a332bd1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -292,7 +292,7 @@ export const dict = { "ui.approval.source.yolo": "由自动批准(YOLO)模式", "ui.approval.source.session": "由会话自动批准规则", "ui.approval.source.default": "默认", - "ui.approval.outsideWorkspace": "路径在你的工作区之外", + "ui.approval.outsideWorkspace": "(工作区之外:{{file}})", "session.tab.review": "审查", "session.review.filesChanged": "{{count}} 个文件变更", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index b1ac1b008a..93746129e7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -290,7 +290,7 @@ export const dict = { "ui.approval.source.yolo": "由自動核准(YOLO)模式", "ui.approval.source.session": "由工作階段自動核准規則", "ui.approval.source.default": "預設", - "ui.approval.outsideWorkspace": "路徑在你的工作區之外", + "ui.approval.outsideWorkspace": "(工作區之外:{{file}})", "session.tab.review": "審查", "session.review.filesChanged": "{{count}} 個檔案變更", diff --git a/packages/opencode/src/kilocode/permission/provenance.ts b/packages/opencode/src/kilocode/permission/provenance.ts index 896db47f1f..38b244448e 100644 --- a/packages/opencode/src/kilocode/permission/provenance.ts +++ b/packages/opencode/src/kilocode/permission/provenance.ts @@ -29,11 +29,19 @@ export namespace PermissionProvenance { rule?: { permission: string; pattern: string; action: Permission.Action } /** True when the ask's target path was outside the workspace/worktree (an `external_directory` ask). */ outsideWorkspace?: boolean + /** The target file path, when the `external_directory` ask carried one, for display as a filename. */ + outsideWorkspacePath?: string + } + + /** The `filepath` an `external_directory` ask's metadata carries, if any (see `Tool.assertExternalDirectory`). */ + export function filepathOf(metadata: Record | undefined): string | undefined { + return typeof metadata?.filepath === "string" ? metadata.filepath : undefined } /** Tag an approval as outside-workspace when it answers an `external_directory` ask. */ - export function tagOutsideWorkspace(approval: Approval, permission: string): Approval { - return permission === "external_directory" ? { ...approval, outsideWorkspace: true } : approval + export function tagOutsideWorkspace(approval: Approval, permission: string, path?: string): Approval { + if (permission !== "external_directory") return approval + return { ...approval, outsideWorkspace: true, ...(path ? { outsideWorkspacePath: path } : {}) } } export type Scope = "global" | "local" @@ -93,7 +101,14 @@ export namespace PermissionProvenance { if (!("approval" in next)) return prior ? { ...next, approval: prior } : next const current = next.approval as Approval | undefined if (!prior?.outsideWorkspace || !current || current.outsideWorkspace) return next - return { ...next, approval: { ...current, outsideWorkspace: true } } + return { + ...next, + approval: { + ...current, + outsideWorkspace: true, + ...(prior.outsideWorkspacePath ? { outsideWorkspacePath: prior.outsideWorkspacePath } : {}), + }, + } } /** diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index fa2a827ebd..9b4907512a 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -99,7 +99,13 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { // record why the call was allowed onto the tool part, then discard the outcome for the tool-facing ask Effect.tap((approval) => input.processor.metadata(options.toolCallId, { - metadata: { approval: PermissionProvenance.tagOutsideWorkspace(approval, req.permission) }, + metadata: { + approval: PermissionProvenance.tagOutsideWorkspace( + approval, + req.permission, + PermissionProvenance.filepathOf(req.metadata), + ), + }, }), ), // record why the call was denied too, so JSON exports and clients can explain the denial @@ -115,6 +121,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { origins: permissionOrigins, }), req.permission, + PermissionProvenance.filepathOf(req.metadata), ), }, }), diff --git a/packages/opencode/test/kilocode/permission/provenance.test.ts b/packages/opencode/test/kilocode/permission/provenance.test.ts index 816d577d99..b2ffae3ecd 100644 --- a/packages/opencode/test/kilocode/permission/provenance.test.ts +++ b/packages/opencode/test/kilocode/permission/provenance.test.ts @@ -132,6 +132,14 @@ describe("PermissionProvenance.carryApproval", () => { }) }) + test("also carries the outsideWorkspacePath forward alongside the marker", () => { + const outside = { source: "manual" as const, outsideWorkspace: true, outsideWorkspacePath: "/tmp/secret.txt" } + const next = { approval: { source: "agent" as const, agent: "build" } } + expect(PermissionProvenance.carryApproval({ approval: outside }, next)).toEqual({ + approval: { source: "agent", agent: "build", outsideWorkspace: true, outsideWorkspacePath: "/tmp/secret.txt" }, + }) + }) + test("does not add outsideWorkspace when the prior approval was not outside the workspace", () => { const next = { approval: { source: "agent" as const, agent: "build" } } expect(PermissionProvenance.carryApproval({ approval }, next)).toBe(next) @@ -157,6 +165,36 @@ describe("PermissionProvenance.tagOutsideWorkspace", () => { const approval = { source: "manual" as const } expect(PermissionProvenance.tagOutsideWorkspace(approval, "read")).toBe(approval) }) + + test("carries the target path when one is given", () => { + const approval = { source: "manual" as const } + expect(PermissionProvenance.tagOutsideWorkspace(approval, "external_directory", "/tmp/secret.txt")).toEqual({ + source: "manual", + outsideWorkspace: true, + outsideWorkspacePath: "/tmp/secret.txt", + }) + }) + + test("omits outsideWorkspacePath when no path is given", () => { + const approval = { source: "manual" as const } + expect(PermissionProvenance.tagOutsideWorkspace(approval, "external_directory")).toEqual({ + source: "manual", + outsideWorkspace: true, + }) + }) +}) + +describe("PermissionProvenance.filepathOf", () => { + test("reads the filepath an external_directory ask's metadata carries", () => { + expect(PermissionProvenance.filepathOf({ filepath: "/tmp/secret.txt", parentDir: "/tmp" })).toBe( + "/tmp/secret.txt", + ) + }) + + test("returns undefined when there is no filepath, e.g. a bash directory scan", () => { + expect(PermissionProvenance.filepathOf({ command: "cat /tmp/secret.txt", access: "read" })).toBeUndefined() + expect(PermissionProvenance.filepathOf(undefined)).toBeUndefined() + }) }) describe("askPermission returns provenance", () => { From 356f5b0a6c888764d41cf533c9e8963a4a7bf94f Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 19:19:07 +0200 Subject: [PATCH 5/6] fix(ui): drop italic styling and highlight decision text in tool approval line --- packages/kilo-ui/src/components/basic-tool.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/kilo-ui/src/components/basic-tool.css b/packages/kilo-ui/src/components/basic-tool.css index 9d7ff5acc7..7f54737247 100644 --- a/packages/kilo-ui/src/components/basic-tool.css +++ b/packages/kilo-ui/src/components/basic-tool.css @@ -508,8 +508,8 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty } /* "why was this allowed" line inside a tool's expanded body. Styled like - [data-component="tool-hint"] (muted, italic) so it reads as ambient - context rather than a call to action, and recedes the way reasoning text does. */ + [data-component="tool-hint"] (muted) so it reads as ambient context rather + than a call to action, and recedes the way reasoning text does. */ [data-slot="tool-approval-line"] { display: flex; flex-wrap: wrap; @@ -518,7 +518,6 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty padding: 4px 0 6px; font-family: var(--font-family-sans); font-size: var(--font-size-small); - font-style: italic; line-height: var(--line-height-normal); color: var(--text-weak); opacity: 0.9; @@ -528,6 +527,11 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty color: var(--text-weak); } + [data-slot="tool-approval-decision"] { + font-weight: var(--font-weight-medium); + color: var(--text-strong); + } + [data-slot="tool-approval-rule"] { font-family: var(--font-family-mono); } From 4feb0c903d9e193dace32f7a1361135ce2b2c4a1 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 19:29:13 +0200 Subject: [PATCH 6/6] fix(vscode): preserve approval metadata when slimming tool parts for the webview --- .../src/kilo-provider/slim-metadata.ts | 4 ++++ .../tests/unit/slim-metadata.test.ts | 20 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts index df8d576e03..9175ea673d 100644 --- a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts +++ b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts @@ -73,6 +73,7 @@ function slimEdit(state: Record): Record { } } if (meta.diagnostics) result.diagnostics = meta.diagnostics + if (meta.approval) result.approval = meta.approval next.metadata = result return next } @@ -84,6 +85,7 @@ function slimPatch(state: Record): Record { if (isObj(meta)) { const slim: Record = {} if (meta.diagnostics) slim.diagnostics = meta.diagnostics + if (meta.approval) slim.approval = meta.approval if (Array.isArray(meta.files)) { slim.files = (meta.files as Record[]).map((f) => { const diff = patch(f.patch) ?? patch(f.diff) @@ -115,6 +117,7 @@ function slimMultiedit(state: Record): Record if (isObj(meta)) { const slim: Record = {} if (meta.diagnostics) slim.diagnostics = meta.diagnostics + if (meta.approval) slim.approval = meta.approval if (Array.isArray(meta.results)) { slim.results = (meta.results as Record[]).map((r) => { const rs: Record = {} @@ -149,6 +152,7 @@ function slimWrite(state: Record): Record { if (meta.filepath) slim.filepath = meta.filepath if (meta.exists !== undefined) slim.exists = meta.exists if (meta.diagnostics) slim.diagnostics = meta.diagnostics + if (meta.approval) slim.approval = meta.approval const fd = meta.filediff if (isObj(fd)) { slim.filediff = { diff --git a/packages/kilo-vscode/tests/unit/slim-metadata.test.ts b/packages/kilo-vscode/tests/unit/slim-metadata.test.ts index 21c62f4722..96ee681858 100644 --- a/packages/kilo-vscode/tests/unit/slim-metadata.test.ts +++ b/packages/kilo-vscode/tests/unit/slim-metadata.test.ts @@ -30,6 +30,10 @@ const BIG = "x".repeat(200_000) // 200 KB — typical file content size const DIAG = [ { range: { start: { line: 1, character: 0 }, end: { line: 1, character: 5 } }, message: "err", severity: 1 }, ] +// Regression for #13001: slimmers used to rebuild `metadata` from an explicit allowlist that +// didn't include `approval`, silently dropping the auto-approval reason (and the +// outside-workspace note) before it ever reached the webview. +const APPROVAL = { source: "agent", agent: "code", outsideWorkspace: true, outsideWorkspacePath: "/tmp/a.ts" } // --------------------------------------------------------------------------- // Tests @@ -96,6 +100,7 @@ describe("slimPart", () => { diff: BIG, filediff: { file: "/a.ts", patch: PATCH, before: BIG, after: BIG, additions: 3, deletions: 1 }, diagnostics: { "/a.ts": DIAG }, + approval: APPROVAL, }, }) @@ -103,7 +108,7 @@ describe("slimPart", () => { expect(bytes(slimPart(heavy))).toBeLessThan(MAX_SLIM_BYTES) }) - it("keeps filediff counts and diagnostics", () => { + it("keeps filediff counts, diagnostics, and approval", () => { const slim = slimPart(heavy) as Record const meta = slim.state.metadata expect(meta.filediff.file).toBe("/a.ts") @@ -111,6 +116,7 @@ describe("slimPart", () => { expect(meta.filediff.additions).toBe(3) expect(meta.filediff.deletions).toBe(1) expect(meta.diagnostics).toEqual({ "/a.ts": DIAG }) + expect(meta.approval).toEqual(APPROVAL) }) it("keeps output and input intact", () => { @@ -176,6 +182,7 @@ describe("slimPart", () => { }, ], diagnostics: { "/a.ts": DIAG }, + approval: APPROVAL, }, }) @@ -183,7 +190,7 @@ describe("slimPart", () => { expect(bytes(slimPart(heavy))).toBeLessThan(MAX_SLIM_BYTES) }) - it("keeps file summary fields and diagnostics", () => { + it("keeps file summary fields, diagnostics, and approval", () => { const slim = slimPart(heavy) as Record const meta = slim.state.metadata expect(meta.files[0].filePath).toBe("/a.ts") @@ -193,6 +200,7 @@ describe("slimPart", () => { expect(meta.files[0].additions).toBe(5) expect(meta.files[1].type).toBe("add") expect(meta.diagnostics).toEqual({ "/a.ts": DIAG }) + expect(meta.approval).toEqual(APPROVAL) }) it("drops unknown heavy metadata fields", () => { @@ -266,6 +274,7 @@ describe("slimPart", () => { }, { filediff: { file: "/b.ts", before: BIG, after: BIG, additions: 2, deletions: 0 }, diagnostics: {} }, ], + approval: APPROVAL, }, }) @@ -273,7 +282,7 @@ describe("slimPart", () => { expect(bytes(slimPart(heavy))).toBeLessThan(MAX_SLIM_BYTES) }) - it("keeps filediff counts and per-result diagnostics", () => { + it("keeps filediff counts, per-result diagnostics, and approval", () => { const slim = slimPart(heavy) as Record const meta = slim.state.metadata expect(meta.results[0].filediff.file).toBe("/a.ts") @@ -282,6 +291,7 @@ describe("slimPart", () => { expect(meta.results[0].diagnostics).toEqual({ "/a.ts": DIAG }) expect(meta.results[1].filediff.file).toBe("/b.ts") expect(meta.diagnostics).toEqual({ "/a.ts": DIAG }) + expect(meta.approval).toEqual(APPROVAL) }) it("drops unknown heavy metadata fields", () => { @@ -318,6 +328,7 @@ describe("slimPart", () => { diff: BIG, filediff: { file: "/a.ts", patch: PATCH, before: BIG, after: BIG, additions: 100, deletions: 0 }, diagnostics: { "/a.ts": DIAG }, + approval: APPROVAL, }, }) @@ -325,7 +336,7 @@ describe("slimPart", () => { expect(bytes(slimPart(heavy))).toBeLessThan(MAX_SLIM_BYTES) }) - it("keeps filepath, exists, filediff counts, diagnostics", () => { + it("keeps filepath, exists, filediff counts, diagnostics, and approval", () => { const slim = slimPart(heavy) as Record const meta = slim.state.metadata expect(meta.filepath).toBe("/a.ts") @@ -335,6 +346,7 @@ describe("slimPart", () => { expect(meta.filediff.additions).toBe(100) expect(meta.filediff.deletions).toBe(0) expect(meta.diagnostics).toEqual({ "/a.ts": DIAG }) + expect(meta.approval).toEqual(APPROVAL) }) it("drops unknown heavy metadata fields", () => {