diff --git a/.changeset/swe-pruner-experimental.md b/.changeset/swe-pruner-experimental.md new file mode 100644 index 00000000000..763b04fe277 --- /dev/null +++ b/.changeset/swe-pruner-experimental.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": minor +"@kilocode/sdk": minor +--- + +Add experimental SWE-Pruner support (disabled by default). When enabled via `experimental.swe_pruner` or the Experimental settings tab in VS Code, the read and grep tools accept an optional `context_focus_question` parameter; when the agent provides it, large tool outputs are pruned by a small model down to the lines relevant to that question, with omitted sections marked inline and a `SWE-Pruner · kept/total` indicator on the tool row. The skimming model can be overridden via `experimental.swe_pruner_model` (defaults to the configured small model). Any pruning failure falls back to the full output. diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index 04cb462161e..7ac44b13cf3 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -32,6 +32,8 @@ - +- + - - diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index a5ab17ed51d..b73a35a5303 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -1630,6 +1630,14 @@ function ToolText(props: { text: string; delay?: number; animate?: boolean }) { ) } +function swePruned(metadata: Record) { + const value = metadata["swePruner"] + if (typeof value !== "object" || value === null) return undefined + const info = value as { kept?: unknown; total?: unknown } + if (typeof info.kept !== "number" || typeof info.total !== "number") return undefined + return { kept: info.kept, total: info.total } +} + function ToolLoadedFile(props: { text: string; animate?: boolean; onClick?: () => void }) { let ref: HTMLDivElement | undefined useToolFade(() => ref, { delay: 0.02, wipe: true, animate: props.animate }) @@ -1758,10 +1766,9 @@ ToolRegistry.register({ if (!value || !Array.isArray(value)) return [] return value.filter((p): p is string => typeof p === "string") }) + const pruned = createMemo(() => swePruned(props.metadata)) const pending = createMemo(() => busy(props.status)) - const images = createMemo(() => - (props.attachments ?? []).filter((f) => f.mime.startsWith("image/") && f.url), - ) + const images = createMemo(() => (props.attachments ?? []).filter((f) => f.mime.startsWith("image/") && f.url)) const preview = (url: string, alt?: string) => dialog.show(() => ) return ( <> @@ -1808,6 +1815,9 @@ ToolRegistry.register({ + + {(info) => } + ) }, @@ -1881,29 +1891,35 @@ ToolRegistry.register({ const args: string[] = [] if (props.input.pattern) args.push("pattern=" + props.input.pattern) if (props.input.include) args.push("include=" + props.input.include) + const pruned = createMemo(() => swePruned(props.metadata)) const pending = createMemo(() => busy(props.status)) return ( - - } - > - - {(output) => ( -
- -
- )} + <> + + } + > + + {(output) => ( +
+ +
+ )} +
+
+ + {(info) => } -
+ ) }, }) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx index a641b50eff6..fbb24b2e2f2 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx @@ -7,6 +7,8 @@ import { useConfig } from "../../context/config" import { useLanguage } from "../../context/language" import { useVSCode } from "../../context/vscode" import type { ExtensionMessage } from "../../types/messages" +import { parseModelString } from "../../../../src/shared/provider-model" +import { ModelSelectorBase } from "../shared/ModelSelector" import SettingsRow from "./SettingsRow" interface ShareOption { @@ -180,6 +182,38 @@ const ExperimentalTab: Component = () => { + + updateExperimental("swe_pruner", checked)} + hideLabel + > + {language.t("settings.experimental.swePruner.title")} + + + + + + + updateExperimental("swe_pruner_model", providerID && modelID ? `${providerID}/${modelID}` : null) + } + placement="bottom-start" + allowClear + clearLabel={language.t("settings.providers.notSet")} + label={language.t("settings.experimental.swePrunerModel.title")} + description={language.t("settings.experimental.swePrunerModel.description")} + /> + + + {/* MCP timeout */} )[PARAMETER] + if (typeof value !== "string") return undefined + const trimmed = value.trim() + return trimmed.length > 0 ? trimmed : undefined +} + +/** Advertise the focus parameter to the model without mutating the cached tool schema. */ +export function extend(schema: JSONSchema7): JSONSchema7 { + if (typeof schema !== "object" || schema === null || schema.type !== "object") return schema + return { + ...schema, + properties: { + ...schema.properties, + [PARAMETER]: { type: "string", description: DESCRIPTION }, + }, + } +} + +export type Range = [number, number] + +/** Parse the skimmer reply into sorted, merged, clamped keep-ranges. Returns undefined to keep everything. */ +export function parse(text: string, total: number): Range[] | undefined { + const trimmed = text.trim() + if (!trimmed || /^all\b/i.test(trimmed)) return undefined + const found: Range[] = [] + for (const line of trimmed.split("\n")) { + for (const token of line + .trim() + .replace(/^[-*•]\s+/, "") + .split(/[,;]/)) { + const item = token.trim() + if (!item) continue + const pair = item.match(/^\[*(\d+)\s*[-–—]\s*(\d+)\]*$/) + if (pair) { + found.push([Number(pair[1]), Number(pair[2])]) + continue + } + const single = item.match(/^\[*(\d+)\]*$/) + if (single) found.push([Number(single[1]), Number(single[1])]) + } + } + if (found.length === 0) return undefined + const clamped = found + .map(([start, end]): Range => [Math.max(1, Math.min(start, end)), Math.min(total, Math.max(start, end))]) + .filter(([start, end]) => start <= total && end >= 1 && start <= end) + if (clamped.length === 0) return undefined + clamped.push([1, Math.min(KEEP_HEAD, total)]) + if (total > KEEP_TAIL) clamped.push([total - KEEP_TAIL + 1, total]) + clamped.sort((a, b) => a[0] - b[0]) + const merged: Range[] = [] + for (const range of clamped) { + const last = merged[merged.length - 1] + if (last && range[0] <= last[1] + MERGE_GAP + 1) { + last[1] = Math.max(last[1], range[1]) + continue + } + merged.push([range[0], range[1]]) + } + return merged +} + +export function kept(ranges: Range[]) { + return ranges.reduce((sum, [start, end]) => sum + (end - start + 1), 0) +} + +/** Reassemble the output from keep-ranges, marking omitted sections inline. */ +export function assemble(lines: string[], ranges: Range[], total: number) { + const parts: string[] = [ + `[SWE-Pruner: kept ${kept(ranges)} of ${total} output lines relevant to the focus question. Omitted sections are marked below; call the tool again without ${PARAMETER} for the full output.]`, + ] + let cursor = 1 + for (const [start, end] of ranges) { + if (start > cursor) parts.push(`... [${start - cursor} lines omitted by SWE-Pruner] ...`) + parts.push(...lines.slice(start - 1, end)) + cursor = end + 1 + } + if (cursor <= total) parts.push(`... [${total - cursor + 1} lines omitted by SWE-Pruner] ...`) + return parts.join("\n") +} + +const resolve = Effect.fn("SwePruner.resolve")(function* () { + const provider = yield* Provider.Service + const config = yield* Config.Service + const cfg = yield* config.get() + const configured = cfg.experimental?.swe_pruner_model + if (configured) { + const parsed = Provider.parseModel(configured) + const model = yield* provider + .getModel(parsed.providerID, parsed.modelID) + .pipe(Effect.catch(() => Effect.succeed(undefined))) + if (model) return model + log.warn("configured model unavailable, falling back to small model", { model: configured }) + } + const ref = yield* provider.defaultModel() + return (yield* provider.getSmallModel(ref.providerID)) ?? (yield* provider.getModel(ref.providerID, ref.modelID)) +}) + +const skim = Effect.fn("SwePruner.skim")(function* (input: { question: string; output: string; abort?: AbortSignal }) { + const provider = yield* Provider.Service + const model = yield* resolve() + const language = yield* provider.getLanguage(model) + const lines = input.output.split("\n") + const numbered = lines.map((line, index) => `${index + 1}|${line}`).join("\n") + const signals = [AbortSignal.timeout(TIMEOUT_MS), ...(input.abort ? [input.abort] : [])] + const result = yield* Effect.tryPromise({ + try: () => + generateText({ + model: language, + temperature: model.capabilities.temperature ? 0.1 : undefined, + providerOptions: ProviderTransform.providerOptions( + model, + mergeDeep(ProviderTransform.smallOptions(model), model.options), + ), + maxRetries: 1, + abortSignal: AbortSignal.any(signals), + system: INSTRUCTION, + messages: [ + { + role: "user" as const, + content: `Focus question: ${input.question}\n\nTool output:\n${numbered}`, + }, + ], + }), + catch: (err) => (err instanceof Error ? err : new Error(String(err))), + }) + const ranges = parse(result.text, lines.length) + if (!ranges) return undefined + const keep = kept(ranges) + if (keep / lines.length > MAX_KEEP_RATIO) return undefined + return { output: assemble(lines, ranges, lines.length), kept: keep, total: lines.length } +}) + +/** Prune a tool result when a focus question was provided. Fails open to the original result. */ +export const sweep = Effect.fn("SwePruner.sweep")(function* (input: { + tool: string + args: unknown + result: Tool.ExecuteResult + abort?: AbortSignal +}) { + const focus = question(input.args) + if (!focus) return input.result + if (input.result.metadata["truncated"] === true) return input.result + const size = input.result.output.length + if (size < MIN_CHARS || size > MAX_CHARS) return input.result + if (input.result.output.split("\n").length < MIN_LINES) return input.result + const pruned = yield* skim({ question: focus, output: input.result.output, abort: input.abort }).pipe( + Effect.catchCause((cause) => { + log.error("skim failed, returning full output", { tool: input.tool, cause }) + return Effect.succeed(undefined) + }), + ) + if (!pruned) return input.result + log.info("pruned", { tool: input.tool, kept: pruned.kept, total: pruned.total }) + return { + ...input.result, + output: pruned.output, + metadata: { + ...input.result.metadata, + swePruner: { question: focus, kept: pruned.kept, total: pruned.total }, + }, + } +}) + +export * as SwePruner from "./swe-pruner" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 415b781c78f..c0789522952 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1605,6 +1605,10 @@ export const layer = Layer.effect( Effect.provideService(ToolRegistry.Service, registry), Effect.provideService(MCP.Service, mcp), Effect.provideService(Truncate.Service, truncate), + // kilocode_change start - SWE-Pruner (experimental) + Effect.provideService(Config.Service, config), + Effect.provideService(Provider.Service, provider), + // kilocode_change end ) if (lastUser.format?.type === "json_schema") { diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index 430cb0b07af..f5d075099b3 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -21,6 +21,10 @@ import { PartID } from "./schema" import * as Log from "@opencode-ai/core/util/log" import { EffectBridge } from "@/effect/bridge" import * as SandboxPolicy from "@/kilocode/sandbox/policy" // kilocode_change +// kilocode_change start +import { SwePruner } from "@/kilocode/swe-pruner" +import { Config } from "@/config/config" +// kilocode_change end const log = Log.create({ service: "session.tools" }) @@ -46,6 +50,10 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { const registry = yield* ToolRegistry.Service const mcp = yield* MCP.Service const truncate = yield* Truncate.Service + // kilocode_change start - SWE-Pruner (experimental) + const config = yield* Config.Service + const swe = SwePruner.enabled(yield* config.get()) + // kilocode_change end const context = (args: Record, options: ToolExecutionOptions): Tool.Context => ({ sessionID: input.session.id, @@ -79,7 +87,11 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { family: input.model.family, // kilocode_change agent: input.agent, })) { - const schema = ProviderTransform.schema(input.model, ToolJsonSchema.fromTool(item)) + // kilocode_change start - SWE-Pruner (experimental): advertise the focus parameter on prunable tools + const pruner = swe && SwePruner.prunable(item.id) + const base = ToolJsonSchema.fromTool(item) + const schema = ProviderTransform.schema(input.model, pruner ? SwePruner.extend(base) : base) + // kilocode_change end tools[item.id] = tool({ description: item.description, inputSchema: jsonSchema(schema), @@ -93,7 +105,11 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { { args }, ) // kilocode_change start - const result = yield* SandboxPolicy.executeTool(ctx.sessionID, item, item.execute(args, ctx)) + let result = yield* SandboxPolicy.executeTool(ctx.sessionID, item, item.execute(args, ctx)) + // SWE-Pruner (experimental): prune the output when the model provided a focus question. + // Runs before tool.execute.after so plugins observe the final output the model will + // see; pruning is signalled to them via metadata.swePruner. + if (pruner) result = yield* SwePruner.sweep({ tool: item.id, args, result, abort: ctx.abort }) // kilocode_change end const output = { ...result, diff --git a/packages/opencode/test/kilocode/swe-pruner.test.ts b/packages/opencode/test/kilocode/swe-pruner.test.ts new file mode 100644 index 00000000000..396f26d4310 --- /dev/null +++ b/packages/opencode/test/kilocode/swe-pruner.test.ts @@ -0,0 +1,139 @@ +import { describe, expect, test } from "bun:test" +import { SwePruner } from "../../src/kilocode/swe-pruner" + +describe("SwePruner.question", () => { + test("extracts a non-empty focus question from raw args", () => { + expect(SwePruner.question({ filePath: "/a", context_focus_question: "How is auth handled?" })).toBe( + "How is auth handled?", + ) + }) + + test("returns undefined for missing, empty, or non-string values", () => { + expect(SwePruner.question({ filePath: "/a" })).toBeUndefined() + expect(SwePruner.question({ context_focus_question: " " })).toBeUndefined() + expect(SwePruner.question({ context_focus_question: 42 })).toBeUndefined() + expect(SwePruner.question(undefined)).toBeUndefined() + expect(SwePruner.question(null)).toBeUndefined() + }) +}) + +describe("SwePruner.prunable", () => { + test("only read and grep are prunable", () => { + expect(SwePruner.prunable("read")).toBe(true) + expect(SwePruner.prunable("grep")).toBe(true) + expect(SwePruner.prunable("bash")).toBe(false) + expect(SwePruner.prunable("edit")).toBe(false) + }) +}) + +describe("SwePruner.extend", () => { + test("adds the focus parameter without mutating the input schema", () => { + const schema = { + type: "object" as const, + properties: { filePath: { type: "string" as const } }, + required: ["filePath"], + } + const extended = SwePruner.extend(schema) + expect(extended.properties?.[SwePruner.PARAMETER]).toMatchObject({ type: "string" }) + expect(extended.required).toEqual(["filePath"]) + expect(schema.properties).not.toHaveProperty(SwePruner.PARAMETER) + }) + + test("leaves non-object schemas untouched", () => { + const schema = { type: "string" as const } + expect(SwePruner.extend(schema)).toBe(schema) + }) +}) + +describe("SwePruner.parse", () => { + test("parses ranges and singles, clamps, sorts, and merges", () => { + const ranges = SwePruner.parse("40-60\n10-20\n12\n62", 100) + expect(ranges).toEqual([ + [1, 5], + [10, 20], + [40, 62], + [96, 100], + ]) + }) + + test("always keeps head and tail lines", () => { + const ranges = SwePruner.parse("50-55", 100) + expect(ranges?.[0]).toEqual([1, 5]) + expect(ranges?.[ranges.length - 1]).toEqual([96, 100]) + }) + + test("returns undefined for ALL or unparseable replies", () => { + expect(SwePruner.parse("ALL", 100)).toBeUndefined() + expect(SwePruner.parse("all of it is relevant", 100)).toBeUndefined() + expect(SwePruner.parse("nothing useful here", 100)).toBeUndefined() + expect(SwePruner.parse("", 100)).toBeUndefined() + }) + + test("drops ranges entirely out of bounds and clamps partial overlaps", () => { + expect(SwePruner.parse("200-300", 100)).toBeUndefined() + const ranges = SwePruner.parse("90-300", 100) + expect(ranges?.[ranges.length - 1]).toEqual([90, 100]) + }) + + test("tolerates reversed bounds and bulleted lists", () => { + const ranges = SwePruner.parse("- 60-40\n* 70", 100) + expect(ranges).toContainEqual([40, 60]) + }) + + test("parses comma-separated ranges on a single line", () => { + const ranges = SwePruner.parse("10-20, 30-40; 50", 100) + expect(ranges).toContainEqual([10, 20]) + expect(ranges).toContainEqual([30, 40]) + expect(ranges).toContainEqual([50, 50]) + }) + + test("treats comma-separated singles as singles, not a range", () => { + const ranges = SwePruner.parse("10, 20", 100) + expect(ranges).toContainEqual([10, 10]) + expect(ranges).toContainEqual([20, 20]) + expect(ranges).not.toContainEqual([10, 20]) + }) + + test("tolerates JSON-style array replies", () => { + const ranges = SwePruner.parse("[[10, 12], [30, 33]]", 100) + expect(ranges).toContainEqual([10, 12]) + expect(ranges).toContainEqual([30, 33]) + }) +}) + +describe("SwePruner.assemble", () => { + const lines = Array.from({ length: 20 }, (_, index) => `line ${index + 1}`) + + test("keeps selected ranges and marks omitted sections", () => { + const output = SwePruner.assemble( + lines, + [ + [1, 3], + [10, 12], + ], + 20, + ) + expect(output).toContain("line 1") + expect(output).toContain("line 12") + expect(output).not.toContain("line 5\n") + expect(output).toContain("[6 lines omitted by SWE-Pruner]") + expect(output).toContain("[8 lines omitted by SWE-Pruner]") + expect(output.startsWith("[SWE-Pruner: kept 6 of 20 output lines")).toBe(true) + }) + + test("adds no trailing marker when the last range reaches the end", () => { + const output = SwePruner.assemble(lines, [[18, 20]], 20) + expect(output.endsWith("line 20")).toBe(true) + }) +}) + +describe("SwePruner.kept", () => { + test("sums inclusive range sizes", () => { + expect( + SwePruner.kept([ + [1, 5], + [10, 10], + ]), + ).toBe(6) + }) +}) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 7e4afca20fb..6580b89b642 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1694,6 +1694,8 @@ export type Config = { sandbox?: boolean sandbox_restrict_network?: boolean sandbox_writable_paths?: Array + swe_pruner?: boolean + swe_pruner_model?: string mcp_timeout?: number policies?: Array } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 3ccce1a3671..3ad7879d6d6 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11957,6 +11957,16 @@ } } } + }, + "422": { + "description": "CommitMessageNoChangesError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CommitMessageNoChangesError" + } + } + } } }, "description": "Generate a commit message using AI based on the current git diff.", @@ -25168,6 +25178,18 @@ "sandbox_restrict_network": { "type": "boolean" }, + "sandbox_writable_paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "swe_pruner": { + "type": "boolean" + }, + "swe_pruner_model": { + "type": "string" + }, "mcp_timeout": { "type": "integer", "exclusiveMinimum": 0 @@ -27331,6 +27353,16 @@ "required": ["id", "sessionID", "output"], "additionalProperties": false }, + "CommitMessageNoChangesError": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"], + "additionalProperties": false + }, "ConfigOverlayResponse": { "type": "object", "properties": { diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 02097ec0133..868bf3d70ec 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -121,6 +121,7 @@ export const dict = { "ui.tool.read": "قراءة", "ui.tool.loaded": "تم التحميل", + "ui.tool.swePruned": "SWE-Pruner · تم الاحتفاظ بـ {{kept}} من {{total}} سطرًا", // kilocode_change "ui.tool.list": "قائمة", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 9220aff8aaf..f09b6d52f31 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -121,6 +121,7 @@ export const dict = { "ui.tool.read": "Ler", "ui.tool.loaded": "Carregado", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} de {{total}} linhas mantidas", // kilocode_change "ui.tool.list": "Listar", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 22f22d6ce5f..cf3f1a2a9c6 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -125,6 +125,7 @@ export const dict = { "ui.tool.read": "Čitanje", "ui.tool.loaded": "Učitano", + "ui.tool.swePruned": "SWE-Pruner · zadržano {{kept}} od {{total}} redova", // kilocode_change "ui.tool.list": "Listanje", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index be8b339bb0e..2587a192e4e 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -120,6 +120,7 @@ export const dict = { "ui.tool.read": "Læs", "ui.tool.loaded": "Indlæst", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} af {{total}} linjer beholdt", // kilocode_change "ui.tool.list": "Liste", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 219de6ae171..c47501afd3b 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -126,6 +126,7 @@ export const dict = { "ui.tool.read": "Lesen", "ui.tool.loaded": "Geladen", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} von {{total}} Zeilen behalten", // kilocode_change "ui.tool.list": "Auflisten", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index a5a790bfa72..d10d1a1b864 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -127,6 +127,7 @@ export const dict: Record = { "ui.tool.read": "Read", "ui.tool.loaded": "Loaded", + "ui.tool.swePruned": "SWE-Pruner · kept {{kept}} of {{total}} lines", // kilocode_change "ui.tool.list": "List", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 9abd48e2b32..f0dd59fbaa5 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -121,6 +121,7 @@ export const dict = { "ui.tool.read": "Leer", "ui.tool.loaded": "Cargado", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} de {{total}} líneas conservadas", // kilocode_change "ui.tool.list": "Listar", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 975c371349d..ab057725dab 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -121,6 +121,7 @@ export const dict = { "ui.tool.read": "Lire", "ui.tool.loaded": "Chargé", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} lignes conservées sur {{total}}", // kilocode_change "ui.tool.list": "Lister", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/it.ts b/packages/ui/src/i18n/it.ts index 1547357babf..41973bbdc48 100644 --- a/packages/ui/src/i18n/it.ts +++ b/packages/ui/src/i18n/it.ts @@ -131,6 +131,7 @@ export const dict: Record = { "ui.tool.read": "Leggi", "ui.tool.loaded": "Caricato", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} di {{total}} righe mantenute", // kilocode_change "ui.tool.list": "Elenco", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 62a9dbc2a58..49f124a0fff 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -120,6 +120,7 @@ export const dict = { "ui.tool.read": "読み込み", "ui.tool.loaded": "読み込み済み", + "ui.tool.swePruned": "SWE-Pruner · {{total}} 行中 {{kept}} 行を保持", // kilocode_change "ui.tool.list": "リスト", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index e38bd5a520a..e087064375c 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -121,6 +121,7 @@ export const dict = { "ui.tool.read": "읽기", "ui.tool.loaded": "로드됨", + "ui.tool.swePruned": "SWE-Pruner · {{total}}줄 중 {{kept}}줄 유지", // kilocode_change "ui.tool.list": "목록", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/nl.ts b/packages/ui/src/i18n/nl.ts index 820c9f4a3db..334af1ada93 100644 --- a/packages/ui/src/i18n/nl.ts +++ b/packages/ui/src/i18n/nl.ts @@ -134,6 +134,7 @@ export const dict: Record = { "ui.tool.read": "Lezen", "ui.tool.loaded": "Geladen", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} van {{total}} regels behouden", // kilocode_change "ui.tool.list": "Lijst", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 68d365c5b39..043eb298ba2 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -124,6 +124,7 @@ export const dict: Record = { "ui.tool.read": "Les", "ui.tool.loaded": "Lastet", + "ui.tool.swePruned": "SWE-Pruner · {{kept}} av {{total}} linjer beholdt", // kilocode_change "ui.tool.list": "Liste", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index c1c6fa1c56d..47b49754f1e 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -120,6 +120,7 @@ export const dict = { "ui.tool.read": "Odczyt", "ui.tool.loaded": "Załadowano", + "ui.tool.swePruned": "SWE-Pruner · zachowano {{kept}} z {{total}} wierszy", // kilocode_change "ui.tool.list": "Lista", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 05a100e1fa6..3bbe1a1d270 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -120,6 +120,7 @@ export const dict = { "ui.tool.read": "Чтение", "ui.tool.loaded": "Загружено", + "ui.tool.swePruned": "SWE-Pruner · сохранено {{kept}} из {{total}} строк", // kilocode_change "ui.tool.list": "Список", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 15060187b9e..dcc0dec7663 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -122,6 +122,7 @@ export const dict = { "ui.tool.read": "อ่าน", "ui.tool.loaded": "โหลดแล้ว", + "ui.tool.swePruned": "SWE-Pruner · เก็บไว้ {{kept}} จาก {{total}} บรรทัด", // kilocode_change "ui.tool.list": "รายการ", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index 7a3ae527ebb..e66437a4ac5 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -127,6 +127,7 @@ export const dict = { "ui.tool.read": "Oku", "ui.tool.loaded": "Yüklendi", + "ui.tool.swePruned": "SWE-Pruner · {{total}} satırdan {{kept}} tanesi korundu", // kilocode_change "ui.tool.list": "Listele", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/uk.ts b/packages/ui/src/i18n/uk.ts index 7155c8e84be..dfeece39ea8 100644 --- a/packages/ui/src/i18n/uk.ts +++ b/packages/ui/src/i18n/uk.ts @@ -139,6 +139,7 @@ export const dict: Record = { "ui.tool.read": "Читання", "ui.tool.loaded": "Завантажено", + "ui.tool.swePruned": "SWE-Pruner · збережено {{kept}} з {{total}} рядків", // kilocode_change "ui.tool.list": "Список", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 9f56af1ebee..f8f99c3ec13 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -124,6 +124,7 @@ export const dict = { "ui.tool.read": "读取", "ui.tool.loaded": "已加载", + "ui.tool.swePruned": "SWE-Pruner · 保留 {{total}} 行中的 {{kept}} 行", // kilocode_change "ui.tool.list": "列表", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index b495faf96b6..ae01cb1fdcc 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -124,6 +124,7 @@ export const dict = { "ui.tool.read": "讀取", "ui.tool.loaded": "已載入", + "ui.tool.swePruned": "SWE-Pruner · 保留 {{total}} 行中的 {{kept}} 行", // kilocode_change "ui.tool.list": "清單", "ui.tool.glob": "Glob", "ui.tool.grep": "Grep",