diff --git a/.changeset/refine-memory-controls.md b/.changeset/refine-memory-controls.md new file mode 100644 index 0000000000..97d452f7c2 --- /dev/null +++ b/.changeset/refine-memory-controls.md @@ -0,0 +1,8 @@ +--- +"@kilocode/cli": patch +"@kilocode/kilo-memory": patch +"@kilocode/kilo-ui": patch +"kilo-code": patch +--- + +Simplify project memory settings and activity visibility, replace direct editing with folder inspection, add nested memory slash-command completion and status views, improve empty-project handling, compact native tool-call summaries, and remove legacy memory audit logs. diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-200-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-200-chromium-linux.png deleted file mode 100644 index f7283e81eb..0000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-200-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a89d1a9a31f7de1780d772b7caf71d96f0b4b895df954c77dcd508a7febe7c9 -size 1950 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-chromium-linux.png deleted file mode 100644 index ca5db9f648..0000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-memory-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:431a0c12cb844b5d4e535cc4c7bf5546fd9e66a178b8023b00978d07c291a531 -size 3919 diff --git a/packages/kilo-memory/src/commands.ts b/packages/kilo-memory/src/commands.ts index 8fcdbb8d4c..0b7d48d453 100644 --- a/packages/kilo-memory/src/commands.ts +++ b/packages/kilo-memory/src/commands.ts @@ -2,13 +2,12 @@ export const MEMORY_COMMAND_CATALOG = [ { usage: "on", description: "Enable project memory" }, { usage: "off", description: "Disable project memory" }, { usage: "status", description: "Storage location and stored memory overview" }, - { usage: "show", description: "Full audit view (sources, index, changes, decisions)" }, + { usage: "show", description: "Stored project memory overview" }, { usage: "remember ", description: "Save a project memory note" }, { usage: "correct ", description: "Save a correction to project memory" }, { usage: "forget ", description: "Remove matching project memory" }, { usage: "auto on|off", description: "Turn automatic memory saves on or off" }, - { usage: "verbose on|off", description: "Turn verbose memory details on or off" }, - { usage: "edit", description: "Open project.md in $VISUAL/$EDITOR, then rebuild" }, + { usage: "inspect", description: "Reveal the project memory folder" }, { usage: "rebuild", description: "Rebuild the memory index from source files" }, { usage: "purge confirm", description: "Delete all project memory files" }, ] as const @@ -18,7 +17,7 @@ export const MEMORY_USAGE = `/memory [project] ${MEMORY_COMMAND_CATALOG.map((ite export const MEMORY_OPERATIONS = [ "enable", "status", - "edit", + "inspect", "disable", "rebuild", "remember", @@ -26,21 +25,13 @@ export const MEMORY_OPERATIONS = [ "forget", "purge", "auto", - "verbose", ] as const -export const MEMORY_PROMPT_OPERATIONS = ["remember", "forget"] as const - export type MemoryOperation = (typeof MEMORY_OPERATIONS)[number] -export type MemoryPromptOperation = (typeof MEMORY_PROMPT_OPERATIONS)[number] export function isMemoryOperation(input: unknown): input is MemoryOperation { return typeof input === "string" && (MEMORY_OPERATIONS as readonly string[]).includes(input) } -export function isMemoryPromptOperation(input: unknown): input is MemoryPromptOperation { - return typeof input === "string" && (MEMORY_PROMPT_OPERATIONS as readonly string[]).includes(input) -} - type Help = { kind: "help" } @@ -62,7 +53,7 @@ type Operation = } | { kind: "operation" - operation: "auto" | "verbose" + operation: "auto" mode: "on" | "off" } | { @@ -72,7 +63,7 @@ type Operation = } | { kind: "operation" - operation: Exclude + operation: Exclude } type Usage = { @@ -104,7 +95,7 @@ function usage(reason: string): ParsedMemoryCommand { function operation(verb: string, text: string): ParsedMemoryCommand | undefined { if (verb === "on" || verb === "enable") return { kind: "operation", operation: "enable" } if (verb === "off" || verb === "disable") return { kind: "operation", operation: "disable" } - if (verb === "status" || verb === "edit" || verb === "rebuild") { + if (verb === "status" || verb === "inspect" || verb === "rebuild") { return { kind: "operation", operation: verb } } if (verb === "purge") { @@ -116,11 +107,6 @@ function operation(verb: string, text: string): ParsedMemoryCommand | undefined if (mode === "on" || mode === "off") return { kind: "operation", operation: "auto", mode } return usage("Missing auto mode. Run /memory auto on or /memory auto off.") } - if (verb === "verbose") { - const mode = text.toLowerCase() - if (mode === "on" || mode === "off") return { kind: "operation", operation: "verbose", mode } - return usage("Missing verbose mode. Run /memory verbose on or /memory verbose off.") - } if (verb === "remember") { if (text) return { kind: "operation", operation: "remember", text } return usage("Missing text.") diff --git a/packages/kilo-memory/src/effect/index.ts b/packages/kilo-memory/src/effect/index.ts index ca94a98aa6..4a58e71bf3 100644 --- a/packages/kilo-memory/src/effect/index.ts +++ b/packages/kilo-memory/src/effect/index.ts @@ -46,7 +46,9 @@ export namespace KiloMemory { } export async function prepare(input: Input) { - return root(input) + const dir = root(input) + await MemoryFiles.cleanup(dir) + return dir } export async function status(input: Input) { diff --git a/packages/kilo-memory/src/storage/audit.ts b/packages/kilo-memory/src/storage/audit.ts index 1369ea1c67..70c7c42bf3 100644 --- a/packages/kilo-memory/src/storage/audit.ts +++ b/packages/kilo-memory/src/storage/audit.ts @@ -1,13 +1,7 @@ -import { appendFile, chmod } from "fs/promises" -import path from "path" import z from "zod" import { MemoryFs } from "./fs" -import { MemoryPaths } from "./paths" -import { MemoryRedact } from "../capture/redact" export namespace MemoryAudit { - const MAX_LOG = 128_000 - const LOG_MARGIN = 16_000 const Log = z .object({ kind: z.literal("log"), @@ -49,45 +43,10 @@ export namespace MemoryAudit { }[] } - function cap(input: string) { - if (Buffer.byteLength(input) <= MAX_LOG) return input - const lines = input.split("\n").reverse() - const kept: string[] = [] - lines.reduce((sum, line) => { - if (sum >= MAX_LOG) return sum - kept.push(line) - return sum + Buffer.byteLength(`${line}\n`) - }, 0) - return kept.reverse().join("\n") - } - - async function line(file: string, text: string) { - await MemoryFs.dir(path.dirname(file)) - const info = await MemoryFs.guard(file) - if (info && !info.isFile()) throw new Error(`memory path is not a file: ${file}`) - await appendFile(file, text, { mode: MemoryFs.FILE }) - await chmod(file, MemoryFs.FILE).catch((error: unknown) => { - if (process.platform === "win32") return - throw error - }) - const next = await MemoryFs.guard(file) - if (!next?.isFile()) throw new Error(`memory path is not a file: ${file}`) - if (next.size <= MAX_LOG + LOG_MARGIN) return - await MemoryFs.write(file, cap((await MemoryFs.read(file)) ?? "")) - } - - async function audit(root: string, input: Decision) { - const data = MemoryRedact.value(input) as Decision - await MemoryFs.queue(root, () => - line( - MemoryPaths.files(root).decisions, - `${JSON.stringify({ - v: 1, - time: new Date().toISOString(), - ...data, - })}\n`, - ), - ) + function audit(root: string, input: Decision) { + void root + void input + return Promise.resolve() } export async function append(root: string, text: string) { @@ -99,12 +58,8 @@ export namespace MemoryAudit { } export async function readDecisions(root: string) { - return MemoryFs.read(MemoryPaths.files(root).decisions) - .then((text) => text ?? "") - .catch((error: unknown) => { - if (MemoryFs.miss(error)) return "" - throw error - }) + void root + return "" } function record(input: string) { diff --git a/packages/kilo-memory/src/storage/fs.ts b/packages/kilo-memory/src/storage/fs.ts index 3b0130ff18..e9df6dcf17 100644 --- a/packages/kilo-memory/src/storage/fs.ts +++ b/packages/kilo-memory/src/storage/fs.ts @@ -109,6 +109,15 @@ export namespace MemoryFs { return readFile(file, "utf8") } + export async function remove(file: string) { + await parents(path.dirname(file)) + const info = await guard(file) + if (!info) return false + if (!info.isFile()) throw new Error(`memory path is not a file: ${file}`) + await rm(file, { force: true }) + return true + } + export async function json(file: string) { const text = await read(file) return text === undefined ? undefined : JSON.parse(text) diff --git a/packages/kilo-memory/src/storage/state.ts b/packages/kilo-memory/src/storage/state.ts index e3e1728ec3..bb373e2461 100644 --- a/packages/kilo-memory/src/storage/state.ts +++ b/packages/kilo-memory/src/storage/state.ts @@ -10,6 +10,9 @@ import { MemoryText } from "../text" import { MemoryTopics } from "../recall/topics" export namespace MemoryState { + const CLEAN_LIMIT = 128 + const CLEAN_RETRY_MS = 60_000 + const cleaned = new Map() const seed: Record = { "project.md": "# Project Memory\n\n## Facts\n\n## Decisions\n\n## Constraints\n\n## Open Questions\n", "environment.md": "# Environment Memory\n\n## Commands\n\n## Paths\n\n## Tooling\n", @@ -92,6 +95,38 @@ export namespace MemoryState { ) } + export async function cleanup(root: string) { + const retry = cleaned.get(root) + if (retry !== undefined && retry > Date.now()) return false + cleaned.delete(root) + const owns = await owned(root).catch((error: unknown) => { + MemoryFs.warn("failed to inspect legacy memory audit", { error, root }) + return undefined + }) + if (owns !== true) { + if (owns === undefined) cache(root, Date.now() + CLEAN_RETRY_MS) + return false + } + const removed = await MemoryFs.remove(MemoryPaths.files(root).decisions).catch((error: unknown) => { + MemoryFs.warn("failed to remove legacy memory audit", { error, root }) + return undefined + }) + if (removed === undefined) { + cache(root, Date.now() + CLEAN_RETRY_MS) + return false + } + cache(root, Number.POSITIVE_INFINITY) + return removed + } + + function cache(root: string, retry: number) { + cleaned.delete(root) + cleaned.set(root, retry) + if (cleaned.size <= CLEAN_LIMIT) return + const key = cleaned.keys().next().value + if (typeof key === "string") cleaned.delete(key) + } + export async function readIndex(root: string) { const file = MemoryPaths.files(root).index return MemoryFs.read(file) @@ -218,8 +253,9 @@ export namespace MemoryState { index: await readIndex(root), inventory, items: await inspect(root, inventory), - changes: await MemoryAudit.readChanges(root), - decisions: await MemoryAudit.readDecisions(root), + // Retain empty fields for wire compatibility while the legacy audit file is removed. + changes: "", + decisions: "", } } diff --git a/packages/kilo-memory/src/storage/store.ts b/packages/kilo-memory/src/storage/store.ts index c7ca4be45d..8d79568f6c 100644 --- a/packages/kilo-memory/src/storage/store.ts +++ b/packages/kilo-memory/src/storage/store.ts @@ -27,6 +27,7 @@ export namespace MemoryFiles { export const indexExpired = MemoryState.indexExpired export const scaffold = MemoryState.scaffold export const owned = MemoryState.owned + export const cleanup = MemoryState.cleanup export const writeSession = MemorySessions.writeSession export const readSession = MemorySessions.readSession diff --git a/packages/kilo-memory/test/command-cases.json b/packages/kilo-memory/test/command-cases.json index 1992f2dc48..dea1a43042 100644 --- a/packages/kilo-memory/test/command-cases.json +++ b/packages/kilo-memory/test/command-cases.json @@ -26,10 +26,10 @@ "operation": "status" }, { - "name": "edit operation", - "input": "/memory edit", + "name": "inspect operation", + "input": "/memory inspect", "result": "operation", - "operation": "edit" + "operation": "inspect" }, { "name": "on operation", @@ -94,26 +94,6 @@ "operation": "auto", "mode": "off" }, - { - "name": "verbose mode usage", - "input": "/memory verbose", - "result": "usage", - "reason": "Missing verbose mode" - }, - { - "name": "verbose on operation", - "input": "/memory verbose on", - "result": "operation", - "operation": "verbose", - "mode": "on" - }, - { - "name": "verbose off operation", - "input": "/memory verbose off", - "result": "operation", - "operation": "verbose", - "mode": "off" - }, { "name": "remember operation keeps text", "input": "/memory remember use bun test from packages/opencode", @@ -148,12 +128,6 @@ "operation": "auto", "mode": "off" }, - { - "name": "inspect action is unknown", - "input": "/memory inspect", - "result": "usage", - "reason": "Unknown memory action" - }, { "name": "unknown action", "input": "/memory wat", diff --git a/packages/kilo-memory/test/commands.test.ts b/packages/kilo-memory/test/commands.test.ts index b91d4baad8..c5661b137a 100644 --- a/packages/kilo-memory/test/commands.test.ts +++ b/packages/kilo-memory/test/commands.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { parseMemoryCommand, type MemoryOperation, type ParsedMemoryCommand } from "../src/commands" +import { MEMORY_USAGE, parseMemoryCommand, type MemoryOperation, type ParsedMemoryCommand } from "../src/commands" type Case = { name: string @@ -29,7 +29,7 @@ function expected(item: Case): ParsedMemoryCommand | undefined { if (!item.query) throw new Error(`Missing query for fixture: ${item.name}`) return { kind: "operation", operation: item.operation, query: item.query } } - if (item.operation === "auto" || item.operation === "verbose") { + if (item.operation === "auto") { if (!item.mode) throw new Error(`Missing mode for fixture: ${item.name}`) return { kind: "operation", operation: item.operation, mode: item.mode } } @@ -41,6 +41,23 @@ function expected(item: Case): ParsedMemoryCommand | undefined { } describe("memory commands", () => { + test("does not expose verbose mode", () => { + expect(MEMORY_USAGE).not.toContain("verbose") + expect(parseMemoryCommand("/memory verbose on")).toEqual({ + kind: "usage", + reason: "Unknown memory action: verbose.", + }) + }) + + test("replaces edit with inspect", () => { + expect(MEMORY_USAGE).toContain("inspect") + expect(MEMORY_USAGE).not.toContain("edit") + expect(parseMemoryCommand("/memory edit")).toEqual({ + kind: "usage", + reason: "Unknown memory action: edit.", + }) + }) + test("parse shared fixtures", () => { for (const item of cases) { const parsed = parseMemoryCommand(item.input) diff --git a/packages/kilo-memory/test/core.test.ts b/packages/kilo-memory/test/core.test.ts index 388dcb6c1b..04b52a0362 100644 --- a/packages/kilo-memory/test/core.test.ts +++ b/packages/kilo-memory/test/core.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect, spyOn, test } from "bun:test" import { mkdir, mkdtemp, readdir, rm, symlink, utimes, writeFile } from "fs/promises" import os from "os" import path from "path" @@ -10,6 +10,7 @@ import { MemoryOperations } from "../src/capture/operations" import { MemoryPaths } from "../src/storage/paths" import { MemoryRecall } from "../src/recall/recall" import { MemorySchema } from "../src/schema" +import { KiloMemory } from "../src/effect/index" async function tmp() { const dir = await mkdtemp(path.join(os.tmpdir(), "kilo-memory-")) @@ -43,13 +44,119 @@ describe("memory core package", () => { expect(shown.sources.corrections).toContain("## Corrections") expect(await Bun.file(path.join(t.root, ".gitignore")).text()).toBe("*\n!.gitignore\n") expect(shown.index).toBe("") + expect(shown.changes).toBe("") + expect(shown.decisions).toBe("") + expect(await Bun.file(path.join(t.root, "decisions.jsonl")).exists()).toBe(false) + }) + }) + + test("prepare removes legacy decisions once from owned memory roots", async () => { + await use(async (t) => { + await Memory.enable({ root: t.root }) + const legacy = path.join(t.root, "decisions.jsonl") + await writeFile(legacy, '{"kind":"log"}\n') + + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(legacy).exists()).toBe(false) + + await writeFile(legacy, '{"kind":"log"}\n') + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(legacy).exists()).toBe(true) + + const other = path.join(t.dir, "unowned") + await mkdir(other) + const file = path.join(other, "decisions.jsonl") + await writeFile(file, '{"kind":"log"}\n') + + await KiloMemory.status({ root: other }) + expect(await Bun.file(file).exists()).toBe(true) + }) + }) + + test("prepare ignores legacy decisions cleanup failures", async () => { + const clock = spyOn(Date, "now") + const now = Date.now() + clock.mockReturnValue(now) + try { + await use(async (t) => { + await Memory.enable({ root: t.root }) + const legacy = path.join(t.root, "decisions.jsonl") + await mkdir(legacy) + + const status = await KiloMemory.status({ root: t.root }) + expect(status.state.enabled).toBe(true) + + await rm(legacy, { recursive: true }) + await writeFile(legacy, '{"kind":"log"}\n') + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(legacy).exists()).toBe(true) + + clock.mockReturnValue(now + 60_001) + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(legacy).exists()).toBe(false) + }) + } finally { + clock.mockRestore() + } + }) + + test("prepare ignores corrupt manifests during legacy cleanup", async () => { + const clock = spyOn(Date, "now") + const now = Date.now() + clock.mockReturnValue(now) + try { + await use(async (t) => { + await Memory.enable({ root: t.root }) + const paths = MemoryPaths.files(t.root) + await writeFile(paths.manifest, "{") + + const status = await KiloMemory.status({ root: t.root }) + expect(status.state.enabled).toBe(true) + + await writeFile(paths.manifest, '{"kind":"kilo-memory","version":1}\n') + await writeFile(paths.decisions, '{"kind":"log"}\n') + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(paths.decisions).exists()).toBe(true) + + clock.mockReturnValue(now + 60_001) + await KiloMemory.status({ root: t.root }) + expect(await Bun.file(paths.decisions).exists()).toBe(false) + }) + } finally { + clock.mockRestore() + } + }) + + test("legacy cleanup cache evicts older roots", async () => { + await use(async (t) => { + const first = path.join(t.dir, "cache-0") + await mkdir(first) + await MemoryFiles.writeManifest(first) + await MemoryFiles.cleanup(first) + const legacy = MemoryPaths.files(first).decisions + await writeFile(legacy, '{"kind":"log"}\n') + + for (let i = 1; i <= 128; i++) { + const root = path.join(t.dir, `cache-${i}`) + await mkdir(root) + await MemoryFiles.writeManifest(root) + await MemoryFiles.cleanup(root) + } + + await MemoryFiles.cleanup(first) + expect(await Bun.file(legacy).exists()).toBe(false) }) }) test("enable preserves existing memory settings", async () => { await use(async (t) => { const enabled = await Memory.enable({ root: t.root }) - await MemoryFiles.writeState(t.root, { ...enabled.state, autoInject: false, autoConsolidate: false, verbose: true }) + await MemoryFiles.writeState(t.root, { + ...enabled.state, + autoInject: false, + autoConsolidate: false, + verbose: true, + }) const next = await Memory.enable({ root: t.root }) @@ -111,43 +218,7 @@ describe("memory core package", () => { }) }) - test("decision and change audit records redact secret-like text in one log", async () => { - await use(async (t) => { - const secret = "sk-abcdefghijklmnopqrstuvwxyz123456" - await Memory.enable({ root: t.root }) - await MemoryFiles.decide(t.root, { - kind: "recall", - result: "skipped", - query: `check api_key=${secret}`, - skipped: [{ reason: "secret", text: `password=hunter2 ${secret}` }], - }) - await MemoryFiles.append(t.root, `provider error "api_key": "${secret}"`) - const shown = await Memory.show({ root: t.root }) - - expect(shown.decisions).toContain("[redacted]") - expect(shown.decisions).toContain('"kind":"log"') - expect(shown.decisions).not.toContain(secret) - expect(shown.decisions).not.toContain("hunter2") - expect(shown.changes).toContain("[redacted]") - expect(shown.decisions).toContain("provider error") - }) - }) - - test("targeted recall redacts query before decision truncation", async () => { - await use(async (t) => { - const secret = "sk-" + "a".repeat(40) - await Memory.enable({ root: t.root }) - - await Memory.recall({ root: t.root, query: "x".repeat(220) + secret }) - const shown = await Memory.show({ root: t.root }) - - expect(shown.decisions).toContain("[redacted]") - expect(shown.decisions).not.toContain(secret) - expect(shown.decisions).not.toContain(secret.slice(0, 20)) - }) - }) - - test("stale locks are stolen before appending audit records", async () => { + test("stale locks are stolen before applying memory", async () => { await use(async (t) => { await Memory.enable({ root: t.root }) const lock = path.join(t.root, ".lock") @@ -155,10 +226,10 @@ describe("memory core package", () => { await mkdir(lock) await utimes(lock, old, old) - await MemoryFiles.append(t.root, "after stale lock") + await Memory.apply({ root: t.root, ops: [{ action: "add", key: "after_lock", text: "Stale locks recover." }] }) const shown = await Memory.show({ root: t.root }) - expect(shown.changes).toContain("after stale lock") + expect(shown.sources.project).toContain("after_lock") }) }) @@ -174,7 +245,6 @@ describe("memory core package", () => { expect(state.enabled).toBe(false) expect(files.some((file) => file.startsWith("state.json.bad-"))).toBe(true) expect(shown.inventory.items).toEqual({}) - expect(shown.changes).toContain("recover state.json") }) }) @@ -280,7 +350,6 @@ describe("memory core package", () => { const shown = await Memory.show({ root: t.root }) expect(mixed.result.added).toBe(1) - // The skip record is redacted: it flows into the persistent decisions audit. expect(mixed.result.skipped).toContainEqual({ reason: "secret", text: "[redacted]" }) expect(JSON.stringify(mixed.result.skipped)).not.toContain("sk-abcdefghijklmnopqrstuvwxyz") expect(shown.sources.project).toContain("safe_fact") @@ -335,7 +404,6 @@ describe("memory core package", () => { ]) expect(shown.sources.project).not.toContain("memory_echo") expect(shown.index).not.toContain("memory_echo") - expect(shown.decisions).toContain('"reason":"self_referential"') }) }) @@ -372,17 +440,10 @@ describe("memory core package", () => { expect(shown.sources.project).not.toContain("Vim keybindings") expect(shown.index).toContain("repo_style") expect(shown.index).not.toContain("reply_style") - expect(shown.decisions).toContain('"reason":"out_of_scope"') - expect(shown.decisions).not.toContain("reply_style") - expect(shown.decisions).not.toContain("theme") - expect(shown.decisions).not.toContain("editor") - expect(shown.decisions).not.toContain("I prefer terse summaries") - expect(shown.decisions).not.toContain("dark mode") - expect(shown.decisions).not.toContain("Vim keybindings") }) }) - test("out-of-scope secret ops stay out of the operations audit", async () => { + test("out-of-scope secret ops stay out of memory", async () => { await use(async (t) => { await Memory.enable({ root: t.root }) @@ -393,9 +454,8 @@ describe("memory core package", () => { const shown = await Memory.show({ root: t.root }) expect(result.result.skipped).toEqual([{ reason: "out_of_scope", text: "My preference is [redacted]" }]) - expect(shown.decisions).toContain('"reason":"out_of_scope"') - expect(shown.decisions).not.toContain("private_pref") - expect(shown.decisions).not.toContain("password=hunter2") + expect(shown.sources.project).not.toContain("private_pref") + expect(shown.sources.project).not.toContain("password=hunter2") }) }) @@ -478,7 +538,7 @@ describe("memory core package", () => { }) }) - test("targeted recall returns typed memory and audits matched files", async () => { + test("targeted recall returns typed memory and matched files", async () => { await use(async (t) => { await Memory.enable({ root: t.root }) await Memory.remember({ @@ -490,12 +550,8 @@ describe("memory core package", () => { }) const result = await Memory.recall({ root: t.root, query: "what command runs cli tests?" }) - const shown = await Memory.show({ root: t.root }) - expect(result.result?.block).toContain("cli_tests") expect(result.files).toEqual(["environment.md"]) - expect(shown.decisions).toContain('"kind":"recall"') - expect(shown.decisions).toContain('"result":"recalled"') }) }) diff --git a/packages/kilo-memory/test/effect-capture.test.ts b/packages/kilo-memory/test/effect-capture.test.ts index ae1eaea096..91e0ec84e0 100644 --- a/packages/kilo-memory/test/effect-capture.test.ts +++ b/packages/kilo-memory/test/effect-capture.test.ts @@ -80,7 +80,7 @@ function run(input: { } describe("MemoryCapture (fake ports)", () => { - test("turn-close typed LLM saves environment memory and audit records", async () => { + test("turn-close typed LLM saves environment memory", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -102,9 +102,6 @@ describe("MemoryCapture (fake ports)", () => { const shown = await KiloMemory.show({ root: t.root }) expect(shown.sources.environment).toContain("cli_memory_tests") - expect(shown.decisions).toContain('"kind":"digest"') - expect(shown.decisions).toContain('"kind":"typed"') - expect(shown.decisions).toContain('"result":"saved"') } finally { await t.done() } @@ -138,9 +135,6 @@ describe("MemoryCapture (fake ports)", () => { const shown = await KiloMemory.show({ root: t.root }) expect(shown.sources.environment).toContain("cli_tests") expect(shown.sources.environment).not.toContain(secret) - expect(shown.decisions).toContain('"reason":"secret"') - // The audit record itself must not carry the raw secret (decisions are exposed via /memory/show). - expect(shown.decisions).not.toContain(secret) const detail = events.find((item) => item.detail?.type === "saved")?.detail expect(detail?.message).toContain("environment.md:cli_tests") expect(detail?.message).not.toContain(secret) @@ -173,18 +167,15 @@ describe("MemoryCapture (fake ports)", () => { sessionID: "ses_effect", max: MemorySchema.maxStoredDigestSummary, }) - const shown = await KiloMemory.show({ root: t.root }) expect(saved?.summary).toContain("[redacted]") expect(saved?.summary).not.toContain(secret) expect(saved?.summary).not.toContain(secret.slice(0, 20)) - expect(shown.decisions).not.toContain(secret) - expect(shown.decisions).not.toContain(secret.slice(0, 20)) } finally { await t.done() } }) - test("turn-close surfaces content-gate rejections in the audit with redacted text", async () => { + test("turn-close rejects self-referential content while applying safe operations", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -206,10 +197,6 @@ describe("MemoryCapture (fake ports)", () => { expect(result).toMatchObject({ skipped: false, operationCount: 1 }) const shown = await KiloMemory.show({ root: t.root }) expect(shown.sources.project).not.toContain("gate_check") - // The apply-time content gate is visible in the audit, and its recorded text is redacted. - expect(shown.decisions).toContain('"reason":"self_referential"') - expect(shown.decisions).toContain("[redacted]") - expect(shown.decisions).not.toContain("password=hunter2") } finally { await t.done() } @@ -346,7 +333,7 @@ describe("MemoryCapture (fake ports)", () => { } }) - test("interrupted close records a non-LLM fallback digest tagged with the reason", async () => { + test("interrupted close records a non-LLM fallback digest", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -367,9 +354,6 @@ describe("MemoryCapture (fake ports)", () => { const raw = await Bun.file(path.join(MemoryPaths.files(t.root).sessions, file)).text() expect(saved?.fallback).toBe(true) expect(raw).toContain("Fallback: true") - const shown = await KiloMemory.show({ root: t.root }) - expect(shown.decisions).toContain("session digest fallback on interrupted") - expect(shown.decisions).toContain('"fallback":true') } finally { await t.done() } @@ -457,7 +441,7 @@ describe("MemoryCapture (fake ports)", () => { } }) - test("template echo digest output falls back and records template_echo", async () => { + test("template echo digest output falls back", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -473,16 +457,13 @@ describe("MemoryCapture (fake ports)", () => { }) const saved = await MemoryFiles.readSession(t.root, { sessionID: "ses_effect", max: 480 }) - const shown = await KiloMemory.show({ root: t.root }) expect(saved?.fallback).toBe(true) - expect(shown.decisions).toContain('"reason":"template_echo"') - expect(shown.decisions).toContain('"fallback":true') } finally { await t.done() } }) - test("empty digest output falls back and records empty_digest", async () => { + test("empty digest output falls back", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -498,11 +479,8 @@ describe("MemoryCapture (fake ports)", () => { }) const saved = await MemoryFiles.readSession(t.root, { sessionID: "ses_effect", max: 480 }) - const shown = await KiloMemory.show({ root: t.root }) expect(saved?.fallback).toBe(true) expect(saved?.summary).toContain("User:") - expect(shown.decisions).toContain('"reason":"empty_digest"') - expect(shown.decisions).toContain('"fallback":true') } finally { await t.done() } @@ -603,7 +581,7 @@ describe("MemoryCapture (fake ports)", () => { } }) - test("records audit when configured memory model is unavailable", async () => { + test("configured memory model fallback still captures memory", async () => { const t = await tmp() try { await KiloMemory.enable({ root: t.root }) @@ -620,8 +598,8 @@ describe("MemoryCapture (fake ports)", () => { }), }) - const shown = await KiloMemory.show({ root: t.root }) - expect(shown.changes).toContain("memory_model_config reason=model unavailable fallback=1") + const saved = await MemoryFiles.readSession(t.root, { sessionID: "ses_effect", max: 480 }) + expect(saved?.summary).toContain("Explored repo setup") } finally { await t.done() } diff --git a/packages/kilo-ui/src/components/basic-tool.css b/packages/kilo-ui/src/components/basic-tool.css index 49ed771af5..ce3007d750 100644 --- a/packages/kilo-ui/src/components/basic-tool.css +++ b/packages/kilo-ui/src/components/basic-tool.css @@ -7,11 +7,15 @@ } [data-slot="basic-tool-tool-info"] { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; font-size: var(--kilo-font-size-12); text-align: start; } [data-slot="basic-tool-tool-info-structured"] { + width: 100%; min-width: 0; overflow: hidden; } @@ -25,6 +29,7 @@ } [data-slot="basic-tool-tool-info-main"] { + width: 100%; align-items: baseline; overflow: hidden; } @@ -34,6 +39,7 @@ } [data-slot="basic-tool-tool-subtitle"] { + flex: 1 1 auto; font-size: var(--kilo-font-size-12); color: var(--text-weak); } @@ -51,6 +57,8 @@ } [data-slot="basic-tool-tool-arg"] { + flex: 0 1 auto; + max-width: 24ch; font-size: var(--kilo-font-size-12); color: var(--text-weak); } diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 131874b564..ae7c0a24c9 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -1099,7 +1099,7 @@ function McpTool(props: ToolProps) { if (typeof value === "boolean") return [`${key}=${value}`] return [] }) - .slice(0, 3) + .slice(0, 1) } const formatted = createMemo(() => { diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 03c2ebb000..abea4fe311 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -3962,7 +3962,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper detail, }) } - void this.memory.fetch(sessionID, false) + void this.memory.fetch(sessionID) } return } diff --git a/packages/kilo-vscode/src/kilo-provider/memory.ts b/packages/kilo-vscode/src/kilo-provider/memory.ts index 136d977da5..c234022224 100644 --- a/packages/kilo-vscode/src/kilo-provider/memory.ts +++ b/packages/kilo-vscode/src/kilo-provider/memory.ts @@ -1,11 +1,5 @@ import * as vscode from "vscode" -import * as path from "node:path" -import { - isMemoryOperation, - isMemoryPromptOperation, - type MemoryOperation, - type MemoryPromptOperation, -} from "@kilocode/kilo-memory/commands" +import { isMemoryOperation, type MemoryOperation } from "@kilocode/kilo-memory/commands" import { MemorySchema } from "@kilocode/kilo-memory/schema" import type { KiloClient, Session } from "@kilocode/sdk/v2/client" import { retry } from "../services/cli-backend/retry" @@ -14,6 +8,7 @@ import { getErrorMessage } from "../kilo-provider-utils" type MemorySourceFile = MemorySchema.Source type MemoryApi = KiloClient["memory"] const CACHE_LIMIT = 8 +const STORED_LIMIT = 16 const NO_PROJECT = "No active project for memory. Open a file in the target folder to manage its memory." export type KiloProviderMemoryMessage = { @@ -53,6 +48,20 @@ function memory(client: KiloClient | undefined): MemoryApi | undefined { return (client as { memory?: MemoryApi } | undefined)?.memory } +function count(text: string) { + return text.split("\n").filter((line) => line.trim().startsWith("- ")).length +} + +function stored(text: string) { + return text + .split("\n") + .filter((line) => line.trim()) + .map((line) => { + const marker = line.indexOf(":: ") + return marker === -1 ? line : line.slice(marker + 3) + }) +} + function request(input: Record): { value: KiloProviderMemoryMessage } | { error: string } { const op = operation(input.operation) if (!op) return { error: "Unknown memory operation" } @@ -102,14 +111,16 @@ export class KiloProviderMemory { async handle(message: Record): Promise { if (message.type === "requestMemory") { - this.fetch( - typeof message.sessionID === "string" ? message.sessionID : undefined, - message.includeSources === true, - ).catch((err: unknown) => console.error("[Kilo New] fetchAndSendMemory failed:", err)) + this.fetch(typeof message.sessionID === "string" ? message.sessionID : undefined).catch((err: unknown) => + console.error("[Kilo New] fetchAndSendMemory failed:", err), + ) return true } if (message.type === "memoryShow") { - await this.show(typeof message.sessionID === "string" ? message.sessionID : undefined) + await this.show( + typeof message.sessionID === "string" ? message.sessionID : undefined, + message.mode === "status" ? "status" : "show", + ) return true } if (message.type === "memoryOperation") { @@ -127,17 +138,11 @@ export class KiloProviderMemory { await this.run(parsed.value) return true } - if (message.type === "memoryPrompt") { - const op = isMemoryPromptOperation(message.operation) ? message.operation : undefined - if (!op) return true - await this.prompt(op, typeof message.sessionID === "string" ? message.sessionID : undefined) - return true - } return false } - fetch(sessionID?: string, includeSources = false): Promise { - return this.serial(() => this.load(sessionID, includeSources)) + fetch(sessionID?: string): Promise { + return this.serial(() => this.load(sessionID)) } /** Resolves once the serialized operation queue has drained. */ @@ -145,7 +150,7 @@ export class KiloProviderMemory { return this.tail } - private async load(sessionID?: string, includeSources = false): Promise { + private async load(sessionID?: string): Promise { try { const directory = this.input.dir(sessionID ?? this.input.session()?.id) const client = this.input.client() @@ -168,14 +173,10 @@ export class KiloProviderMemory { } const { data: status } = await retry(() => api.status({ directory }, { throwOnError: true })) - const show = includeSources - ? (await retry(() => api.show({ directory }, { throwOnError: true }))).data - : undefined const msg = { type: "memoryLoaded", sessionID, status, - ...(show ? { show } : {}), } this.cache(directory, msg) this.input.post(msg) @@ -189,27 +190,11 @@ export class KiloProviderMemory { } } - async prompt(value: MemoryPromptOperation, sessionID?: string): Promise { - const title = value === "remember" ? "Remember in project memory" : "Forget project memory" - const placeHolder = value === "remember" ? "Project fact, command, or correction" : "Text to remove" - const text = await vscode.window.showInputBox({ title, placeHolder, ignoreFocusOut: true }) - if (!text?.trim()) { - // Clear the webview's pending state for this action when the input is dismissed. - this.input.post({ type: "memoryOperationResult", operation: value, sessionID, ok: true }) - return - } - await this.run({ - operation: value, - sessionID, - ...(value === "remember" ? { text: text.trim() } : { query: text.trim() }), - }) + show(sessionID?: string, mode: "status" | "show" = "show"): Promise { + return this.serial(() => this.doShow(sessionID, mode)) } - show(sessionID?: string): Promise { - return this.serial(() => this.doShow(sessionID)) - } - - private async doShow(sessionID?: string): Promise { + private async doShow(sessionID: string | undefined, mode: "status" | "show"): Promise { const client = this.input.client() if (!client) { this.input.post({ @@ -237,55 +222,56 @@ export class KiloProviderMemory { this.input.post({ type: "memoryLoaded", sessionID, error: NO_PROJECT }) return } - const { data: show } = await retry(() => api.show({ directory }, { throwOnError: true })) - const { data: status } = await retry(() => api.status({ directory }, { throwOnError: true })) - const current = sessionID ?? this.input.session()?.id - const startup = - current && status.state.stats.lastInjectedSessionID === current ? status.state.stats.lastInjectedTokens : 0 - const content = [ - "# Kilo Memory", - "", - `Root: ${show.root}`, - `Enabled: ${show.state.enabled ? "yes" : "no"}`, - `Auto-save: ${show.state.autoConsolidate ? "on" : "off"}`, - `Startup context: ${show.state.autoInject ? "on" : "off"}`, - `Stored index tokens: ${status.index.estimatedTokens}`, - `Startup context tokens for this session: ${startup}`, - `Last auto-save model usage: ${status.state.stats.lastConsolidationTokens} tokens`, - "", - "## project.md", - show.sources.project.trim(), - "", - "## environment.md", - show.sources.environment.trim(), - "", - "## corrections.md", - show.sources.corrections.trim(), - "", - "## index.kmem", - show.index.trim(), - "", - "## items", - show.items.trim(), - "", - "## changes", - show.changes.trim(), - "", - "## decisions.jsonl", - show.decisions.trim(), - "", - ].join("\n") - await vscode.workspace - .openTextDocument({ content, language: "markdown" }) - .then((doc) => vscode.window.showTextDocument(doc, { preview: true })) + const [{ data: show }, { data: status }] = await Promise.all([ + retry(() => api.show({ directory }, { throwOnError: true })), + retry(() => api.status({ directory }, { throwOnError: true })), + ]) const msg = { type: "memoryLoaded", sessionID, status, - show, } this.cache(directory, msg) this.input.post(msg) + const items = stored(show.items) + if (mode === "show" && items.length === 0) { + void vscode.window.showInformationMessage( + "This project doesn't have any memory yet. It will start showing after you use Kilo.", + ) + return + } + const entries: vscode.QuickPickItem[] = [ + { + label: `${status.state.enabled ? "Enabled" : "Disabled"} · ${status.state.scope}`, + description: status.state.autoConsolidate ? "Auto-save on" : "Auto-save off", + }, + { label: "Storage", detail: status.root }, + { + label: "Sources", + description: `project.md ${count(show.sources.project)} · environment.md ${count(show.sources.environment)} · corrections.md ${count(show.sources.corrections)}`, + }, + { + label: "Index", + description: `${status.index.estimatedTokens.toLocaleString()} estimated tokens`, + }, + ] + if (mode === "show") { + const shown = items.slice(0, STORED_LIMIT) + entries.push( + { + label: "Stored memory", + description: + shown.length < items.length ? `${shown.length} of ${items.length} shown` : `${shown.length} shown`, + }, + ...shown.map((label) => ({ label })), + ) + } + void vscode.window.showQuickPick(entries, { + title: mode === "show" ? "Memory" : "Memory status", + placeHolder: mode === "show" ? "Stored project memory" : "Project memory status", + matchOnDescription: true, + matchOnDetail: true, + }) } catch (err) { console.error("[Kilo New] KiloProvider: Failed to show memory:", err) this.input.post({ @@ -358,31 +344,28 @@ export class KiloProviderMemory { return false } const data = await this.action(api, directory, message) - const refreshed = await Promise.all([ - retry(() => api.status({ directory }, { throwOnError: true })), - retry(() => api.show({ directory }, { throwOnError: true })), - ]).catch((err: unknown) => { - console.warn("[Kilo New] Memory changed but refresh failed:", err) - return undefined - }) - const status = refreshed?.[0].data - const show = refreshed?.[1].data + const refreshed = + message.operation === "status" + ? { data } + : await retry(() => api.status({ directory }, { throwOnError: true })).catch((err: unknown) => { + console.warn("[Kilo New] Memory changed but refresh failed:", err) + return undefined + }) + const status = refreshed?.data const result = { type: "memoryOperationResult", operation: message.operation, sessionID: message.sessionID, ok: true, ...(status ? { status } : {}), - ...(show ? { show } : {}), result: data, } this.input.post(result) - if (status && show) { + if (status) { const loaded = { type: "memoryLoaded", sessionID: message.sessionID, status, - show, } this.cache(directory, loaded) this.input.post(loaded) @@ -409,12 +392,11 @@ export class KiloProviderMemory { const op = message.operation if (op === "enable") return (await api.enable({ directory }, { throwOnError: true })).data if (op === "status") return (await api.status({ directory }, { throwOnError: true })).data - if (op === "edit") return this.edit(api, directory) + if (op === "inspect") return this.inspect(api, directory) if (op === "disable") return (await api.disable({ directory }, { throwOnError: true })).data if (op === "rebuild") return (await api.rebuild({ directory }, { throwOnError: true })).data if (op === "purge") return this.purge(api, directory, message) if (op === "auto") return this.auto(api, directory, message) - if (op === "verbose") return this.verbose(api, directory, message) if (op === "remember") return this.remember(api, directory, message) if (op === "correct") return this.correct(api, directory, message) return this.forget(api, directory, message) @@ -460,12 +442,10 @@ export class KiloProviderMemory { return (await api.forget({ directory, query, sessionID: message.sessionID }, { throwOnError: true })).data } - private async edit(api: MemoryApi, directory: string) { + private async inspect(api: MemoryApi, directory: string) { const { data: status } = await retry(() => api.status({ directory }, { throwOnError: true })) if (!status.state.enabled) throw new Error("Memory is disabled. Run /memory on first.") - const uri = vscode.Uri.file(path.join(status.root, "project.md")) - const doc = await vscode.workspace.openTextDocument(uri) - await vscode.window.showTextDocument(doc, { preview: false }) + await vscode.commands.executeCommand("revealFileInOS", vscode.Uri.file(status.root)) return status } @@ -481,11 +461,4 @@ export class KiloProviderMemory { } throw new Error("Auto-save mode is required") } - - private async verbose(api: MemoryApi, directory: string, message: KiloProviderMemoryMessage) { - if (message.mode === "on" || message.mode === "off") { - return (await api.configure({ directory, verbose: message.mode === "on" }, { throwOnError: true })).data - } - throw new Error("Verbose mode is required") - } } diff --git a/packages/kilo-vscode/tests/setup/vscode-mock.ts b/packages/kilo-vscode/tests/setup/vscode-mock.ts index a5914f7bff..9d01986cc8 100644 --- a/packages/kilo-vscode/tests/setup/vscode-mock.ts +++ b/packages/kilo-vscode/tests/setup/vscode-mock.ts @@ -88,6 +88,7 @@ const mockVscode = { tabGroups: { all: [] }, showTextDocument: async () => {}, showInformationMessage: async () => undefined, + showQuickPick: async () => undefined, showErrorMessage: async () => undefined, showWarningMessage: async () => undefined, createTerminal: () => ({ show: noop, sendText: noop, dispose: noop }), diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-memory-events.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-memory-events.test.ts index f4f417fad0..274cb8cc60 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-memory-events.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-memory-events.test.ts @@ -28,18 +28,6 @@ function status(root: string) { } } -function show(root: string) { - return { - root: `${root}/.kilo/memory`, - state: status(root).state, - sources: { project: "", environment: "", corrections: "" }, - index: "", - items: "", - changes: "", - decisions: "", - } -} - describe("KiloProvider memory events", () => { it("routes tracked background memory events to their session directory", async () => { const calls: string[] = [] @@ -151,10 +139,6 @@ describe("KiloProvider memory events", () => { calls.push(["disable", input.directory]) return { data: { root: `${input.directory}/.kilo/memory`, state: status(input.directory).state } } }, - show: async (input: { directory: string }) => { - calls.push(["show", input.directory]) - return { data: show(input.directory) } - }, }, } as unknown as KiloClient const posts: unknown[] = [] @@ -176,7 +160,6 @@ describe("KiloProvider memory events", () => { ["status", "/repo/project"], ["disable", "/repo/project"], ["status", "/repo/project"], - ["show", "/repo/project"], ]) expect(posts).toContainEqual(expect.objectContaining({ type: "memoryLoaded", sessionID: "ses_active" })) }) diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-memory.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-memory.test.ts index 1d3fb20c11..4769ccf2fe 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-memory.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-memory.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from "bun:test" +import { describe, expect, it, spyOn } from "bun:test" import type { KiloClient } from "@kilocode/sdk/v2/client" +import * as vscode from "vscode" import { KiloProviderMemory } from "../../src/kilo-provider/memory" function subject(client: KiloClient | undefined) { @@ -18,6 +19,7 @@ function status(root: string) { root: `${root}/.kilo/memory`, state: { enabled: true, + scope: "project", autoConsolidate: true, stats: { lastInjectedSessionID: "", @@ -42,6 +44,89 @@ function show(root: string) { } describe("KiloProviderMemory", () => { + it("shows stored memory and explains empty projects", async () => { + const picker = spyOn(vscode.window, "showQuickPick") + const notice = spyOn(vscode.window, "showInformationMessage") + const full = status("/repo") + const view = show("/repo") + view.items = "record id=project.md:Facts:test :: Stored memory fact :: with context" + const stored = subject({ + memory: { + show: async () => ({ data: view }), + status: async () => ({ data: full }), + }, + } as unknown as KiloClient) + const empty = subject({ + memory: { + show: async () => ({ data: show("/empty") }), + status: async () => ({ data: status("/empty") }), + }, + } as unknown as KiloClient) + + try { + await stored.memory.show("ses_stored") + await empty.memory.show("ses_empty") + + expect(picker).toHaveBeenCalledTimes(1) + expect(picker.mock.calls[0]?.[0]).toContainEqual( + expect.objectContaining({ label: "Storage", detail: "/repo/.kilo/memory" }), + ) + expect(picker.mock.calls[0]?.[0]).toContainEqual( + expect.objectContaining({ label: "Stored memory fact :: with context" }), + ) + expect(notice).toHaveBeenCalledWith( + "This project doesn't have any memory yet. It will start showing after you use Kilo.", + ) + } finally { + picker.mockRestore() + notice.mockRestore() + } + }) + + it("shows the stored memory total when the list is truncated", async () => { + const picker = spyOn(vscode.window, "showQuickPick") + const view = show("/repo") + view.items = Array.from({ length: 17 }, (_, i) => `- id=item-${i} :: Fact ${i}`).join("\n") + const item = subject({ + memory: { + show: async () => ({ data: view }), + status: async () => ({ data: status("/repo") }), + }, + } as unknown as KiloClient) + + try { + await item.memory.show("ses_stored") + + expect(picker.mock.calls[0]?.[0]).toContainEqual( + expect.objectContaining({ label: "Stored memory", description: "16 of 17 shown" }), + ) + expect(picker.mock.calls[0]?.[0]).toHaveLength(21) + } finally { + picker.mockRestore() + } + }) + + it("routes inspect operations to the memory folder", async () => { + const reveal = spyOn(vscode.commands, "executeCommand") + const item = subject({ + memory: { + status: async () => ({ data: status("/repo") }), + show: async () => ({ data: show("/repo") }), + }, + } as unknown as KiloClient) + + try { + await item.memory.run({ operation: "inspect", sessionID: "ses_inspect" }) + + expect(reveal).toHaveBeenCalledWith("revealFileInOS", expect.objectContaining({ fsPath: "/repo/.kilo/memory" })) + expect(item.posts).toContainEqual( + expect.objectContaining({ type: "memoryOperationResult", operation: "inspect", ok: true }), + ) + } finally { + reveal.mockRestore() + } + }) + it("handles clients without memory endpoints gracefully", async () => { const item = subject({} as KiloClient) @@ -117,7 +202,7 @@ describe("KiloProviderMemory", () => { expect(posts[1]).toMatchObject({ type: "memoryLoaded", sessionID: "ses_8", - show: { root: "/repo/ses_8/.kilo/memory" }, + status: { root: "/repo/ses_8/.kilo/memory" }, }) }) @@ -158,34 +243,29 @@ describe("KiloProviderMemory", () => { it("routes status operations without mutating memory", async () => { const calls: string[] = [] const state = status("/repo") - const view = show("/repo") const item = subject({ memory: { status: async () => { calls.push("status") return { data: state } }, - show: async () => { - calls.push("show") - return { data: view } - }, }, } as unknown as KiloClient) await item.memory.run({ operation: "status", sessionID: "ses_memory" }) - expect(calls).toEqual(["status", "status", "show"]) + expect(calls).toEqual(["status"]) expect(item.posts).toContainEqual( - expect.objectContaining({ type: "memoryOperationResult", operation: "status", ok: true }), + expect.objectContaining({ type: "memoryOperationResult", operation: "status", ok: true, result: state }), ) + expect(item.posts).toContainEqual(expect.objectContaining({ type: "memoryLoaded", status: state })) }) - it("routes auto-save, verbose, and purge operations with explicit payloads", async () => { + it("routes auto-save and purge operations with explicit payloads", async () => { const calls: unknown[] = [] const state = status("/repo") const view = show("/repo") state.state.autoConsolidate = false - state.state.verbose = true const item = subject({ memory: { configure: async (input: unknown) => { @@ -202,14 +282,12 @@ describe("KiloProviderMemory", () => { } as unknown as KiloClient) await item.memory.run({ operation: "auto", mode: "off", sessionID: "ses_memory" }) - await item.memory.run({ operation: "verbose", mode: "on", sessionID: "ses_memory" }) await item.memory.run({ operation: "purge", confirm: true, sessionID: "ses_memory" }) expect(calls).toEqual([ ["configure", { directory: "/repo", autoConsolidate: false }], - ["configure", { directory: "/repo", verbose: true }], ["purge", { directory: "/repo", confirm: true }], ]) - expect(item.posts.filter((post) => (post as { type?: string }).type === "memoryOperationResult")).toHaveLength(3) + expect(item.posts.filter((post) => (post as { type?: string }).type === "memoryOperationResult")).toHaveLength(2) }) }) diff --git a/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts index fbc8aa4255..7c226ecaba 100644 --- a/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts @@ -24,12 +24,22 @@ const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") const KILO_MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/message-part.tsx") const KILO_MESSAGE_HIGHLIGHT_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/message-highlight.ts") +const KILO_BASIC_TOOL_CSS_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/basic-tool.css") const KILO_MESSAGE_PART_CSS_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/message-part.css") const SHELL_ROLLING_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/shell-rolling-results.tsx") const ASSISTANT_MESSAGE_FILE = path.join( MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx", ) +const TASK_HEADER_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx") +const CONTEXT_TAB_FILE = path.join( + MONOREPO_ROOT, + "packages/kilo-vscode/webview-ui/src/components/settings/ContextTab.tsx", +) +const PROMPT_INPUT_FILE = path.join( + MONOREPO_ROOT, + "packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx", +) const TRANSCRIPT_PARTS_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/utils/transcript-parts.ts") const CHAT_LAYOUT_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/styles/chat-layout.css") @@ -331,6 +341,55 @@ describe("AssistantMessage visible row contract (source)", () => { it("uses the plan exit card only when plan metadata is renderable", () => { expect(src).toContain("if (!planExitInfo(part)) return") }) + + it("uses the native recall tool without a separate memory badge", () => { + const tools = fs.readFileSync(KILO_MESSAGE_PART_FILE, "utf-8") + expect(src).not.toContain("assistant-memory-badge") + expect(tools).toContain("ToolRegistry.render(part.tool) ?? McpTool") + }) +}) + +describe("Native tool summary contract (source)", () => { + const tools = fs.readFileSync(KILO_MESSAGE_PART_FILE, "utf-8") + const css = fs.readFileSync(KILO_BASIC_TOOL_CSS_FILE, "utf-8") + + it("shows one secondary argument while preserving complete expanded input", () => { + const start = tools.indexOf("const inputArgs") + const end = tools.indexOf("const formatted", start) + expect(tools.slice(start, end)).toContain(".slice(0, 1)") + expect(tools).toContain("JSON.stringify(props.input, null, 2)") + }) + + it("gives the primary label remaining width and bounds secondary arguments", () => { + expect(css).toMatch(/\[data-slot="basic-tool-tool-info"\][\s\S]*?flex: 1 1 auto;/) + expect(css).toMatch(/\[data-slot="basic-tool-tool-subtitle"\][\s\S]*?flex: 1 1 auto;/) + expect(css).toMatch(/\[data-slot="basic-tool-tool-arg"\][\s\S]*?max-width: 24ch;/) + }) +}) + +describe("Memory control placement contract (source)", () => { + const header = fs.readFileSync(TASK_HEADER_FILE, "utf-8") + const settings = fs.readFileSync(CONTEXT_TAB_FILE, "utf-8") + const prompt = fs.readFileSync(PROMPT_INPUT_FILE, "utf-8") + + it("keeps memory controls out of the task header", () => { + expect(header).not.toContain("useMemory") + expect(header).not.toContain('name="memory"') + }) + + it("shows storage inspection in settings without a manual rebuild action", () => { + expect(settings).toContain("settings.context.memory.storage.title") + expect(settings).toContain("settings.context.memory.status.enabledTokens") + expect(settings).toContain("memory.inspect()") + expect(settings).not.toContain("memory.rebuild()") + expect(settings).not.toContain("lastOperationCount") + expect(settings).not.toContain("sessionTokens") + }) + + it("expands bare memory commands into inline completion", () => { + expect(prompt).toContain('const value = "/memory "') + expect(prompt).toContain("slash.onInput(value, value.length)") + }) }) describe("Assistant transcript spacing contract (source)", () => { diff --git a/packages/kilo-vscode/tests/unit/memory-activity.test.ts b/packages/kilo-vscode/tests/unit/memory-activity.test.ts deleted file mode 100644 index 316175e256..0000000000 --- a/packages/kilo-vscode/tests/unit/memory-activity.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, expect, it } from "bun:test" -import { MemoryMarkerMeta } from "@kilocode/kilo-memory/marker-meta" -import { addMemoryActivity, markerActivity } from "../../webview-ui/src/utils/memory-activity" - -describe("memory activity", () => { - it("accumulates saved events with their message and source references", () => { - const items = addMemoryActivity( - [], - { - type: "saved", - message: "Saved project memory", - operationCount: 4, - added: 3, - removed: 1, - sources: ["project.md:kilo_colors"], - }, - 10, - ) - - expect(items).toEqual([ - { - type: "saved", - at: 10, - tokens: 0, - count: 3, - items: ["Saved project memory"], - refs: ["project.md:kilo_colors"], - }, - ]) - expect(addMemoryActivity(items, { type: "recalled" }, 20)).toEqual(items) - }) - - it("ignores removal-only events and caps saved activity", () => { - const removed = { type: "saved" as const, message: "Memory updated · 1 removed", added: 0, removed: 1 } - expect(addMemoryActivity([], removed, 10)).toEqual([]) - - const items = Array.from({ length: 60 }).reduce( - (all, _, at) => addMemoryActivity(all, { type: "saved", added: 1 }, at), - [] as ReturnType, - ) - expect(items).toHaveLength(50) - expect(items[0]?.at).toBe(10) - expect(items.at(-1)?.at).toBe(59) - }) - - it("decodes loaded and recalled markers for activity summaries", () => { - const loaded = markerActivity( - [ - { - type: "text", - metadata: MemoryMarkerMeta.metadata( - { - type: "startup", - bytes: 10, - tokens: 42, - count: 1, - files: ["project.md"], - items: ["Use Kilo colors"], - }, - true, - ), - }, - ], - 10, - ) - const recalled = markerActivity( - [ - { - type: "text", - metadata: MemoryMarkerMeta.metadata( - { - type: "recall", - bytes: 10, - tokens: 8, - count: 2, - files: ["project.md"], - items: ["Prefer dark mode"], - }, - true, - ), - }, - ], - 20, - ) - - expect(loaded).toMatchObject({ type: "loaded", tokens: 42, count: 1, items: [] }) - expect(recalled).toMatchObject({ type: "recalled", tokens: 8, count: 2, items: ["Prefer dark mode"] }) - }) -}) diff --git a/packages/kilo-vscode/tests/unit/memory-command.test.ts b/packages/kilo-vscode/tests/unit/memory-command.test.ts index 6b160d62af..d42a67aa86 100644 --- a/packages/kilo-vscode/tests/unit/memory-command.test.ts +++ b/packages/kilo-vscode/tests/unit/memory-command.test.ts @@ -4,7 +4,7 @@ import { parseMemoryCommand, type ParsedMemoryCommand } from "../../webview-ui/s type MemoryOperation = | "enable" | "status" - | "edit" + | "inspect" | "disable" | "rebuild" | "remember" @@ -12,7 +12,6 @@ type MemoryOperation = | "forget" | "purge" | "auto" - | "verbose" type Case = { name: string input: string @@ -43,7 +42,7 @@ function expected(item: Case): ParsedMemoryCommand | undefined { if (!item.query) throw new Error(`Missing query for fixture: ${item.name}`) return { kind: "operation", operation: item.operation, query: item.query } } - if (item.operation === "auto" || item.operation === "verbose") { + if (item.operation === "auto") { if (!item.mode) throw new Error(`Missing mode for fixture: ${item.name}`) return { kind: "operation", operation: item.operation, mode: item.mode } } diff --git a/packages/kilo-vscode/tests/unit/transcript-parts.test.ts b/packages/kilo-vscode/tests/unit/transcript-parts.test.ts index 351fa3a999..8d0b9abccc 100644 --- a/packages/kilo-vscode/tests/unit/transcript-parts.test.ts +++ b/packages/kilo-vscode/tests/unit/transcript-parts.test.ts @@ -31,6 +31,13 @@ const SCRIPT = ` state: { status: "completed", input: {}, output: "done", title: "Updated todos" }, }, { id: "read-running", type: "tool", tool: "read", state: { status: "running", input: {} } }, + { id: "memory-running", type: "tool", tool: "kilo_memory_recall", state: { status: "running", input: {} } }, + { + id: "memory-completed", + type: "tool", + tool: "kilo_memory_recall", + state: { status: "completed", input: {}, output: "memory", title: "Memory recalled" }, + }, ] const visible = parts.filter((part) => isRenderable(part, message)).map((part) => part.id) @@ -38,7 +45,14 @@ const SCRIPT = ` console.log("${FAIL}" + reason) process.exit(2) } - const expected = ["visible-text", "visible-reasoning", "todo-completed", "read-running"] + const expected = [ + "visible-text", + "visible-reasoning", + "todo-completed", + "read-running", + "memory-running", + "memory-completed", + ] if (visible.length !== expected.length || visible.some((id, index) => id !== expected[index])) { fail("did not exclude transcript-invisible parts") } diff --git a/packages/kilo-vscode/tests/unit/use-slash-command.test.ts b/packages/kilo-vscode/tests/unit/use-slash-command.test.ts index b243ab06fc..0718b5ab29 100644 --- a/packages/kilo-vscode/tests/unit/use-slash-command.test.ts +++ b/packages/kilo-vscode/tests/unit/use-slash-command.test.ts @@ -27,6 +27,76 @@ function setup(sandbox: () => void, options: { enabled?: () => boolean; exclude? } describe("useSlashCommand sandbox action", () => { + it("opens project memory actions from the top-level command", () => { + const ctx = setup(() => {}) + const state = { text: "/memory" } + const textarea = { + value: state.text, + setSelectionRange: () => {}, + focus: () => {}, + } as unknown as HTMLTextAreaElement + + ctx.slash.onInput("/mem", 4) + + expect(ctx.slash.results()).toContainEqual( + expect.objectContaining({ name: "memory", description: "Manage project memory", hints: ["mem"] }), + ) + ctx.slash.select(ctx.slash.results()[0]!, textarea, (text) => (state.text = text)) + expect(state.text).toBe("/memory ") + expect(ctx.slash.results().map((command) => command.name)).toContain("memory inspect") + ctx.dispose() + }) + + it("offers memory actions after the parent command", () => { + const ctx = setup(() => {}) + + ctx.slash.onInput("/memory ", 8) + + expect(ctx.slash.results().map((command) => command.name)).toEqual([ + "memory status", + "memory show", + "memory on", + "memory off", + "memory inspect", + "memory rebuild", + "memory remember", + "memory correct", + "memory forget", + "memory auto on", + "memory auto off", + "memory purge confirm", + ]) + ctx.dispose() + }) + + it("keeps nested memory actions out of root hint matching", () => { + const ctx = setup(() => {}) + const nested = ctx.slash.commands().filter((command) => command.name.startsWith("memory ")) + + expect(nested.length).toBeGreaterThan(0) + expect(nested.every((command) => command.hints.length === 0)).toBe(true) + ctx.dispose() + }) + + it("completes nested memory actions and closes for free text", () => { + const ctx = setup(() => {}) + const state = { text: "/mem rem" } + const textarea = { + value: state.text, + setSelectionRange: () => {}, + focus: () => {}, + } as unknown as HTMLTextAreaElement + + ctx.slash.onInput(state.text, state.text.length) + expect(ctx.slash.results().map((command) => command.name)).toEqual(["memory remember"]) + ctx.slash.select(ctx.slash.results()[0]!, textarea, (text) => (state.text = text)) + expect(state.text).toBe("/memory remember ") + + ctx.slash.onInput("/memory remember durable fact", 31) + expect(ctx.slash.show()).toBe(false) + ctx.dispose() + }) + it("runs the sandbox toggle as a client command", () => { const state = { toggles: 0, text: "/sandbox", prevented: 0 } const ctx = setup(() => state.toggles++) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index f2045ddf3d..a67b923186 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -11,7 +11,6 @@ import { Component, For, Show, createMemo } from "solid-js" import { Dynamic } from "solid-js/web" import { Part, PART_MAPPING, ToolRegistry } from "@kilocode/kilo-ui/message-part" import type { MessageFeedbackControls } from "@kilocode/kilo-ui/message-part" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" import type { AssistantMessage as SDKAssistantMessage, Part as SDKPart, @@ -23,11 +22,9 @@ import { useSession } from "../../context/session" import { useDisplay } from "../../context/display" import { useConfig } from "../../context/config" import { useLanguage } from "../../context/language" -import { useMemory } from "../../context/memory" import { useServer } from "../../context/server" import { planDisplayPath } from "../../utils/plan-path" import { isRenderable, UPSTREAM_SUPPRESSED_TOOLS } from "../../utils/transcript-parts" -import { MemoryMarkerMeta } from "@kilocode/kilo-memory/marker-meta" import { color as timelineColor } from "../../utils/timeline/colors" import type { Part as TimelinePart } from "../../types/messages" import type { TimelineHighlight } from "../../utils/timeline/highlight" @@ -117,8 +114,6 @@ type ToolStateProps = { status?: string } -type MemoryItem = MemoryMarkerMeta.Decoded - function TodoToolCard(props: { part: ToolPart; forceOpen?: boolean }) { const render = ToolRegistry.render(props.part.tool) const state = () => props.part.state as ToolStateProps @@ -173,8 +168,6 @@ export const AssistantMessage: Component = (props) => { const data = useData() const session = useSession() const display = useDisplay() - const mem = useMemory() - const language = useLanguage() const { config } = useConfig() const open = createMemo(() => config().terminal_command_display !== "collapsed") const edit = createMemo(() => config().code_edit_display === "expanded") @@ -189,33 +182,6 @@ export const AssistantMessage: Component = (props) => { return !!matchToolRequest(part, "question", session.questions()) }) }) - const meta = createMemo(() => - MemoryMarkerMeta.fromParts((props.parts ?? data.store.part?.[props.message.id] ?? []) as MemoryMarkerMeta.Part[]), - ) - const recall = createMemo(() => { - const item = meta() - if (item?.type === "recall") return item - }) - const fmt = (value: number) => value.toLocaleString(language.locale()) - const count = (item: MemoryItem) => fmt(item.count) - const items = (item: MemoryItem) => item.items ?? [] - const verbose = createMemo(() => Boolean(mem.status()?.state.verbose)) - const tip = (item: MemoryItem) => { - const values = MemoryMarkerMeta.snippets(item, verbose()) - return ( - - 0} - fallback={ - {`${language.t("chat.memory.badge.recalled")} · ${language.t("chat.memory.badge.items", { count: count(item) })}`} - } - > - {(value) => {value}} - - - ) - } - return ( <> @@ -331,17 +297,6 @@ export const AssistantMessage: Component = (props) => { ) }} - - {(item) => ( - - - {language.t("chat.memory.badge.recalled")} ·{" "} - {language.t("chat.memory.badge.items", { count: count(item()) })} - 0}> · {items(item())[0]} - - - )} - > ) } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 18bfddbe88..d2e9bc98c6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -75,7 +75,7 @@ import { import { ReviewComments } from "./ReviewComments" import { partReview, reviewBody } from "../../../../src/shared/review-comments" import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" -import { MEMORY_USAGE, parseMemoryCommand } from "../../utils/memory-command" +import { parseMemoryCommand } from "../../utils/memory-command" import { useMemory } from "../../context/memory" function mergeReviewComments(current: ReviewComment[], incoming: ReviewComment[]): ReviewComment[] { @@ -986,8 +986,16 @@ export const PromptInput: Component = (props) => { return false } if (memory.kind === "help") { - showToast({ variant: "default", title: "/memory", description: MEMORY_USAGE }) - return true + const value = "/memory " + setText(value) + if (textareaRef) { + textareaRef.value = value + textareaRef.setSelectionRange(value.length, value.length) + textareaRef.focus() + } + slash.onInput(value, value.length) + adjustHeight() + return false } if (isDisabled() || speech.active() || terminal.pending() || git.pending() || props.blocked?.()) return false const status = projectMemory.status() @@ -1000,13 +1008,17 @@ export const PromptInput: Component = (props) => { showToast({ variant: "error", title: language.t("chat.memory.project.disabled") }) return false } - if (memory.kind === "show") vscode.postMessage({ type: "memoryShow", sessionID: sid() }) + if (memory.kind === "show") vscode.postMessage({ type: "memoryShow", mode: "show", sessionID: sid() }) if (memory.kind === "operation") { + if (memory.operation === "status") { + vscode.postMessage({ type: "memoryShow", mode: "status", sessionID: sid() }) + return true + } vscode.postMessage({ type: "memoryOperation", operation: memory.operation, sessionID: sid(), - ...(memory.operation === "auto" || memory.operation === "verbose" ? { mode: memory.mode } : {}), + ...(memory.operation === "auto" ? { mode: memory.mode } : {}), ...(memory.operation === "purge" ? { confirm: memory.confirm } : {}), ...(memory.operation === "remember" || memory.operation === "correct" ? { text: memory.text } : {}), ...(memory.operation === "forget" ? { query: memory.query } : {}), diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx index b87eeafae9..9b57672647 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx @@ -13,9 +13,7 @@ import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { Icon } from "@kilocode/kilo-ui/icon" import { Checkbox } from "@kilocode/kilo-ui/checkbox" -import { Switch } from "@kilocode/kilo-ui/switch" import { useSession } from "../../context/session" -import { useMemory } from "../../context/memory" import { calcTokenUsage, collapseCostBreakdown } from "../../context/session-utils" import { useLanguage } from "../../context/language" import { useVSCode } from "../../context/vscode" @@ -26,10 +24,8 @@ import { TranscriptSearch } from "./TranscriptSearch" import { useTranscriptSearch } from "../../context/transcript-search" import { hasModelUsage, tokenSummary } from "../../context/model-usage" import { SessionRenameEditor } from "../shared/SessionRenameEditor" -import { DeferredPopover } from "../shared/DeferredPopover" import { target as todoTarget } from "../../context/todo-revert" import type { Part, TodoItem, ExtensionMessage } from "../../types/messages" -import type { MemoryActivity } from "../../utils/memory-activity" interface TaskHeaderProps { readonly?: boolean @@ -37,7 +33,6 @@ interface TaskHeaderProps { export const TaskHeader: Component = (props) => { const session = useSession() - const memory = useMemory() const language = useLanguage() const search = useTranscriptSearch() @@ -91,76 +86,6 @@ export const TaskHeader: Component = (props) => { return false }) - const memoryVerbose = createMemo(() => Boolean(memory.status()?.state.verbose)) - const memoryActive = createMemo(() => { - if (!memory.enabled()) return false - const stats = memory.status()?.state.stats - return !!stats && stats.lastInjectedSessionID === session.currentSessionID() && stats.lastInjectedTokens > 0 - }) - const memoryStatus = createMemo(() => { - if (memory.error()) return memory.error()! - if (memory.loading()) return language.t("chat.memory.status.loading") - if (!memory.enabled()) return language.t("chat.memory.project.disabled") - if (memoryActive()) return language.t("chat.memory.status.active") - return language.t("chat.memory.project.enabled") - }) - const activity = createMemo(() => [...memory.activity()].sort((a, b) => b.at - a.at)) - const activityLines = createMemo(() => { - if (memory.error() || !memory.enabled()) return [] - const loaded = activity().reduce((sum, item) => sum + (item.type === "loaded" ? item.tokens : 0), 0) - const recalled = activity().reduce((sum, item) => sum + (item.type === "recalled" ? item.count : 0), 0) - const saved = activity().reduce((sum, item) => sum + (item.type === "saved" ? item.count : 0), 0) - return [ - ...(loaded > 0 - ? [language.t("chat.memory.activity.loaded", { tokens: loaded.toLocaleString(language.locale()) })] - : []), - ...(recalled > 0 - ? [language.t("chat.memory.activity.recalled", { count: recalled.toLocaleString(language.locale()) })] - : []), - ...(saved > 0 - ? [language.t("chat.memory.activity.saved", { count: saved.toLocaleString(language.locale()) })] - : []), - ] - }) - const activityItems = createMemo(() => - activity() - .flatMap((item) => { - const values = - item.type === "saved" ? [...item.refs, ...item.items] : item.items.length > 0 ? item.items : item.refs - return values.flatMap((value) => { - const text = value.trim() - return text ? [{ type: item.type, value: text }] : [] - }) - }) - .slice(0, 5), - ) - const activityLabel = (item: { type: MemoryActivity["type"]; value: string }) => - language.t(`chat.memory.activity.${item.type}.item`, { item: item.value }) - const activitySummaryView = () => ( - - 0} - fallback={{language.t("chat.memory.activity.idle")}} - > - - {(line) => {line}} - - - - ) - const activityTooltip = () => ( - <> - {language.t("settings.context.title")} - {memoryStatus()} - {activitySummaryView()} - 0}> - - {(item) => {activityLabel(item)}} - - - > - ) - const vscode = useVSCode() const [expanded, setExpanded] = createSignal(true) @@ -311,121 +236,18 @@ export const TaskHeader: Component = (props) => { )} - - - - - - - > - } - > - - {language.t("settings.context.title")} - - - - - {language.t("settings.context.compaction.title")} - - - session.compact()} - > - {language.t("command.session.compact")} - - - - - - - - {language.t("settings.context.memory.title")} - - {memoryStatus()} - {activitySummaryView()} - - memory.enable()} - > - {language.t("chat.memory.enable")} - - } - > - memory.showMemory()} - > - {language.t("chat.memory.inspect")} - - memory.remember()} - > - {language.t("chat.memory.remember")} - - memory.forget()} - > - {language.t("chat.memory.forget")} - - memory.rebuild()} - > - {language.t("chat.memory.rebuild")} - - memory.disable()} - > - {language.t("chat.memory.disable")} - - - - - - {language.t("chat.memory.verbose")} - memory.verbose(next ? "on" : "off")} - > - {language.t("chat.memory.verbose")} - - - - - - - + + + session.compact()} + aria-label={language.t("command.session.compact")} + /> + + { const status = memory.status() if (!status) return language.t("settings.context.memory.status.notLoaded") if (!status.state.enabled) return language.t("settings.context.memory.status.disabled") + if (status.index.estimatedTokens === 0) return language.t("chat.memory.project.empty") const tokens = status.index.estimatedTokens.toLocaleString(language.locale()) - const session = memory.sessionTokens().toLocaleString(language.locale()) - const ops = status.state.stats.lastOperationCount.toLocaleString(language.locale()) - return language.t("settings.context.memory.status.enabledTokensOps", { session, tokens, ops }) + return language.t("settings.context.memory.status.enabledTokens", { tokens }) } return ( @@ -90,27 +89,23 @@ const ContextTab: Component = () => { - - memory.showMemory()}> - {language.t("settings.context.memory.inspect")} - - memory.rebuild()} - aria-label={language.t("settings.context.memory.rebuild")} - /> - + memory.inspect()} + > + {language.t("settings.context.memory.inspect")} + {(err) => ( diff --git a/packages/kilo-vscode/webview-ui/src/context/memory.tsx b/packages/kilo-vscode/webview-ui/src/context/memory.tsx index 00002e984d..235418c2e1 100644 --- a/packages/kilo-vscode/webview-ui/src/context/memory.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/memory.tsx @@ -1,53 +1,39 @@ -import { createContext, createEffect, createMemo, createSignal, onCleanup, untrack, useContext } from "solid-js" +import { createContext, createEffect, createMemo, createSignal, onCleanup, useContext } from "solid-js" import type { Accessor, ParentComponent } from "solid-js" import { useServer } from "./server" import { useSession } from "./session" import { useVSCode } from "./vscode" import { useLanguage } from "./language" import { showToast } from "@kilocode/kilo-ui/toast" -import type { MemoryShowResponse, MemoryStatusResponse } from "@kilocode/sdk/v2" -import type { ExtensionMessage, Message, Part } from "../types/messages" -import { addMemoryActivity, markerActivity, type MemoryActivity } from "../utils/memory-activity" -import { visibleParts } from "./session-queue" +import type { MemoryStatusResponse } from "@kilocode/sdk/v2" +import type { ExtensionMessage } from "../types/messages" export interface MemoryContextValue { status: Accessor - show: Accessor loading: Accessor pending: Accessor error: Accessor enabled: Accessor - sessionTokens: Accessor totalTokens: Accessor - activity: Accessor - refresh: (includeSources?: boolean) => void - showMemory: () => void + refresh: () => void + inspect: () => void enable: () => void disable: () => void auto: (mode: "on" | "off") => void - verbose: (mode: "on" | "off") => void - rebuild: () => void - remember: () => void - forget: () => void } export const MemoryContext = createContext() const EVENT_DEDUPE_MS = 1000 -type Marker = { part: string; item: MemoryActivity } - export const MemoryProvider: ParentComponent = (props) => { const vscode = useVSCode() const server = useServer() const session = useSession() const language = useLanguage() const [status, setStatus] = createSignal() - const [show, setShow] = createSignal() const [loading, setLoading] = createSignal(false) const [pending, setPending] = createSignal() const [error, setError] = createSignal() - const [saved, setSaved] = createSignal([]) - const [markers, setMarkers] = createSignal>({}) const id = () => session.currentSessionID() const key = (sid?: string) => sid ?? "" @@ -57,101 +43,30 @@ export const MemoryProvider: ParentComponent = (props) => { // currentSessionID yet (PromptInput posts with the draft id), so match both. return sid === id() || sid === session.draftSessionID() } - const marker = (parts: readonly Part[], at: number) => { - for (const part of parts) { - const item = markerActivity([part], at) - if (item) return { part: part.id, item } satisfies Marker - } - } - const stamp = (message: Message) => message.time?.created ?? Date.parse(message.createdAt) - const mark = (messageID: string, part: Part, at: number) => { - const item = marker([part], at) - setMarkers((items) => { - if (item) return { ...items, [messageID]: item } - if (items[messageID]?.part !== part.id) return items - const next = { ...items } - delete next[messageID] - return next - }) - } - const load = (message: Extract) => { - if (!current(message.sessionID)) return - const next = message.mode === "replace" || !message.mode ? {} : { ...markers() } - for (const entry of message.messages) { - const item = marker(entry.parts ?? [], stamp(entry)) - if (item) next[entry.id] = item - else delete next[entry.id] - } - setMarkers(next) - } - const created = (message: Extract) => { - if (!current(message.message.sessionID)) return - const item = marker(message.message.parts ?? [], stamp(message.message)) - if (item) setMarkers((items) => ({ ...items, [message.message.id]: item })) - } - const dropped = (messageID: string) => - setMarkers((items) => { - if (!items[messageID]) return items - const next = { ...items } - delete next[messageID] - return next - }) - const track = (message: ExtensionMessage) => { - if (message.type === "messagesLoaded") return load(message) - if (message.type === "messageCreated") return created(message) - if (message.type === "partUpdated") { - if (current(message.sessionID)) mark(message.messageID, message.part, Date.now()) - return - } - if (message.type === "partsUpdated") { - for (const update of message.updates) { - if (current(update.sessionID)) mark(update.messageID, update.part, Date.now()) - } - return - } - if (message.type === "partRemoved") { - if (!current(message.sessionID)) return - if (markers()[message.messageID]?.part === message.partID) dropped(message.messageID) - return - } - if (message.type === "messageRemoved" && current(message.sessionID)) dropped(message.messageID) - } - const scan = () => { - const next: Record = {} - for (const message of session.messages()) { - const item = marker(session.getParts(message.id), stamp(message)) - if (item) next[message.id] = item - } - setMarkers(next) - } let last: { key: string; time: number } | undefined let scope = "" const clear = () => { setStatus(undefined) - setShow(undefined) setError(undefined) setPending(undefined) - setSaved([]) - setMarkers({}) last = undefined } - const refresh = (includeSources = false) => { + const refresh = () => { if (!server.isConnected()) return setLoading(true) setError(undefined) - vscode.postMessage({ type: "requestMemory", sessionID: id(), includeSources }) + vscode.postMessage({ type: "requestMemory", sessionID: id() }) } - const operation = (op: "enable" | "disable" | "rebuild" | "verbose", mode?: "on" | "off") => { + const operation = (op: "enable" | "disable") => { if (!server.isConnected()) return setPending(key(id())) setError(undefined) vscode.postMessage({ type: "memoryOperation", operation: op, - ...(mode ? { mode } : {}), sessionID: id(), }) } @@ -163,26 +78,15 @@ export const MemoryProvider: ParentComponent = (props) => { vscode.postMessage({ type: "memoryOperation", operation: "auto", mode, sessionID: id() }) } - const prompt = (op: "remember" | "forget") => { + const inspect = () => { if (!server.isConnected()) return setPending(key(id())) setError(undefined) - vscode.postMessage({ type: "memoryPrompt", operation: op, sessionID: id() }) - } - - const showMemory = () => { - if (!server.isConnected()) return - setLoading(true) - setError(undefined) - vscode.postMessage({ type: "memoryShow", sessionID: id() }) + vscode.postMessage({ type: "memoryOperation", operation: "inspect", sessionID: id() }) } const event = (message: Extract) => { if (!current(message.sessionID)) return - if (message.detail.type === "saved") { - setSaved((items) => addMemoryActivity(items, message.detail, Date.now())) - return - } if (message.detail.type !== "error") return if (!message.detail.message) return const dedupeKey = `${message.sessionID ?? ""}:${message.detail.type ?? ""}:${message.detail.message}` @@ -198,11 +102,9 @@ export const MemoryProvider: ParentComponent = (props) => { if (message.error) { setError(message.error) setStatus(undefined) - setShow(undefined) return } if (message.status) setStatus(message.status) - if (message.show) setShow(message.show) setError(undefined) } @@ -217,12 +119,16 @@ export const MemoryProvider: ParentComponent = (props) => { return } if (message.status) setStatus(message.status) - if (message.show) setShow(message.show) setError(undefined) + if (message.operation === "remember" || message.operation === "correct" || message.operation === "forget") { + showToast({ variant: "success", title: language.t("chat.memory.updated") }) + } + if (message.operation === "rebuild") { + showToast({ variant: "success", title: language.t("chat.memory.rebuild") }) + } } const receive = (message: ExtensionMessage) => { - track(message) if (message.type === "memoryEvent") { event(message) return @@ -235,7 +141,7 @@ export const MemoryProvider: ParentComponent = (props) => { done(message) return } - if (message.type === "extensionDataReady" && server.isConnected() && !status()) refresh(false) + if (message.type === "extensionDataReady" && server.isConnected() && !status()) refresh() } const unsubscribe = vscode.onMessage(receive) @@ -250,56 +156,28 @@ export const MemoryProvider: ParentComponent = (props) => { if (scope !== next) { scope = next clear() - untrack(scan) } if (!connected) { setLoading(false) return } - refresh(false) + refresh() }) - const sessionTokens = (snapshot?: MemoryStatusResponse) => { - const sid = id() - if (!snapshot?.state.enabled) return 0 - if (!sid || snapshot.state.stats.lastInjectedSessionID !== sid) return 0 - return snapshot.state.stats.lastInjectedTokens - } - const total = createMemo(() => status()?.index.estimatedTokens ?? 0) - const sessionTotal = createMemo(() => sessionTokens(status())) - - const activity = createMemo(() => { - const revert = session.currentSession()?.revert ?? undefined - const items = Object.entries(markers()).flatMap(([mid, entry]) => { - if (!revert || mid < revert.messageID) return [entry.item] - if (mid !== revert.messageID || !revert.partID) return [] - const visible = visibleParts(mid, session.getParts(mid), revert) - return visible.some((part) => part.id === entry.part) ? [entry.item] : [] - }) - return [...items, ...saved()] - }) - const value: MemoryContextValue = { status, - show, loading, pending: createMemo(() => pending() === key(id())), error, enabled: createMemo(() => status()?.state.enabled ?? false), - sessionTokens: sessionTotal, totalTokens: total, - activity, refresh, - showMemory, + inspect, enable: () => operation("enable"), disable: () => operation("disable"), auto, - verbose: (mode) => operation("verbose", mode), - rebuild: () => operation("rebuild"), - remember: () => prompt("remember"), - forget: () => prompt("forget"), } return {props.children} diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts b/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts index b7a95fd62b..6b9cd42bce 100644 --- a/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts +++ b/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts @@ -27,6 +27,7 @@ interface VSCodeContext { export interface SlashCommandEntry extends SlashCommandInfo { action?: () => void enabled?: Accessor + nested?: boolean } export interface SlashCommand { @@ -119,6 +120,24 @@ export function useSlashCommand( window.dispatchEvent(new CustomEvent("compactSession")) }, }, + { + name: "memory", + description: "Manage project memory", + hints: ["mem"], + nested: true, + }, + { name: "memory status", description: "Show project memory status", hints: [] }, + { name: "memory show", description: "Show stored project memory", hints: [] }, + { name: "memory on", description: "Enable project memory", hints: [] }, + { name: "memory off", description: "Disable project memory", hints: [] }, + { name: "memory inspect", description: "Reveal the project memory folder", hints: [] }, + { name: "memory rebuild", description: "Rebuild the memory index", hints: [] }, + { name: "memory remember", description: "Save a project memory note", hints: [] }, + { name: "memory correct", description: "Save a correction to project memory", hints: [] }, + { name: "memory forget", description: "Remove matching project memory", hints: [] }, + { name: "memory auto on", description: "Enable automatic memory saves", hints: [] }, + { name: "memory auto off", description: "Disable automatic memory saves", hints: [] }, + { name: "memory purge confirm", description: "Delete all project memory files", hints: [] }, { name: "export", description: "Export the current session transcript as Markdown", @@ -198,10 +217,20 @@ export function useSlashCommand( const results = () => { const q = query() if (q === null) return [] - const all = commands() - if (!q) return all + const list = commands() + if (q.startsWith("memory ")) { + const matches = list.filter((cmd) => cmd.name.startsWith("memory ")) + if (q === "memory ") return matches + const lower = q.toLowerCase() + return sortByScore( + matches.filter((cmd) => cmd.name.toLowerCase().startsWith(lower)), + lower, + ) + } + const root = list.filter((cmd) => !cmd.name.includes(" ")) + if (!q) return root const lower = q.toLowerCase() - const matches = all.filter( + const matches = root.filter( (cmd) => cmd.name.toLowerCase().includes(lower) || cmd.description?.toLowerCase().includes(lower) || @@ -230,9 +259,15 @@ export function useSlashCommand( request() setQuery(match[1]) setIndex(0) - } else { - close() + return } + const memory = before.match(/^\/(?:memory|mem)\s+([^\n]*)$/i) + if (!memory) return close() + const value = `memory ${memory[1]}`.toLowerCase() + if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close() + request() + setQuery(value) + setIndex(0) } const select = ( @@ -256,7 +291,11 @@ export function useSlashCommand( const pos = text.length textarea.setSelectionRange(pos, pos) textarea.focus() - close() + if (cmd.nested) { + setQuery(`${cmd.name} `) + setIndex(0) + } + if (!cmd.nested) close() onSelect?.() } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 4d3747c90f..5a04fd4eb7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1633,36 +1633,18 @@ export const dict = { "settings.context.memory.autoSave.title": "حفظ ذاكرة المشروع تلقائيًا", "settings.context.memory.autoSave.description": "حفظ حقائق المشروع الدائمة تلقائيًا من الجولات المكتملة عند تفعيل الذاكرة.", - "settings.context.memory.index.title": "فهرس الذاكرة", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "غير محمّلة", "settings.context.memory.status.disabled": "معطّلة", - "settings.context.memory.status.enabledTokensOps": - "مفعّلة - ~{{session}} رموز سياق بدء التشغيل في هذه الجلسة - ~{{tokens}} رموز في الفهرس المخزّن - آخر عملية {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "فعّل الذاكرة لإنشاء ملفات ذاكرة المشروع.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "فحص", - "settings.context.memory.rebuild": "إعادة بناء فهرس الذاكرة", - "chat.memory.status.loading": "جارٍ تحميل حالة الذاكرة", - "chat.memory.status.active": "الذاكرة نشطة في هذه الجلسة", - "chat.memory.project.enabled": "ذاكرة المشروع مفعّلة", "chat.memory.project.disabled": "ذاكرة المشروع معطّلة", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "فشل أمر الذاكرة", - "chat.memory.inspect": "فحص الذاكرة", - "chat.memory.remember": "تذكّر", - "chat.memory.forget": "انسَ", - "chat.memory.rebuild": "إعادة بناء الفهرس", - "chat.memory.disable": "تعطيل الذاكرة", - "chat.memory.enable": "تفعيل الذاكرة", - "chat.memory.verbose": "تفصيلي", - "chat.memory.activity.idle": "لا يوجد نشاط للذاكرة في هذه الجلسة", - "chat.memory.activity.loaded": "تم تحميل {{tokens}} رمزًا", - "chat.memory.activity.recalled": "تم استدعاء {{count}}", - "chat.memory.activity.saved": "تم حفظ {{count}}", - "chat.memory.activity.loaded.item": "تم التحميل: {{item}}", - "chat.memory.activity.recalled.item": "تم الاستدعاء: {{item}}", - "chat.memory.activity.saved.item": "تم الحفظ: {{item}}", - "chat.memory.badge.recalled": "تم استدعاء الذاكرة", - "chat.memory.badge.items": "{{count}} عناصر", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "استخدام prompt مخصص", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index d5e51be77d..f153fec3c1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1683,36 +1683,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Salvar memória do projeto automaticamente", "settings.context.memory.autoSave.description": "Salva automaticamente fatos duradouros do projeto a partir de turnos concluídos quando a memória está ativada.", - "settings.context.memory.index.title": "Índice da memória", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Não carregada", "settings.context.memory.status.disabled": "Desativada", - "settings.context.memory.status.enabledTokensOps": - "Ativada - ~{{session}} tokens de contexto inicial nesta sessão - ~{{tokens}} tokens no índice armazenado - última op. {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Ative a memória para criar arquivos de memória do projeto.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspecionar", - "settings.context.memory.rebuild": "Reconstruir índice da memória", - "chat.memory.status.loading": "Carregando status da memória", - "chat.memory.status.active": "Memória ativa nesta sessão", - "chat.memory.project.enabled": "Memória do projeto ativada", "chat.memory.project.disabled": "Memória do projeto desativada", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Comando de memória falhou", - "chat.memory.inspect": "Inspecionar memória", - "chat.memory.remember": "Lembrar", - "chat.memory.forget": "Esquecer", - "chat.memory.rebuild": "Reconstruir índice", - "chat.memory.disable": "Desativar memória", - "chat.memory.enable": "Ativar memória", - "chat.memory.verbose": "Detalhado", - "chat.memory.activity.idle": "Nenhuma atividade de memória nesta sessão", - "chat.memory.activity.loaded": "{{tokens}} tokens carregados", - "chat.memory.activity.recalled": "{{count}} recuperados", - "chat.memory.activity.saved": "{{count}} salvos", - "chat.memory.activity.loaded.item": "carregado: {{item}}", - "chat.memory.activity.recalled.item": "recuperado: {{item}}", - "chat.memory.activity.saved.item": "salvo: {{item}}", - "chat.memory.badge.recalled": "Memória recuperada", - "chat.memory.badge.items": "{{count}} itens", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Usar prompt personalizado", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 53946d394d..8a83fd8be4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1676,36 +1676,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Automatsko spremanje memorije projekta", "settings.context.memory.autoSave.description": "Automatski sprema trajne činjenice projekta iz završenih koraka kada je memorija uključena.", - "settings.context.memory.index.title": "Indeks memorije", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Nije učitana", "settings.context.memory.status.disabled": "Onemogućena", - "settings.context.memory.status.enabledTokensOps": - "Omogućena - ~{{session}} tokena početnog konteksta u ovoj sesiji - ~{{tokens}} tokena spremljenog indeksa - zadnja op. {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Omogućite memoriju za kreiranje datoteka memorije projekta.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Pregledaj", - "settings.context.memory.rebuild": "Ponovo izgradi indeks memorije", - "chat.memory.status.loading": "Učitavanje statusa memorije", - "chat.memory.status.active": "Memorija je aktivna u ovoj sesiji", - "chat.memory.project.enabled": "Memorija projekta omogućena", "chat.memory.project.disabled": "Memorija projekta onemogućena", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Komanda memorije nije uspjela", - "chat.memory.inspect": "Pregledaj memoriju", - "chat.memory.remember": "Zapamti", - "chat.memory.forget": "Zaboravi", - "chat.memory.rebuild": "Ponovo izgradi indeks", - "chat.memory.disable": "Onemogući memoriju", - "chat.memory.enable": "Omogući memoriju", - "chat.memory.verbose": "Detaljno", - "chat.memory.activity.idle": "Nema aktivnosti memorije u ovoj sesiji", - "chat.memory.activity.loaded": "učitano {{tokens}} tokena", - "chat.memory.activity.recalled": "prizvano {{count}}", - "chat.memory.activity.saved": "sačuvano {{count}}", - "chat.memory.activity.loaded.item": "učitano: {{item}}", - "chat.memory.activity.recalled.item": "prizvano: {{item}}", - "chat.memory.activity.saved.item": "sačuvano: {{item}}", - "chat.memory.badge.recalled": "Memorija opozvana", - "chat.memory.badge.items": "{{count}} stavki", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Koristi prilagođeni prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 001478eabb..d4c0d06ce8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1666,36 +1666,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Gem projekthukommelse automatisk", "settings.context.memory.autoSave.description": "Gemmer automatisk varige projektfakta fra afsluttede ture, når hukommelse er aktiveret.", - "settings.context.memory.index.title": "Hukommelsesindeks", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Ikke indlæst", "settings.context.memory.status.disabled": "Deaktiveret", - "settings.context.memory.status.enabledTokensOps": - "Aktiveret - ~{{session}} opstartstokens i denne session - ~{{tokens}} tokens i gemt indeks - seneste handling {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Aktivér hukommelse for at oprette projektets hukommelsesfiler.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspicér", - "settings.context.memory.rebuild": "Genopbyg hukommelsesindeks", - "chat.memory.status.loading": "Indlæser hukommelsesstatus", - "chat.memory.status.active": "Hukommelse aktiv i denne session", - "chat.memory.project.enabled": "Projekthukommelse aktiveret", "chat.memory.project.disabled": "Projekthukommelse deaktiveret", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Hukommelseskommando mislykkedes", - "chat.memory.inspect": "Inspicér hukommelse", - "chat.memory.remember": "Husk", - "chat.memory.forget": "Glem", - "chat.memory.rebuild": "Genopbyg indeks", - "chat.memory.disable": "Deaktivér hukommelse", - "chat.memory.enable": "Aktivér hukommelse", - "chat.memory.verbose": "Detaljeret", - "chat.memory.activity.idle": "Ingen hukommelsesaktivitet i denne session", - "chat.memory.activity.loaded": "indlæste {{tokens}} tokens", - "chat.memory.activity.recalled": "genkaldte {{count}}", - "chat.memory.activity.saved": "gemte {{count}}", - "chat.memory.activity.loaded.item": "indlæst: {{item}}", - "chat.memory.activity.recalled.item": "genkaldt: {{item}}", - "chat.memory.activity.saved.item": "gemt: {{item}}", - "chat.memory.badge.recalled": "Hukommelse genkaldt", - "chat.memory.badge.items": "{{count}} elementer", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Brug brugerdefineret prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index b0c0fbee76..bf036e817c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1702,36 +1702,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Projektspeicher automatisch speichern", "settings.context.memory.autoSave.description": "Dauerhafte Projektfakten aus abgeschlossenen Durchläufen automatisch speichern, wenn Speicher aktiviert ist.", - "settings.context.memory.index.title": "Speicherindex", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Nicht geladen", "settings.context.memory.status.disabled": "Deaktiviert", - "settings.context.memory.status.enabledTokensOps": - "Aktiviert - ~{{session}} Startkontext-Tokens in dieser Sitzung - ~{{tokens}} Tokens im gespeicherten Index - letzte Operation {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Aktivieren Sie den Speicher, um Projektspeicherdateien zu erstellen.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Prüfen", - "settings.context.memory.rebuild": "Speicherindex neu erstellen", - "chat.memory.status.loading": "Speicherstatus wird geladen", - "chat.memory.status.active": "Speicher in dieser Sitzung aktiv", - "chat.memory.project.enabled": "Projektspeicher aktiviert", "chat.memory.project.disabled": "Projektspeicher deaktiviert", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Speicherbefehl fehlgeschlagen", - "chat.memory.inspect": "Speicher prüfen", - "chat.memory.remember": "Merken", - "chat.memory.forget": "Vergessen", - "chat.memory.rebuild": "Index neu erstellen", - "chat.memory.disable": "Speicher deaktivieren", - "chat.memory.enable": "Speicher aktivieren", - "chat.memory.verbose": "Ausführlich", - "chat.memory.activity.idle": "Keine Speicheraktivität in dieser Sitzung", - "chat.memory.activity.loaded": "{{tokens}} Token geladen", - "chat.memory.activity.recalled": "{{count}} abgerufen", - "chat.memory.activity.saved": "{{count}} gespeichert", - "chat.memory.activity.loaded.item": "geladen: {{item}}", - "chat.memory.activity.recalled.item": "abgerufen: {{item}}", - "chat.memory.activity.saved.item": "gespeichert: {{item}}", - "chat.memory.badge.recalled": "Speicher abgerufen", - "chat.memory.badge.items": "{{count}} Elemente", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Benutzerdefinierten prompt verwenden", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 09d559e8a8..bd742755d4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1646,36 +1646,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Auto-save project memory", "settings.context.memory.autoSave.description": "Automatically save durable project facts from completed turns when memory is enabled.", - "settings.context.memory.index.title": "Memory index", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Not loaded", "settings.context.memory.status.disabled": "Disabled", - "settings.context.memory.status.enabledTokensOps": - "Enabled - ~{{session}} startup tokens this session - ~{{tokens}} stored index tokens - last op {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Enable memory to create project memory files.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspect", - "settings.context.memory.rebuild": "Rebuild memory index", - "chat.memory.status.loading": "Memory status loading", - "chat.memory.status.active": "Memory active this session", - "chat.memory.project.enabled": "Project memory enabled", "chat.memory.project.disabled": "Project memory disabled", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Memory command failed", - "chat.memory.inspect": "Inspect memory", - "chat.memory.remember": "Remember", - "chat.memory.forget": "Forget", - "chat.memory.rebuild": "Rebuild index", - "chat.memory.disable": "Disable memory", - "chat.memory.enable": "Enable memory", - "chat.memory.verbose": "Verbose", - "chat.memory.activity.idle": "No memory activity this session", - "chat.memory.activity.loaded": "loaded {{tokens}} tokens", - "chat.memory.activity.recalled": "recalled {{count}}", - "chat.memory.activity.saved": "saved {{count}}", - "chat.memory.activity.loaded.item": "loaded: {{item}}", - "chat.memory.activity.recalled.item": "recalled: {{item}}", - "chat.memory.activity.saved.item": "saved: {{item}}", - "chat.memory.badge.recalled": "Memory recalled", - "chat.memory.badge.items": "{{count}} items", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Use Custom Prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 7dc80ddac4..415c87f2ac 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1691,36 +1691,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Guardado automático de memoria del proyecto", "settings.context.memory.autoSave.description": "Guardar automáticamente hechos duraderos del proyecto desde turnos completados cuando la memoria está activada.", - "settings.context.memory.index.title": "Índice de memoria", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "No cargada", "settings.context.memory.status.disabled": "Desactivada", - "settings.context.memory.status.enabledTokensOps": - "Activada - ~{{session}} tokens de contexto inicial en esta sesión - ~{{tokens}} tokens del índice almacenado - última op. {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Activa la memoria para crear archivos de memoria del proyecto.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspeccionar", - "settings.context.memory.rebuild": "Reconstruir índice de memoria", - "chat.memory.status.loading": "Cargando estado de la memoria", - "chat.memory.status.active": "Memoria activa en esta sesión", - "chat.memory.project.enabled": "Memoria del proyecto activada", "chat.memory.project.disabled": "Memoria del proyecto desactivada", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Error en el comando de memoria", - "chat.memory.inspect": "Inspeccionar memoria", - "chat.memory.remember": "Recordar", - "chat.memory.forget": "Olvidar", - "chat.memory.rebuild": "Reconstruir índice", - "chat.memory.disable": "Desactivar memoria", - "chat.memory.enable": "Activar memoria", - "chat.memory.verbose": "Detallado", - "chat.memory.activity.idle": "Sin actividad de memoria en esta sesión", - "chat.memory.activity.loaded": "{{tokens}} tokens cargados", - "chat.memory.activity.recalled": "{{count}} recuperados", - "chat.memory.activity.saved": "{{count}} guardados", - "chat.memory.activity.loaded.item": "cargado: {{item}}", - "chat.memory.activity.recalled.item": "recuperado: {{item}}", - "chat.memory.activity.saved.item": "guardado: {{item}}", - "chat.memory.badge.recalled": "Memoria recuperada", - "chat.memory.badge.items": "{{count}} elementos", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Usar prompt personalizado", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 4698b7a16c..05fac462ea 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1711,36 +1711,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Enregistrement automatique de la mémoire du projet", "settings.context.memory.autoSave.description": "Enregistrer automatiquement les faits durables du projet à partir des tours terminés lorsque la mémoire est activée.", - "settings.context.memory.index.title": "Index de mémoire", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Non chargée", "settings.context.memory.status.disabled": "Désactivée", - "settings.context.memory.status.enabledTokensOps": - "Activée - ~{{session}} tokens de contexte initial dans cette session - ~{{tokens}} tokens dans l’index stocké - dernière opération {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Activez la mémoire pour créer les fichiers de mémoire du projet.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspecter", - "settings.context.memory.rebuild": "Reconstruire l’index de mémoire", - "chat.memory.status.loading": "Chargement de l’état de la mémoire", - "chat.memory.status.active": "Mémoire active pour cette session", - "chat.memory.project.enabled": "Mémoire du projet activée", "chat.memory.project.disabled": "Mémoire du projet désactivée", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "La commande de mémoire a échoué", - "chat.memory.inspect": "Inspecter la mémoire", - "chat.memory.remember": "Mémoriser", - "chat.memory.forget": "Oublier", - "chat.memory.rebuild": "Reconstruire l’index", - "chat.memory.disable": "Désactiver la mémoire", - "chat.memory.enable": "Activer la mémoire", - "chat.memory.verbose": "Détaillé", - "chat.memory.activity.idle": "Aucune activité de mémoire pour cette session", - "chat.memory.activity.loaded": "{{tokens}} jetons chargés", - "chat.memory.activity.recalled": "{{count}} rappelés", - "chat.memory.activity.saved": "{{count}} enregistrés", - "chat.memory.activity.loaded.item": "chargé : {{item}}", - "chat.memory.activity.recalled.item": "rappelé : {{item}}", - "chat.memory.activity.saved.item": "enregistré : {{item}}", - "chat.memory.badge.recalled": "Mémoire rappelée", - "chat.memory.badge.items": "{{count}} éléments", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Utiliser un prompt personnalisé", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index 484ce4ef52..43f32b66be 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -1469,36 +1469,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Salvataggio automatico memoria progetto", "settings.context.memory.autoSave.description": "Salva automaticamente fatti durevoli del progetto dai turni completati quando la memoria è attiva.", - "settings.context.memory.index.title": "Indice della memoria", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Non caricata", "settings.context.memory.status.disabled": "Disattivata", - "settings.context.memory.status.enabledTokensOps": - "Attivata - ~{{session}} token di contesto iniziale in questa sessione - ~{{tokens}} token nell’indice salvato - ultima operazione {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Attiva la memoria per creare i file di memoria del progetto.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Ispeziona", - "settings.context.memory.rebuild": "Ricostruisci indice della memoria", - "chat.memory.status.loading": "Caricamento stato memoria", - "chat.memory.status.active": "Memoria attiva in questa sessione", - "chat.memory.project.enabled": "Memoria del progetto attivata", "chat.memory.project.disabled": "Memoria del progetto disattivata", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Comando memoria non riuscito", - "chat.memory.inspect": "Ispeziona memoria", - "chat.memory.remember": "Ricorda", - "chat.memory.forget": "Dimentica", - "chat.memory.rebuild": "Ricostruisci indice", - "chat.memory.disable": "Disattiva memoria", - "chat.memory.enable": "Attiva memoria", - "chat.memory.verbose": "Dettagliato", - "chat.memory.activity.idle": "Nessuna attività di memoria in questa sessione", - "chat.memory.activity.loaded": "{{tokens}} token caricati", - "chat.memory.activity.recalled": "{{count}} richiamati", - "chat.memory.activity.saved": "{{count}} salvati", - "chat.memory.activity.loaded.item": "caricato: {{item}}", - "chat.memory.activity.recalled.item": "richiamato: {{item}}", - "chat.memory.activity.saved.item": "salvato: {{item}}", - "chat.memory.badge.recalled": "Memoria richiamata", - "chat.memory.badge.items": "{{count}} elementi", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Messaggio commit", "settings.commitMessage.override.title": "Usa prompt personalizzato", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 8b869c38de..8b63bbee61 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1662,36 +1662,18 @@ export const dict = { "settings.context.memory.autoSave.title": "プロジェクトメモリを自動保存", "settings.context.memory.autoSave.description": "メモリが有効なとき、完了したターンから永続的なプロジェクト情報を自動保存します。", - "settings.context.memory.index.title": "メモリインデックス", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "未読み込み", "settings.context.memory.status.disabled": "無効", - "settings.context.memory.status.enabledTokensOps": - "有効 - このセッションの起動コンテキスト ~{{session}} トークン - 保存済みインデックス ~{{tokens}} トークン - 最後の操作 {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "メモリを有効にしてプロジェクトメモリファイルを作成します。", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "検査", - "settings.context.memory.rebuild": "メモリインデックスを再構築", - "chat.memory.status.loading": "メモリ状態を読み込み中", - "chat.memory.status.active": "このセッションでメモリが有効です", - "chat.memory.project.enabled": "プロジェクトメモリが有効です", "chat.memory.project.disabled": "プロジェクトメモリが無効です", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "メモリコマンドに失敗しました", - "chat.memory.inspect": "メモリを検査", - "chat.memory.remember": "記憶", - "chat.memory.forget": "忘れる", - "chat.memory.rebuild": "インデックスを再構築", - "chat.memory.disable": "メモリを無効化", - "chat.memory.enable": "メモリを有効化", - "chat.memory.verbose": "詳細", - "chat.memory.activity.idle": "このセッションにはメモリアクティビティがありません", - "chat.memory.activity.loaded": "{{tokens}} トークンを読み込み", - "chat.memory.activity.recalled": "{{count}} 件を呼び出し", - "chat.memory.activity.saved": "{{count}} 件を保存", - "chat.memory.activity.loaded.item": "読み込み: {{item}}", - "chat.memory.activity.recalled.item": "呼び出し: {{item}}", - "chat.memory.activity.saved.item": "保存: {{item}}", - "chat.memory.badge.recalled": "メモリを呼び出しました", - "chat.memory.badge.items": "{{count}} 件", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "カスタム prompt を使用", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 5593d3ad06..edcfa35ae6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1645,36 +1645,18 @@ export const dict = { "settings.context.memory.autoSave.title": "프로젝트 메모리 자동 저장", "settings.context.memory.autoSave.description": "메모리가 활성화되면 완료된 턴에서 지속적인 프로젝트 사실을 자동으로 저장합니다.", - "settings.context.memory.index.title": "메모리 인덱스", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "로드되지 않음", "settings.context.memory.status.disabled": "비활성화됨", - "settings.context.memory.status.enabledTokensOps": - "활성화됨 - 이 세션의 시작 컨텍스트 ~{{session}} 토큰 - 저장된 인덱스 ~{{tokens}} 토큰 - 마지막 작업 {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "메모리를 활성화하여 프로젝트 메모리 파일을 만듭니다.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "검사", - "settings.context.memory.rebuild": "메모리 인덱스 다시 빌드", - "chat.memory.status.loading": "메모리 상태 로드 중", - "chat.memory.status.active": "이 세션에서 메모리 활성화됨", - "chat.memory.project.enabled": "프로젝트 메모리 활성화됨", "chat.memory.project.disabled": "프로젝트 메모리 비활성화됨", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "메모리 명령 실패", - "chat.memory.inspect": "메모리 검사", - "chat.memory.remember": "기억", - "chat.memory.forget": "잊기", - "chat.memory.rebuild": "인덱스 다시 빌드", - "chat.memory.disable": "메모리 비활성화", - "chat.memory.enable": "메모리 활성화", - "chat.memory.verbose": "상세", - "chat.memory.activity.idle": "이 세션에 메모리 활동 없음", - "chat.memory.activity.loaded": "토큰 {{tokens}}개 로드", - "chat.memory.activity.recalled": "{{count}}개 불러옴", - "chat.memory.activity.saved": "{{count}}개 저장", - "chat.memory.activity.loaded.item": "로드: {{item}}", - "chat.memory.activity.recalled.item": "불러옴: {{item}}", - "chat.memory.activity.saved.item": "저장: {{item}}", - "chat.memory.badge.recalled": "메모리 불러옴", - "chat.memory.badge.items": "{{count}}개 항목", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "사용자 지정 prompt 사용", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 505062378f..3508d0bd94 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1643,36 +1643,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Projectgeheugen automatisch opslaan", "settings.context.memory.autoSave.description": "Sla duurzame projectfeiten automatisch op uit voltooide beurten wanneer geheugen is ingeschakeld.", - "settings.context.memory.index.title": "Geheugenindex", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Niet geladen", "settings.context.memory.status.disabled": "Uitgeschakeld", - "settings.context.memory.status.enabledTokensOps": - "Ingeschakeld - ~{{session}} startcontexttokens in deze sessie - ~{{tokens}} tokens in opgeslagen index - laatste bewerking {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Schakel geheugen in om projectgeheugenbestanden te maken.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspecteren", - "settings.context.memory.rebuild": "Geheugenindex opnieuw opbouwen", - "chat.memory.status.loading": "Geheugenstatus laden", - "chat.memory.status.active": "Geheugen actief in deze sessie", - "chat.memory.project.enabled": "Projectgeheugen ingeschakeld", "chat.memory.project.disabled": "Projectgeheugen uitgeschakeld", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Geheugenopdracht mislukt", - "chat.memory.inspect": "Geheugen inspecteren", - "chat.memory.remember": "Onthouden", - "chat.memory.forget": "Vergeten", - "chat.memory.rebuild": "Index opnieuw opbouwen", - "chat.memory.disable": "Geheugen uitschakelen", - "chat.memory.enable": "Geheugen inschakelen", - "chat.memory.verbose": "Uitgebreid", - "chat.memory.activity.idle": "Geen geheugenactiviteit in deze sessie", - "chat.memory.activity.loaded": "{{tokens}} tokens geladen", - "chat.memory.activity.recalled": "{{count}} opgehaald", - "chat.memory.activity.saved": "{{count}} opgeslagen", - "chat.memory.activity.loaded.item": "geladen: {{item}}", - "chat.memory.activity.recalled.item": "opgehaald: {{item}}", - "chat.memory.activity.saved.item": "opgeslagen: {{item}}", - "chat.memory.badge.recalled": "Geheugen opgehaald", - "chat.memory.badge.items": "{{count}} items", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Aangepaste prompt gebruiken", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 0c3438b09d..82375c64f6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1665,36 +1665,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Lagre prosjektminne automatisk", "settings.context.memory.autoSave.description": "Lagrer varige prosjektfakta automatisk fra fullførte turer når minne er aktivert.", - "settings.context.memory.index.title": "Minneindeks", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Ikke lastet", "settings.context.memory.status.disabled": "Deaktivert", - "settings.context.memory.status.enabledTokensOps": - "Aktivert - ~{{session}} oppstartstokener i denne økten - ~{{tokens}} tokener i lagret indeks - siste operasjon {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Aktiver minne for å opprette prosjektets minnefiler.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Inspiser", - "settings.context.memory.rebuild": "Bygg minneindeks på nytt", - "chat.memory.status.loading": "Laster minnestatus", - "chat.memory.status.active": "Minne aktivt i denne økten", - "chat.memory.project.enabled": "Prosjektminne aktivert", "chat.memory.project.disabled": "Prosjektminne deaktivert", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Minnekommando mislyktes", - "chat.memory.inspect": "Inspiser minne", - "chat.memory.remember": "Husk", - "chat.memory.forget": "Glem", - "chat.memory.rebuild": "Bygg indeks på nytt", - "chat.memory.disable": "Deaktiver minne", - "chat.memory.enable": "Aktiver minne", - "chat.memory.verbose": "Detaljert", - "chat.memory.activity.idle": "Ingen minneaktivitet i denne økten", - "chat.memory.activity.loaded": "lastet inn {{tokens}} tokener", - "chat.memory.activity.recalled": "hentet {{count}}", - "chat.memory.activity.saved": "lagret {{count}}", - "chat.memory.activity.loaded.item": "lastet inn: {{item}}", - "chat.memory.activity.recalled.item": "hentet: {{item}}", - "chat.memory.activity.saved.item": "lagret: {{item}}", - "chat.memory.badge.recalled": "Minne hentet", - "chat.memory.badge.items": "{{count}} elementer", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Bruk egendefinert prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index e83736e700..762e3499ec 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1674,36 +1674,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Automatycznie zapisuj pamięć projektu", "settings.context.memory.autoSave.description": "Automatycznie zapisuje trwałe fakty projektu z zakończonych tur, gdy pamięć jest włączona.", - "settings.context.memory.index.title": "Indeks pamięci", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Nie wczytano", "settings.context.memory.status.disabled": "Wyłączona", - "settings.context.memory.status.enabledTokensOps": - "Włączona - ~{{session}} tokenów kontekstu startowego w tej sesji - ~{{tokens}} tokenów w zapisanym indeksie - ostatnia operacja {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Włącz pamięć, aby utworzyć pliki pamięci projektu.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Sprawdź", - "settings.context.memory.rebuild": "Odbuduj indeks pamięci", - "chat.memory.status.loading": "Ładowanie stanu pamięci", - "chat.memory.status.active": "Pamięć aktywna w tej sesji", - "chat.memory.project.enabled": "Pamięć projektu włączona", "chat.memory.project.disabled": "Pamięć projektu wyłączona", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Polecenie pamięci nie powiodło się", - "chat.memory.inspect": "Sprawdź pamięć", - "chat.memory.remember": "Zapamiętaj", - "chat.memory.forget": "Zapomnij", - "chat.memory.rebuild": "Odbuduj indeks", - "chat.memory.disable": "Wyłącz pamięć", - "chat.memory.enable": "Włącz pamięć", - "chat.memory.verbose": "Szczegółowo", - "chat.memory.activity.idle": "Brak aktywności pamięci w tej sesji", - "chat.memory.activity.loaded": "wczytano {{tokens}} tokenów", - "chat.memory.activity.recalled": "przywołano {{count}}", - "chat.memory.activity.saved": "zapisano {{count}}", - "chat.memory.activity.loaded.item": "wczytano: {{item}}", - "chat.memory.activity.recalled.item": "przywołano: {{item}}", - "chat.memory.activity.saved.item": "zapisano: {{item}}", - "chat.memory.badge.recalled": "Pamięć przywołana", - "chat.memory.badge.items": "{{count}} elementów", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Użyj niestandardowego prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 26be38f1ba..e8f2586887 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1673,36 +1673,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Автосохранение памяти проекта", "settings.context.memory.autoSave.description": "Автоматически сохраняет устойчивые факты проекта из завершённых ходов, когда память включена.", - "settings.context.memory.index.title": "Индекс памяти", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Не загружена", "settings.context.memory.status.disabled": "Отключена", - "settings.context.memory.status.enabledTokensOps": - "Включена - ~{{session}} токенов начального контекста в этой сессии - ~{{tokens}} токенов в сохранённом индексе - последняя операция {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Включите память, чтобы создать файлы памяти проекта.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Проверить", - "settings.context.memory.rebuild": "Перестроить индекс памяти", - "chat.memory.status.loading": "Загрузка состояния памяти", - "chat.memory.status.active": "Память активна в этом сеансе", - "chat.memory.project.enabled": "Память проекта включена", "chat.memory.project.disabled": "Память проекта отключена", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Команда памяти не выполнена", - "chat.memory.inspect": "Проверить память", - "chat.memory.remember": "Запомнить", - "chat.memory.forget": "Забыть", - "chat.memory.rebuild": "Перестроить индекс", - "chat.memory.disable": "Отключить память", - "chat.memory.enable": "Включить память", - "chat.memory.verbose": "Подробно", - "chat.memory.activity.idle": "В этом сеансе нет активности памяти", - "chat.memory.activity.loaded": "загружено {{tokens}} токенов", - "chat.memory.activity.recalled": "извлечено {{count}}", - "chat.memory.activity.saved": "сохранено {{count}}", - "chat.memory.activity.loaded.item": "загружено: {{item}}", - "chat.memory.activity.recalled.item": "извлечено: {{item}}", - "chat.memory.activity.saved.item": "сохранено: {{item}}", - "chat.memory.badge.recalled": "Память извлечена", - "chat.memory.badge.items": "{{count}} элементов", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Использовать пользовательский prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 9f4ce36ba2..9b4db93245 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1643,36 +1643,18 @@ export const dict = { "settings.context.memory.autoSave.title": "บันทึกความจำของโปรเจกต์อัตโนมัติ", "settings.context.memory.autoSave.description": "บันทึกข้อเท็จจริงถาวรของโปรเจกต์จากรอบที่เสร็จแล้วโดยอัตโนมัติเมื่อเปิดใช้ความจำ", - "settings.context.memory.index.title": "ดัชนีความจำ", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "ยังไม่ได้โหลด", "settings.context.memory.status.disabled": "ปิดใช้งาน", - "settings.context.memory.status.enabledTokensOps": - "เปิดใช้งาน - ~{{session}} โทเค็นบริบทเริ่มต้นในเซสชันนี้ - ~{{tokens}} โทเค็นในดัชนีที่บันทึกไว้ - การทำงานล่าสุด {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "เปิดใช้ความจำเพื่อสร้างไฟล์ความจำของโปรเจกต์", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "ตรวจสอบ", - "settings.context.memory.rebuild": "สร้างดัชนีความจำใหม่", - "chat.memory.status.loading": "กำลังโหลดสถานะความจำ", - "chat.memory.status.active": "หน่วยความจำทำงานในเซสชันนี้", - "chat.memory.project.enabled": "เปิดใช้ความจำของโปรเจกต์แล้ว", "chat.memory.project.disabled": "ปิดใช้ความจำของโปรเจกต์แล้ว", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "คำสั่งความจำล้มเหลว", - "chat.memory.inspect": "ตรวจสอบความจำ", - "chat.memory.remember": "จำ", - "chat.memory.forget": "ลืม", - "chat.memory.rebuild": "สร้างดัชนีใหม่", - "chat.memory.disable": "ปิดใช้ความจำ", - "chat.memory.enable": "เปิดใช้หน่วยความจำ", - "chat.memory.verbose": "แบบละเอียด", - "chat.memory.activity.idle": "ไม่มีกิจกรรมหน่วยความจำในเซสชันนี้", - "chat.memory.activity.loaded": "โหลด {{tokens}} โทเค็นแล้ว", - "chat.memory.activity.recalled": "เรียกคืน {{count}} รายการแล้ว", - "chat.memory.activity.saved": "บันทึก {{count}} รายการแล้ว", - "chat.memory.activity.loaded.item": "โหลดแล้ว: {{item}}", - "chat.memory.activity.recalled.item": "เรียกคืนแล้ว: {{item}}", - "chat.memory.activity.saved.item": "บันทึกแล้ว: {{item}}", - "chat.memory.badge.recalled": "เรียกคืนความจำแล้ว", - "chat.memory.badge.items": "{{count}} รายการ", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "ใช้ prompt แบบกำหนดเอง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 9019d1d4b0..997798a9f0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1630,36 +1630,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Proje belleğini otomatik kaydet", "settings.context.memory.autoSave.description": "Bellek açıkken tamamlanan turlardan kalıcı proje gerçeklerini otomatik olarak kaydeder.", - "settings.context.memory.index.title": "Bellek indeksi", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Yüklenmedi", "settings.context.memory.status.disabled": "Devre dışı", - "settings.context.memory.status.enabledTokensOps": - "Etkin - bu oturumda ~{{session}} başlangıç bağlamı tokenı - depolanan indekste ~{{tokens}} token - son işlem {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Proje belleği dosyalarını oluşturmak için belleği etkinleştirin.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "İncele", - "settings.context.memory.rebuild": "Bellek indeksini yeniden oluştur", - "chat.memory.status.loading": "Bellek durumu yükleniyor", - "chat.memory.status.active": "Bellek bu oturumda etkin", - "chat.memory.project.enabled": "Proje belleği etkin", "chat.memory.project.disabled": "Proje belleği devre dışı", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Bellek komutu başarısız oldu", - "chat.memory.inspect": "Belleği incele", - "chat.memory.remember": "Hatırla", - "chat.memory.forget": "Unut", - "chat.memory.rebuild": "İndeksi yeniden oluştur", - "chat.memory.disable": "Belleği devre dışı bırak", - "chat.memory.enable": "Belleği etkinleştir", - "chat.memory.verbose": "Ayrıntılı", - "chat.memory.activity.idle": "Bu oturumda bellek etkinliği yok", - "chat.memory.activity.loaded": "{{tokens}} token yüklendi", - "chat.memory.activity.recalled": "{{count}} öğe geri çağrıldı", - "chat.memory.activity.saved": "{{count}} öğe kaydedildi", - "chat.memory.activity.loaded.item": "yüklendi: {{item}}", - "chat.memory.activity.recalled.item": "geri çağrıldı: {{item}}", - "chat.memory.activity.saved.item": "kaydedildi: {{item}}", - "chat.memory.badge.recalled": "Bellek geri çağrıldı", - "chat.memory.badge.items": "{{count}} öğe", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Özel prompt Kullan", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index f0b55e6374..c7edb20c28 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1627,36 +1627,18 @@ export const dict = { "settings.context.memory.autoSave.title": "Автозбереження пам’яті проєкту", "settings.context.memory.autoSave.description": "Автоматично зберігає сталі факти проєкту із завершених ходів, коли пам’ять увімкнено.", - "settings.context.memory.index.title": "Індекс пам’яті", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "Не завантажено", "settings.context.memory.status.disabled": "Вимкнено", - "settings.context.memory.status.enabledTokensOps": - "Увімкнено - ~{{session}} токенів стартового контексту в цій сесії - ~{{tokens}} токенів у збереженому індексі - остання операція {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "Увімкніть пам’ять, щоб створити файли пам’яті проєкту.", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "Перевірити", - "settings.context.memory.rebuild": "Перебудувати індекс пам’яті", - "chat.memory.status.loading": "Завантаження стану пам’яті", - "chat.memory.status.active": "Пам’ять активна в цьому сеансі", - "chat.memory.project.enabled": "Пам’ять проєкту увімкнено", "chat.memory.project.disabled": "Пам’ять проєкту вимкнено", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "Команду пам’яті не виконано", - "chat.memory.inspect": "Перевірити пам’ять", - "chat.memory.remember": "Запам’ятати", - "chat.memory.forget": "Забути", - "chat.memory.rebuild": "Перебудувати індекс", - "chat.memory.disable": "Вимкнути пам’ять", - "chat.memory.enable": "Увімкнути пам’ять", - "chat.memory.verbose": "Докладно", - "chat.memory.activity.idle": "У цьому сеансі немає активності пам’яті", - "chat.memory.activity.loaded": "завантажено {{tokens}} токенів", - "chat.memory.activity.recalled": "відновлено {{count}}", - "chat.memory.activity.saved": "збережено {{count}}", - "chat.memory.activity.loaded.item": "завантажено: {{item}}", - "chat.memory.activity.recalled.item": "відновлено: {{item}}", - "chat.memory.activity.saved.item": "збережено: {{item}}", - "chat.memory.badge.recalled": "Пам’ять відновлено", - "chat.memory.badge.items": "{{count}} елементів", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "Використовувати власний prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 884e6a4e37..3444a21d8d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1595,36 +1595,18 @@ export const dict = { "settings.context.memory.project.title": "项目记忆", "settings.context.memory.autoSave.title": "自动保存项目记忆", "settings.context.memory.autoSave.description": "启用记忆时,自动从已完成轮次保存持久项目事实。", - "settings.context.memory.index.title": "记忆索引", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "未加载", "settings.context.memory.status.disabled": "已禁用", - "settings.context.memory.status.enabledTokensOps": - "已启用 - 本会话启动上下文约 {{session}} 个 token - 已存储索引约 {{tokens}} 个 token - 上次操作 {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "启用记忆以创建项目记忆文件。", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "检查", - "settings.context.memory.rebuild": "重建记忆索引", - "chat.memory.status.loading": "正在加载记忆状态", - "chat.memory.status.active": "本次会话中记忆已启用", - "chat.memory.project.enabled": "项目记忆已启用", "chat.memory.project.disabled": "项目记忆已禁用", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "记忆命令失败", - "chat.memory.inspect": "检查记忆", - "chat.memory.remember": "记住", - "chat.memory.forget": "忘记", - "chat.memory.rebuild": "重建索引", - "chat.memory.disable": "禁用记忆", - "chat.memory.enable": "启用记忆", - "chat.memory.verbose": "详细", - "chat.memory.activity.idle": "本次会话中没有记忆活动", - "chat.memory.activity.loaded": "已加载 {{tokens}} 个 token", - "chat.memory.activity.recalled": "已召回 {{count}} 项", - "chat.memory.activity.saved": "已保存 {{count}} 项", - "chat.memory.activity.loaded.item": "已加载:{{item}}", - "chat.memory.activity.recalled.item": "已召回:{{item}}", - "chat.memory.activity.saved.item": "已保存:{{item}}", - "chat.memory.badge.recalled": "已召回记忆", - "chat.memory.badge.items": "{{count}} 项", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "使用自定义 prompt", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 2d5329b4f9..dfa2dd85f3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1559,36 +1559,18 @@ export const dict = { "settings.context.memory.project.title": "專案記憶", "settings.context.memory.autoSave.title": "自動儲存專案記憶", "settings.context.memory.autoSave.description": "啟用記憶時,自動從已完成回合儲存持久專案事實。", - "settings.context.memory.index.title": "記憶索引", + "settings.context.memory.storage.title": "Storage", "settings.context.memory.status.notLoaded": "未載入", "settings.context.memory.status.disabled": "已停用", - "settings.context.memory.status.enabledTokensOps": - "已啟用 - 此工作階段啟動內容約 {{session}} 個 token - 已儲存索引約 {{tokens}} 個 token - 上次操作 {{ops}}", - "settings.context.memory.index.path": "{{path}}/index.kmem", - "settings.context.memory.index.enable": "啟用記憶以建立專案記憶檔案。", + "settings.context.memory.status.enabledTokens": "Enabled - ~{{tokens}} stored tokens", + "settings.context.memory.storage.path": "{{path}}", + "settings.context.memory.storage.enable": "Enable memory to create project memory files.", "settings.context.memory.inspect": "檢查", - "settings.context.memory.rebuild": "重建記憶索引", - "chat.memory.status.loading": "正在載入記憶狀態", - "chat.memory.status.active": "本次工作階段中記憶已啟用", - "chat.memory.project.enabled": "專案記憶已啟用", "chat.memory.project.disabled": "專案記憶已停用", + "chat.memory.project.empty": "This project doesn't have any memory yet. It will start showing after you use Kilo.", "chat.memory.command.failed": "記憶命令失敗", - "chat.memory.inspect": "檢查記憶", - "chat.memory.remember": "記住", - "chat.memory.forget": "忘記", - "chat.memory.rebuild": "重建索引", - "chat.memory.disable": "停用記憶", - "chat.memory.enable": "啟用記憶", - "chat.memory.verbose": "詳細", - "chat.memory.activity.idle": "本次工作階段中沒有記憶活動", - "chat.memory.activity.loaded": "已載入 {{tokens}} 個 token", - "chat.memory.activity.recalled": "已召回 {{count}} 項", - "chat.memory.activity.saved": "已儲存 {{count}} 項", - "chat.memory.activity.loaded.item": "已載入:{{item}}", - "chat.memory.activity.recalled.item": "已召回:{{item}}", - "chat.memory.activity.saved.item": "已儲存:{{item}}", - "chat.memory.badge.recalled": "已召回記憶", - "chat.memory.badge.items": "{{count}} 項", + "chat.memory.updated": "Memory updated", + "chat.memory.rebuild": "Memory index rebuilt", "settings.commitMessage.title": "Commit Message", "settings.commitMessage.override.title": "使用自訂 prompt", diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx index f97d6fe945..7d5e6705e9 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -9,8 +9,6 @@ import type { Meta, StoryObj } from "storybook-solidjs-vite" import type { AssistantMessage } from "@kilocode/sdk/v2" -import { MemoryContract } from "@kilocode/kilo-memory/effect/httpapi" -import { MemorySchema } from "@kilocode/kilo-memory/schema" import { StoryProviders, defaultMockData, mockSessionValue } from "./StoryProviders" import { ChatView } from "../components/chat/ChatView" import { ErrorDisplay } from "../components/chat/ErrorDisplay" @@ -22,7 +20,6 @@ import { MessageList } from "../components/chat/MessageList" import { VscodeUserMessage } from "../components/chat/VscodeUserMessage" import { TurnOutcome } from "../components/shared/TurnOutcome" import { SessionContext } from "../context/session" -import { MemoryContext, type MemoryContextValue } from "../context/memory" import { ProviderContext } from "../context/provider" import { ServerContext } from "../context/server" import { WorktreeModeProvider } from "../context/worktree-mode" @@ -991,88 +988,6 @@ export const TaskHeaderWithTodosAllDone: Story = { }, } -const state = MemorySchema.create() -const mockMemory: MemoryContextValue = { - status: () => ({ - root: "/project", - state: MemoryContract.state({ - ...state, - enabled: true, - stats: { - ...state.stats, - lastInjectedAt: headerNow, - lastInjectedBytes: 2_132, - lastInjectedTokens: 533, - lastInjectedSessionID: SESSION_ID, - }, - }), - exists: { state: true, index: true }, - index: { bytes: 49_600, estimatedTokens: 12_400, preview: "" }, - }), - show: () => undefined, - loading: () => false, - pending: () => false, - error: () => undefined, - enabled: () => true, - sessionTokens: () => 533, - totalTokens: () => 12_400, - activity: () => [ - { - type: "loaded", - at: headerNow, - tokens: 533, - count: 1, - items: [], - refs: ["project.md"], - }, - ], - refresh: () => {}, - showMemory: () => {}, - enable: () => {}, - disable: () => {}, - auto: () => {}, - verbose: () => {}, - rebuild: () => {}, - remember: () => {}, - forget: () => {}, -} - -const memoryHeader = (width: string) => { - const session = { - ...mockSessionValue({ id: SESSION_ID, status: "idle" }), - messages: () => [{ id: "msg-001" }] as any[], - contextUsage: () => ({ tokens: 34300, percentage: 17 }), - costBreakdown: () => [{ label: "Session", cost: 0.64 }], - currentSession: () => ({ - id: SESSION_ID, - title: "Integrate project memory", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }), - } - return ( - - - - - - - - - - ) -} - -export const TaskHeaderWithMemory: Story = { - name: "TaskHeader — with memory enabled", - render: () => memoryHeader("380px"), -} - -export const TaskHeaderWithMemory200: Story = { - name: "TaskHeader — with memory enabled 200", - render: () => memoryHeader("200px"), -} - const usageTokens = { input: 25_900_000, output: 52_000, reasoning: 4_100, cache: { read: 10_500_000, write: 80_000 } } const usageData = { sessionIDs: [SESSION_ID, "story-subagent-001"], diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css index da8ef5d18e..123ca91680 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css @@ -228,14 +228,6 @@ width: 100%; } -[data-component="assistant-memory-badge"] { - align-self: flex-start; - color: var(--vscode-descriptionForeground); - font-family: var(--font-family-sans); - font-size: var(--font-size-small); - line-height: var(--line-height-normal); -} - .vscode-session-turn-diffs { width: 100%; } diff --git a/packages/kilo-vscode/webview-ui/src/styles/task-header.css b/packages/kilo-vscode/webview-ui/src/styles/task-header.css index e6097cd989..96d48734d0 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/task-header.css +++ b/packages/kilo-vscode/webview-ui/src/styles/task-header.css @@ -473,172 +473,6 @@ font-variant-numeric: tabular-nums; } -.task-header-context-trigger { - all: unset; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 3px; - color: var(--vscode-foreground); - cursor: pointer; -} - -.task-header-context-trigger:hover { - background: var(--vscode-toolbar-hoverBackground); -} - -[data-slot="task-header-memory-dot"] { - position: absolute; - right: 2px; - bottom: 2px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--vscode-testing-iconPassed, #73c991); -} - -.task-header-context-popover { - width: min(280px, calc(100vw - 16px)); - max-width: calc(100vw - 16px); - white-space: normal; - overflow-wrap: break-word; -} - -.task-header-memory-tooltip { - max-width: min(320px, calc(100vw - 16px)); - white-space: normal; - overflow-wrap: break-word; -} - -[data-slot="task-header-context-tooltip-title"] { - margin-bottom: 4px; - color: var(--vscode-foreground); - font-weight: 600; -} - -[data-slot="task-header-context-tooltip-status"] { - margin-bottom: 2px; - color: var(--vscode-foreground); -} - -[data-slot="task-header-context-menu"] { - display: flex; - flex-direction: column; - padding: 8px; -} - -[data-slot="task-header-context-title"] { - padding: 2px 4px 8px; - color: var(--vscode-foreground); - font-size: var(--kilo-font-size-12); - font-weight: 600; -} - -[data-slot="task-header-context-section"] { - display: flex; - flex-direction: column; - gap: 6px; - padding: 6px 0; -} - -[data-slot="task-header-context-section"] + [data-slot="task-header-context-section"] { - margin-top: 6px; - padding-top: 12px; - border-top: 1px solid var(--vscode-widget-border, var(--vscode-editorWidget-border)); -} - -[data-slot="task-header-context-section-title"] { - display: flex; - align-items: center; - gap: 4px; - padding: 0 4px; - color: var(--vscode-descriptionForeground); - font-size: var(--kilo-font-size-11); - font-weight: 600; -} - -[data-slot="task-header-memory-status"] { - padding: 0 4px; - color: var(--vscode-foreground); - font-size: var(--kilo-font-size-11); -} - -[data-slot="task-header-memory-activity"] { - min-width: 0; - padding: 0 4px; - color: var(--vscode-descriptionForeground); - font-size: var(--kilo-font-size-11); - white-space: normal; - overflow-wrap: break-word; -} - -[data-slot="task-header-memory-activity-summary"] { - display: flex; - flex-direction: column; - gap: 2px; -} - -[data-slot="task-header-memory-activity-list"] { - display: flex; - flex-direction: column; - gap: 2px; - margin-top: 4px; - min-width: 0; - color: var(--vscode-foreground); - line-height: 1.3; - white-space: normal; - overflow-wrap: break-word; -} - -[data-slot="task-header-memory-activity-list"] > div { - white-space: normal; - overflow-wrap: break-word; -} - -[data-slot="task-header-context-actions"] { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 4px; -} - -[data-slot="task-header-context-action"] { - border: 0; - border-radius: 3px; - padding: 4px 6px; - color: var(--vscode-foreground); - background: transparent; - font: inherit; - font-size: var(--kilo-font-size-11); - text-align: left; - cursor: pointer; -} - -[data-slot="task-header-context-action"]:last-child:nth-child(odd) { - grid-column: 1 / -1; -} - -[data-slot="task-header-context-action"]:hover:not(:disabled) { - background: var(--vscode-list-hoverBackground); -} - -[data-slot="task-header-context-action"]:disabled { - color: var(--vscode-disabledForeground); - cursor: default; -} - -[data-slot="task-header-memory-verbose"] { - display: flex; - align-items: center; - justify-content: space-between; - padding-top: 6px; - border-top: 1px solid var(--vscode-widget-border, var(--vscode-editorWidget-border)); - color: var(--vscode-foreground); - font-size: var(--kilo-font-size-11); -} - /* Expand toggle */ [data-slot="task-header-expand"] { all: unset; diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/memory.ts b/packages/kilo-vscode/webview-ui/src/types/messages/memory.ts index 7db8142a71..4f2283c5ef 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/memory.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/memory.ts @@ -7,13 +7,9 @@ import type { MemoryPurgeResponse, MemoryRememberResponse, MemoryRebuildResponse, - MemoryShowResponse, MemoryStatusResponse, } from "@kilocode/sdk/v2" -import type { - MemoryOperation as SharedMemoryOperation, - MemoryPromptOperation as SharedMemoryPromptOperation, -} from "@kilocode/kilo-memory/commands" +import type { MemoryOperation as SharedMemoryOperation } from "@kilocode/kilo-memory/commands" import type { MemorySchema } from "@kilocode/kilo-memory/schema" export type MemorySourceFile = MemorySchema.Source @@ -22,8 +18,6 @@ export type MemoryOperation = SharedMemoryOperation export type MemoryResultOperation = MemoryOperation -export type MemoryPromptOperation = SharedMemoryPromptOperation - export type MemoryOperationResponse = | MemoryEnableResponse | MemoryConfigureResponse @@ -39,7 +33,6 @@ export interface MemoryLoadedMessage { type: "memoryLoaded" sessionID?: string status?: MemoryStatusResponse - show?: MemoryShowResponse error?: string } @@ -69,7 +62,6 @@ export interface MemoryOperationResultMessage { sessionID?: string ok: boolean status?: MemoryStatusResponse - show?: MemoryShowResponse result?: MemoryOperationResponse error?: string } @@ -77,12 +69,12 @@ export interface MemoryOperationResultMessage { export interface RequestMemoryMessage { type: "requestMemory" sessionID?: string - includeSources?: boolean } export interface MemoryShowMessage { type: "memoryShow" sessionID?: string + mode?: "status" | "show" } export interface MemoryOperationMessage { @@ -97,9 +89,3 @@ export interface MemoryOperationMessage { file?: MemorySourceFile section?: string } - -export interface MemoryPromptMessage { - type: "memoryPrompt" - operation: MemoryPromptOperation - sessionID?: string -} diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts index 7e9cbe199f..48108ab9eb 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts @@ -15,7 +15,7 @@ import type { SkipLegacyMigrationMessage, StartMigrationMessage, } from "./migration" -import type { MemoryShowMessage, MemoryOperationMessage, MemoryPromptMessage, RequestMemoryMessage } from "./memory" +import type { MemoryShowMessage, MemoryOperationMessage, RequestMemoryMessage } from "./memory" // ============================================ // Messages FROM webview TO extension @@ -1412,7 +1412,6 @@ export type WebviewMessage = | RequestMemoryMessage | MemoryShowMessage | MemoryOperationMessage - | MemoryPromptMessage | CreateSectionRequest | RenameSectionRequest | DeleteSectionRequest diff --git a/packages/kilo-vscode/webview-ui/src/utils/memory-activity.ts b/packages/kilo-vscode/webview-ui/src/utils/memory-activity.ts deleted file mode 100644 index a0a8afa4a2..0000000000 --- a/packages/kilo-vscode/webview-ui/src/utils/memory-activity.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { MemoryMarkerMeta } from "@kilocode/kilo-memory/marker-meta" -import type { MemoryEventDetail } from "../types/messages/memory" - -const LIMIT = 50 - -export type MemoryActivity = { - type: "loaded" | "recalled" | "saved" - at: number - tokens: number - count: number - items: string[] - refs: string[] -} - -export function markerActivity(parts: readonly MemoryMarkerMeta.Part[], at: number): MemoryActivity | undefined { - const meta = MemoryMarkerMeta.fromParts(parts) - if (!meta) return - return { - type: meta.type === "startup" ? "loaded" : "recalled", - at, - tokens: meta.tokens, - count: meta.count, - items: meta.items, - refs: meta.files, - } -} - -export function addMemoryActivity( - items: readonly MemoryActivity[], - detail: MemoryEventDetail, - at: number, -): MemoryActivity[] { - if (detail.type !== "saved") return [...items] - const count = detail.added ?? detail.operationCount ?? 1 - if (count <= 0) return [...items] - const item: MemoryActivity = { - type: "saved", - at, - tokens: 0, - count, - items: detail.message ? [detail.message] : [], - refs: [...(detail.sources ?? []), ...(detail.files ?? [])], - } - return [...items, item].slice(-LIMIT) -} diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-memory.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-memory.tsx index 041642085b..0f0000ab28 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-memory.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-memory.tsx @@ -12,6 +12,8 @@ import { useTheme } from "@tui/context/theme" import { useTuiConfig } from "@tui/config" import { useBindings } from "@tui/keymap" import { useDialog, type DialogContext } from "@tui/ui/dialog" +import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select" +import { useToast } from "@tui/ui/toast" import { getScrollAcceleration } from "@tui/util/scroll" import { route } from "@/kilocode/cli/cmd/tui/memory-command" import { errorMessage } from "@/util/error" @@ -41,9 +43,14 @@ export function showMemoryDialog(dialog: DialogContext, input?: { workspace?: st dialog.replace(() => ) } -export function showMemoryHelpDialog(dialog: DialogContext, reason?: string) { +export function showMemoryHelpDialog( + dialog: DialogContext, + input?: { workspace?: string; directory?: string; reason?: string }, +) { dialog.setSize("large") - dialog.replace(() => ) + dialog.replace(() => ( + + )) } export function showMemoryStatusDialog(dialog: DialogContext, input?: { workspace?: string; directory?: string }) { @@ -99,32 +106,6 @@ function MemorySourcesInfo(props: { ) } -function MemoryActivityInfo(props: { - state: { - autoInject: boolean - stats: MemoryAutosaveStatus.Stats & { - lastInjectedTokens: number - lastRecallCount: number - } - } -}) { - const { theme } = useTheme() - return ( - - Activity - - startup context {props.state.autoInject ? "on" : "off"} - {props.state.stats.lastInjectedTokens > 0 - ? ` · last injected ${fmt(props.state.stats.lastInjectedTokens)} tokens` - : ""} - - 0}> - last recall {fmt(props.state.stats.lastRecallCount)} items - - - ) -} - function MemoryItemsInfo(props: { items: string }) { const { theme } = useTheme() return ( @@ -137,36 +118,42 @@ function MemoryItemsInfo(props: { items: string }) { ) } -export function DialogMemoryHelp(props: { reason?: string }) { +function draft(usage: string) { + const head = usage.split(" ")[0] + if (usage.includes("<") || usage.includes("|")) return `${head} ` + return usage +} + +export function DialogMemoryHelp(props: { workspace?: string; directory?: string; reason?: string }) { + const sdk = useSDK() + const project = useProject() const dialog = useDialog() const { theme } = useTheme() + const toast = useToast() + const options: DialogSelectOption[] = MEMORY_COMMAND_CATALOG.map((item) => ({ + title: item.description, + footer: `/memory ${item.usage}`, + category: "Memory", + value: item.usage, + })) return ( - - - - Memory - - dialog.clear()}> - esc - - - {(reason) => {reason()}} - - - {(item) => ( - - - /memory {item.usage} - - - {item.description} - - - )} - - - + {(reason) => {reason()}}} + onSelect={async (option) => { + dialog.clear() + const workspace = props.workspace ?? project.workspace.current() + const result = await sdk.client.tui.appendPrompt({ + ...route({ workspace, directory: props.directory }), + text: `/memory ${draft(option.value)}`, + }) + if (!result.error) return + toast.show({ variant: "error", message: `Memory menu failed: ${errorMessage(result.error)}` }) + }} + /> ) } @@ -218,14 +205,6 @@ function DialogMemoryStatus(props: { workspace?: string; directory?: string }) { Auto-save sends best-effort-redacted turn context to your configured model provider; disable with /memory auto off. - - Verbose - {item().state.verbose ? "on" : "off"} - - Verbose shows recall and save details; toggle with /memory verbose on|off. - - - @@ -307,7 +286,6 @@ export function DialogMemory(props: { workspace?: string; directory?: string }) - diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-prompt.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-prompt.tsx index c1aac24d7f..2a202e1c63 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-prompt.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-prompt.tsx @@ -1,5 +1,5 @@ import type { KiloClient } from "@kilocode/sdk/v2" -import type { CliRenderer } from "@opentui/core" +import open from "open" import type { DialogContext } from "@tui/ui/dialog" import type { ToastContext } from "@tui/ui/toast" import { @@ -18,7 +18,6 @@ export namespace MemoryPrompt { sessionID?: string toast: ToastContext dialog: DialogContext - renderer?: CliRenderer done(): void }) { const handled = await runMemoryCommand({ @@ -28,10 +27,13 @@ export namespace MemoryPrompt { directory: input.directory, sessionID: input.sessionID, toast: input.toast, - renderer: input.renderer, + inspect: async (root) => { + await open(root) + }, show: () => showMemoryDialog(input.dialog, { workspace: input.workspace, directory: input.directory }), status: () => showMemoryStatusDialog(input.dialog, { workspace: input.workspace, directory: input.directory }), - usage: (message) => showMemoryHelpDialog(input.dialog, message), + usage: (reason) => + showMemoryHelpDialog(input.dialog, { workspace: input.workspace, directory: input.directory, reason }), }) if (!handled) return false input.done() diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-status.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-status.tsx index 0715b7a219..94c1698393 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-status.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/memory-status.tsx @@ -1,6 +1,6 @@ import type { TuiPluginApi } from "@kilocode/plugin/tui" import type { Event } from "@kilocode/sdk/v2" -import { createEffect, createMemo, createResource, createSignal, onCleanup, onMount, Show } from "solid-js" +import { createMemo, createResource, createSignal, onCleanup, onMount, Show } from "solid-js" import * as Log from "@opencode-ai/core/util/log" import { route } from "@/kilocode/cli/cmd/tui/memory-command" import { MemoryTuiMeta } from "@/kilocode/cli/cmd/tui/memory-meta" @@ -10,12 +10,9 @@ export function memoryRow(input: { enabled?: boolean loading?: boolean active: boolean - verbose: boolean - flash?: string }): { label: "Loading" | "Unavailable" | "Disabled" | "Enabled" tone: "muted" | "success" | "error" - caption?: string } { if (input.enabled === undefined) { return input.loading ? { label: "Loading", tone: "muted" } : { label: "Unavailable", tone: "error" } @@ -24,7 +21,6 @@ export function memoryRow(input: { return { label: "Enabled", tone: input.active ? ("success" as const) : ("muted" as const), - caption: input.verbose ? input.flash : undefined, } } @@ -54,33 +50,9 @@ export function MemorySidebar(props: { api: TuiPluginApi; sessionID: string }) { }), ), ) - const [flash, setFlash] = createSignal() const [saved, setSaved] = createSignal(false) - const timer = { id: undefined as ReturnType | undefined } const pulse = { id: undefined as ReturnType | undefined } - const prior = { ids: new Set(), ready: false } - const show = (label: string) => { - if (timer.id) clearTimeout(timer.id) - setFlash(label) - timer.id = setTimeout(() => { - setFlash() - timer.id = undefined - }, 5_000) - } - createEffect(() => { - const items = markers() - if (prior.ready) { - for (const item of items) { - if (!prior.ids.has(item.id) && MemoryTuiState.verbose(data())) { - show(item.meta.type === "recall" ? `recalled ${item.meta.count}` : "loaded") - } - } - } - prior.ids = new Set(items.map((item) => item.id)) - prior.ready = true - }) onCleanup(() => { - if (timer.id) clearTimeout(timer.id) if (pulse.id) clearTimeout(pulse.id) }) const state = createMemo(() => @@ -88,8 +60,6 @@ export function MemorySidebar(props: { api: TuiPluginApi; sessionID: string }) { enabled: data() && MemoryTuiState.enabled(data()), loading: data.loading, active: MemoryTuiState.active({ markers: markers().length, saved: saved() }), - verbose: MemoryTuiState.verbose(data()), - flash: flash(), }), ) @@ -97,16 +67,13 @@ export function MemorySidebar(props: { api: TuiPluginApi; sessionID: string }) { const bump = () => setTick((value) => value + 1) const save = (event: Extract) => { if (event.properties.sessionID !== props.sessionID) return - const detail = event.properties.detail - if (detail?.type !== "saved") return + if (event.properties.detail?.type !== "saved") return setSaved(true) if (pulse.id) clearTimeout(pulse.id) pulse.id = setTimeout(() => { setSaved(false) pulse.id = undefined }, 5_000) - if (!MemoryTuiState.verbose(data()) || typeof detail.operationCount !== "number") return - show(`saved ${detail.operationCount}`) } const offs = [ props.api.event.on("memory.status", (event) => { @@ -149,7 +116,6 @@ export function MemorySidebar(props: { api: TuiPluginApi; sessionID: string }) { {row().label} - {row().caption && · {row().caption}} diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/memory-command.ts b/packages/opencode/src/kilocode/cli/cmd/tui/memory-command.ts index ae22e7e5b4..d73fd594de 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/memory-command.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/memory-command.ts @@ -1,13 +1,5 @@ import type { KiloClient } from "@kilocode/sdk/v2" -import type { CliRenderer } from "@opentui/core" -import path from "path" -import { Process } from "@/util/process" -import { splitCommand } from "@/kilocode/util/split-command" -import { - MEMORY_USAGE, - parseMemoryCommand, - type ParsedMemoryCommand, -} from "@kilocode/kilo-memory/commands" +import { MEMORY_USAGE, parseMemoryCommand, type ParsedMemoryCommand } from "@kilocode/kilo-memory/commands" import { errorMessage } from "@/util/error" export { MEMORY_USAGE } @@ -48,33 +40,6 @@ function auto(input: boolean) { return `Memory auto-save ${input ? "on" : "off"}` } -function verbose(input: boolean) { - return `Memory verbose ${input ? "on" : "off"}` -} - -async function edit(input: { file: string; cwd?: string; renderer?: CliRenderer }) { - const editor = (process.env["VISUAL"] || process.env["EDITOR"])?.trim() - if (!editor) throw new Error("Set $VISUAL or $EDITOR to use /memory edit") - - input.renderer?.suspend() - input.renderer?.currentRenderBuffer.clear() - try { - const proc = Process.spawn([...splitCommand(editor), input.file], { - cwd: input.cwd, - stdin: "inherit", - stdout: "inherit", - stderr: "inherit", - shell: process.platform === "win32", - }) - const code = await proc.exited - if (code !== 0) throw new Error(`Editor exited with code ${code}`) - } finally { - input.renderer?.currentRenderBuffer.clear() - input.renderer?.resume() - input.renderer?.requestRender() - } -} - export function parseMemoryInput(input: string): MemoryCommand | undefined { return parseMemoryCommand(input) } @@ -86,7 +51,7 @@ export async function runMemoryCommand(input: { directory?: string sessionID?: string toast: Toast - renderer?: CliRenderer + inspect?(root: string): void | Promise show(): void status(): void usage(message?: string): void @@ -120,12 +85,12 @@ export async function runMemoryCommand(input: { input.status() return true } - if (parsed.operation === "edit") { + if (parsed.operation === "inspect") { const status = read(await input.client.memory.status(route(input))) if (!status.state.enabled) throw new Error("Memory is disabled. Run /memory on first.") - await edit({ file: path.join(status.root, "project.md"), cwd: input.directory, renderer: input.renderer }) - const result = read(await input.client.memory.rebuild(route(input))) - input.toast.show({ variant: "success", message: `${name} rebuilt (${tokens(result.index.tokens)})` }) + if (!input.inspect) throw new Error("Memory folder inspection is unavailable") + input.toast.show({ variant: "info", message: `Memory folder: ${status.root}` }) + await input.inspect(status.root) return true } if (parsed.operation === "auto") { @@ -138,16 +103,6 @@ export async function runMemoryCommand(input: { input.toast.show({ variant: "info", message: auto(result.state.autoConsolidate) }) return true } - if (parsed.operation === "verbose") { - const result = read( - await input.client.memory.configure({ - ...route(input), - verbose: parsed.mode === "on", - }), - ) - input.toast.show({ variant: "info", message: verbose(result.state.verbose) }) - return true - } if (parsed.operation === "disable") { read(await input.client.memory.disable(route(input))) input.toast.show({ variant: "info", message: `${name} disabled` }) diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/memory-meta.ts b/packages/opencode/src/kilocode/cli/cmd/tui/memory-meta.ts index 94a2ac391f..5baef05dbe 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/memory-meta.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/memory-meta.ts @@ -4,10 +4,4 @@ export namespace MemoryTuiMeta { export function fromParts(parts: readonly MemoryMarkerMeta.Part[]) { return MemoryMarkerMeta.fromParts(parts) } - - export function items(input: unknown) { - const value = input as { items?: unknown } | undefined - if (!Array.isArray(value?.items)) return [] - return value.items.filter((item): item is string => typeof item === "string") - } } diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/memory-state.ts b/packages/opencode/src/kilocode/cli/cmd/tui/memory-state.ts index 4e42139106..bed37b9f4e 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/memory-state.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/memory-state.ts @@ -3,10 +3,6 @@ import type { MemoryStatusResponse } from "@kilocode/sdk/v2" type State = MemoryStatusResponse["state"] export namespace MemoryTuiState { - export function verbose(input: Pick | undefined) { - return input?.verbose ?? false - } - export function enabled(input: Pick | undefined) { return input?.enabled ?? false } diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/routes/session/memory.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/routes/session/memory.tsx index 1aca127c54..833483d5e3 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/routes/session/memory.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/routes/session/memory.tsx @@ -1,86 +1,7 @@ -import { createMemo, createResource, For, onCleanup, Show } from "solid-js" -import type { RGBA } from "@opentui/core" -import type { Part } from "@kilocode/sdk/v2" -import * as Log from "@opencode-ai/core/util/log" -import { useEvent } from "@tui/context/event" -import { useProject } from "@tui/context/project" -import { useSDK } from "@tui/context/sdk" -import { useSync } from "@tui/context/sync" -import { route } from "@/kilocode/cli/cmd/tui/memory-command" import { MemoryTuiEvents } from "@/kilocode/cli/cmd/tui/memory-events" -import { MemoryTuiMeta } from "@/kilocode/cli/cmd/tui/memory-meta" -import { MemoryTuiState } from "@/kilocode/cli/cmd/tui/memory-state" - -const log = Log.create({ service: "memory-tui" }) export namespace MemorySessionTui { export function attach(input: Parameters[0]) { return MemoryTuiEvents.attach(input) } - - export function verbose(input: { sessionID(): string }) { - const sdk = useSDK() - const project = useProject() - const sync = useSync() - const event = useEvent() - const session = createMemo(() => sync.session.get(input.sessionID())) - const [state, api] = createResource( - () => { - const item = session() - if (!item) return - return `${item.workspaceID ?? "__default__"}:${item.directory}` - }, - async () => { - try { - const item = session() - const result = await sdk.client.memory.status( - route({ workspace: item?.workspaceID ?? project.workspace.current(), directory: item?.directory }), - ) - return result.data?.state - } catch (err) { - log.warn("memory status unavailable", { err }) - return undefined - } - }, - ) - const refresh = (value: { properties: { sessionID?: string } }) => { - if (value.properties.sessionID && value.properties.sessionID !== input.sessionID()) return - void api.refetch() - } - const offs = [ - event.on("memory.status", refresh), - event.on("memory.updated", refresh), - event.on("memory.error", refresh), - ] - onCleanup(() => offs.forEach((off) => off())) - return () => MemoryTuiState.verbose(state()) - } -} - -export function MemoryMessageMeta(props: { parts: Part[]; color: string | RGBA; verbose(): boolean }) { - const item = createMemo(() => MemoryTuiMeta.fromParts(props.parts)) - - return ( - - {(meta) => { - const snippets = createMemo(() => - meta().type === "recall" - ? MemoryTuiMeta.items(meta()) - .slice(0, 2) - .map((text) => text.trim().slice(0, 80)) - .filter(Boolean) - : [], - ) - return ( - - {" "} - · memory · {meta().type === "startup" ? "Startup Context" : `recalled ${meta().count}`} - 0}> - {(text) => <> · {text}>} - - - ) - }} - - ) } diff --git a/packages/opencode/test/kilocode/cli/cmd/tui/memory-command.test.ts b/packages/opencode/test/kilocode/cli/cmd/tui/memory-command.test.ts index eced02db0b..42df534433 100644 --- a/packages/opencode/test/kilocode/cli/cmd/tui/memory-command.test.ts +++ b/packages/opencode/test/kilocode/cli/cmd/tui/memory-command.test.ts @@ -3,7 +3,6 @@ import type { KiloClient } from "@kilocode/sdk/v2" import { memoryRow } from "@/kilocode/cli/cmd/tui/component/memory-status" import { runMemoryCommand } from "@/kilocode/cli/cmd/tui/memory-command" import { MemoryTuiEvents } from "@/kilocode/cli/cmd/tui/memory-events" -import { MemoryTuiMeta } from "@/kilocode/cli/cmd/tui/memory-meta" import { MemoryTuiState } from "@/kilocode/cli/cmd/tui/memory-state" type Handler = (event: { @@ -64,16 +63,16 @@ describe("memory TUI command parser", () => { ]) }) - test("auto-save, verbose, and purge commands call explicit endpoints", async () => { + test("auto-save and purge commands call explicit endpoints", async () => { const shown: string[] = [] const calls: unknown[] = [] - const state = { autoConsolidate: true, verbose: false } + const state = { autoConsolidate: true } const client = { memory: { status: async () => ({ data: { state } }), configure: async (input: unknown) => { calls.push(input) - return { data: { state: { autoConsolidate: false, verbose: true } } } + return { data: { state: { autoConsolidate: false } } } }, purge: async (input: unknown) => { calls.push(input) @@ -96,17 +95,15 @@ describe("memory TUI command parser", () => { } await runMemoryCommand({ ...base, text: "/memory auto off" }) - await runMemoryCommand({ ...base, text: "/memory verbose on" }) await runMemoryCommand({ ...base, text: "/memory auto status" }) await runMemoryCommand({ ...base, text: "/memory purge" }) await runMemoryCommand({ ...base, text: "/memory purge confirm" }) expect(shown[0]).toBe("Memory auto-save off") - expect(shown[1]).toBe("Memory verbose on") - expect(shown[2]).toContain("Missing auto mode") - expect(shown[3]).toContain("Purge requires confirmation") - expect(shown[4]).toBe("Memory purged") - expect(calls).toEqual([{ autoConsolidate: false }, { verbose: true }, { confirm: true }]) + expect(shown[1]).toContain("Missing auto mode") + expect(shown[2]).toContain("Purge requires confirmation") + expect(shown[3]).toBe("Memory purged") + expect(calls).toEqual([{ autoConsolidate: false }, { confirm: true }]) }) test("status opens overview dialog", async () => { @@ -133,6 +130,31 @@ describe("memory TUI command parser", () => { expect(shown).toEqual([]) }) + test("inspect reveals the memory folder", async () => { + const opened: string[] = [] + const shown: string[] = [] + const client = { + memory: { + status: async () => ({ data: { root: "/tmp/kilo-memory", state: { enabled: true } } }), + }, + } as unknown as KiloClient + + await runMemoryCommand({ + text: "/memory inspect", + client, + toast: { show: (input) => shown.push(input.message) }, + inspect(root) { + opened.push(root) + }, + show() {}, + status() {}, + usage() {}, + }) + + expect(opened).toEqual(["/tmp/kilo-memory"]) + expect(shown).toEqual(["Memory folder: /tmp/kilo-memory"]) + }) + test("bare memory command opens help", async () => { const calls: unknown[] = [] const client = { memory: {} } as unknown as KiloClient @@ -204,7 +226,7 @@ describe("memory TUI command parser", () => { test("memory commands route to session directory when no workspace is active", async () => { const calls: unknown[] = [] - const state = { autoConsolidate: false, verbose: false } + const state = { autoConsolidate: false } const client = { memory: { configure: async (input: unknown) => { @@ -222,7 +244,6 @@ describe("memory TUI command parser", () => { } await runMemoryCommand({ ...base, text: "/memory auto off", directory: "/repo/packages/opencode" }) - await runMemoryCommand({ ...base, text: "/memory verbose on", directory: "/repo/packages/opencode" }) await runMemoryCommand({ ...base, text: "/memory auto off", @@ -232,7 +253,6 @@ describe("memory TUI command parser", () => { expect(calls).toEqual([ { directory: "/repo/packages/opencode", autoConsolidate: false }, - { directory: "/repo/packages/opencode", verbose: true }, { workspace: "wrk_123", autoConsolidate: false }, ]) }) @@ -288,63 +308,38 @@ describe("memory TUI events", () => { }) }) -describe("memory TUI metadata", () => { - test("reads typed verbose and activity state", () => { - expect(MemoryTuiState.verbose({ verbose: true })).toBe(true) - expect(MemoryTuiState.verbose(undefined)).toBe(false) +describe("memory TUI state", () => { + test("tracks active memory", () => { expect(MemoryTuiState.active({ markers: 1, saved: false })).toBe(true) expect(MemoryTuiState.active({ markers: 0, saved: true })).toBe(true) expect(MemoryTuiState.active({ markers: 0, saved: false })).toBe(false) - expect(MemoryTuiMeta.items({ items: ["first", 1, "second"] })).toEqual(["first", "second"]) - expect(MemoryTuiMeta.items({})).toEqual([]) }) }) describe("memory sidebar row", () => { test("shows loading, unavailable, and disabled states", () => { - expect(memoryRow({ loading: true, active: false, verbose: false })).toEqual({ + expect(memoryRow({ loading: true, active: false })).toEqual({ label: "Loading", tone: "muted", }) - expect(memoryRow({ active: false, verbose: false })).toEqual({ + expect(memoryRow({ active: false })).toEqual({ label: "Unavailable", tone: "error", }) - expect(memoryRow({ enabled: false, active: true, verbose: true, flash: "recalled 3" })).toEqual({ + expect(memoryRow({ enabled: false, active: true })).toEqual({ label: "Disabled", tone: "muted", }) }) test("uses muted and green dots for inactive and active sessions", () => { - expect(memoryRow({ enabled: true, active: false, verbose: false })).toEqual({ + expect(memoryRow({ enabled: true, active: false })).toEqual({ label: "Enabled", tone: "muted", }) - expect(memoryRow({ enabled: true, active: true, verbose: false })).toEqual({ + expect(memoryRow({ enabled: true, active: true })).toEqual({ label: "Enabled", tone: "success", }) }) - - test("adds verbose event captions without changing the activity tone", () => { - expect(memoryRow({ enabled: true, active: false, verbose: true, flash: "recalled 3" })).toEqual({ - label: "Enabled", - tone: "muted", - caption: "recalled 3", - }) - expect(memoryRow({ enabled: true, active: true, verbose: true, flash: "saved 2" })).toEqual({ - label: "Enabled", - tone: "success", - caption: "saved 2", - }) - }) - - test("omits verbose event captions when verbose is disabled", () => { - expect(memoryRow({ enabled: true, active: true, verbose: false, flash: "loaded" })).toEqual({ - label: "Enabled", - tone: "success", - caption: undefined, - }) - }) }) diff --git a/packages/opencode/test/kilocode/cli/cmd/tui/memory-status.test.tsx b/packages/opencode/test/kilocode/cli/cmd/tui/memory-status.test.tsx index 25abd342e5..070146a1a1 100644 --- a/packages/opencode/test/kilocode/cli/cmd/tui/memory-status.test.tsx +++ b/packages/opencode/test/kilocode/cli/cmd/tui/memory-status.test.tsx @@ -3,21 +3,10 @@ import { expect, spyOn, test } from "bun:test" import { RGBA } from "@opentui/core" import { testRender } from "@opentui/solid" import type { TuiPluginApi } from "@kilocode/plugin/tui" -import type { Event, GlobalEvent, Message, Part, Session } from "@kilocode/sdk/v2" +import type { Event, Message, Part, Session } from "@kilocode/sdk/v2" import { createSignal } from "solid-js" -import { ArgsProvider } from "@tui/context/args" -import { ExitProvider } from "@tui/context/exit" -import { KVProvider } from "@tui/context/kv" -import { ProjectProvider } from "@tui/context/project" -import { SDKProvider } from "@tui/context/sdk" -import { SyncProvider } from "@tui/context/sync" -import { ToastProvider } from "@tui/ui/toast" import { MemorySidebar } from "@/kilocode/cli/cmd/tui/component/memory-status" -import { MemoryMessageMeta, MemorySessionTui } from "@/kilocode/cli/cmd/tui/routes/session/memory" -import { Global } from "@opencode-ai/core/global" -import { createEventSource, createFetch, directory, json } from "../../../../fixture/tui-sdk" -import { tmpdir } from "../../../../fixture/fixture" -import { TestTuiContexts } from "../../../../fixture/tui-environment" +import { directory } from "../../../../fixture/tui-sdk" const id = "ses_memory_status" @@ -48,10 +37,6 @@ function event(sessionID?: string, count?: number): Extract boolean, timeout = 2_000) { const start = Date.now() while (!fn()) { @@ -60,115 +45,6 @@ async function wait(fn: () => boolean, timeout = 2_000) { } } -function Probe(props: { sessionID: string }) { - const verbose = MemorySessionTui.verbose({ sessionID: () => props.sessionID }) - return {verbose() ? "verbose" : "quiet"} -} - -test("session memory status refetches live and ignores other sessions", async () => { - await using tmp = await tmpdir() - const prior = Global.Path.state - Global.Path.state = tmp.path - await Bun.write(`${tmp.path}/kv.json`, "{}") - const events = createEventSource() - const state = { verbose: false } - const calls = { count: 0 } - const fetch = createFetch((url) => { - if (url.pathname === "/session") return json([session]) - if (url.pathname !== "/memory/status") return - calls.count += 1 - return json({ state: { verbose: state.verbose } }) - }) - try { - const app = await testRender(() => ( - - - {}}> - - - - - - - - - - - - - - - )) - try { - await wait(() => calls.count === 1 && app.captureCharFrame().includes("quiet")) - events.emit(global(event("ses_other"))) - await Bun.sleep(30) - expect(calls.count).toBe(1) - - state.verbose = true - events.emit(global(event(id))) - await wait(() => calls.count === 2 && app.captureCharFrame().includes("verbose")) - } finally { - app.renderer.destroy() - } - } finally { - Global.Path.state = prior - } -}) - -test("message memory metadata reacts to verbose changes and bounds snippets", async () => { - const [verbose, setVerbose] = createSignal(false) - const [parts, setParts] = createSignal([]) - const first = "a".repeat(100) - const part = { - id: "part_memory_recall", - sessionID: id, - messageID: "msg_memory_recall", - type: "text", - text: "", - metadata: { kiloMemory: { type: "recall", count: 3, items: [first, "second", "third"] } }, - } satisfies Part - setParts([part]) - const app = await testRender( - () => ( - - - - ), - { width: 200, height: 3 }, - ) - - try { - await app.renderOnce() - expect(app.captureCharFrame()).toContain("memory · recalled 3") - expect(app.captureCharFrame()).not.toContain("second") - - setVerbose(true) - await app.renderOnce() - expect(app.captureCharFrame()).toContain("a".repeat(80)) - expect(app.captureCharFrame()).not.toContain("a".repeat(81)) - expect(app.captureCharFrame()).toContain("second") - expect(app.captureCharFrame()).not.toContain("third") - - setVerbose(false) - await app.renderOnce() - expect(app.captureCharFrame()).not.toContain("second") - - setParts([ - { - ...part, - id: "part_memory_startup", - metadata: { kiloMemory: { type: "startup", count: 2, tokens: 40 } }, - }, - ]) - await app.renderOnce() - expect(app.captureCharFrame()).toContain("memory · Startup Context") - expect(app.captureCharFrame()).not.toContain("recalled") - } finally { - app.renderer.destroy() - } -}) - type Handler = (event: Event) => void function bus() { @@ -186,7 +62,7 @@ function bus() { } } -test("sidebar refetches status and scopes recall and save flashes", async () => { +test("sidebar refetches status and scopes save activity", async () => { const [parts, setParts] = createSignal([]) const events = bus() const calls = { count: 0 } @@ -203,7 +79,7 @@ test("sidebar refetches status and scopes recall and save flashes", async () => memory: { status: async () => { calls.count += 1 - return { data: { state: { enabled: true, verbose: true } } } + return { data: { state: { enabled: true } } } }, }, }, @@ -232,16 +108,15 @@ test("sidebar refetches status and scopes recall and save flashes", async () => metadata: { kiloMemory: { type: "recall", count: 2 } }, }, ]) - await wait(() => app.captureCharFrame().includes("recalled 2")) - await Bun.sleep(5_100) + await app.renderOnce() expect(app.captureCharFrame()).not.toContain("recalled 2") events.emit(event("ses_other", 4)) await wait(() => calls.count === 2) - expect(app.captureCharFrame()).not.toContain("saved 4") events.emit(event(id, 3)) - await wait(() => calls.count === 3 && app.captureCharFrame().includes("saved 3")) + await wait(() => calls.count === 3) + expect(app.captureCharFrame()).not.toContain("saved 3") const before = clear.mock.calls.length app.renderer.destroy() expect(clear.mock.calls.length).toBeGreaterThan(before) diff --git a/packages/opencode/test/kilocode/memory/memory-integration.test.ts b/packages/opencode/test/kilocode/memory/memory-integration.test.ts index fb7538a6b3..450ec205bf 100644 --- a/packages/opencode/test/kilocode/memory/memory-integration.test.ts +++ b/packages/opencode/test/kilocode/memory/memory-integration.test.ts @@ -257,10 +257,6 @@ describe("KiloMemory integration", () => { expect( events.find((event) => event.sessionID === "ses_memory_event" && event.detail?.type === "saved")?.detail?.tokens, ).toBeUndefined() - const decisions = await MemoryFiles.readDecisions(root) - expect(decisions).toContain('"trigger":"explicit"') - expect(decisions).toContain('"sessionID":"ses_memory_event"') - expect(decisions).toContain('"llm":false') }) test("explicit forget reports removals without save wording", async () => { @@ -288,9 +284,6 @@ describe("KiloMemory integration", () => { expect(events.some((event) => event.detail?.message === "Memory updated · 1 removed")).toBe(true) expect(events.some((event) => event.detail?.message?.includes("Memory saved"))).toBe(false) - const decisions = await MemoryFiles.readDecisions(root) - expect(decisions).toContain("explicit memory operation removed 1 entries") - expect(decisions).toContain("explicit memory operation matched no source memory") }) test("environment prompt rebuilds stale session index format", async () => { @@ -396,8 +389,8 @@ describe("KiloMemory integration", () => { expect(after.sources).toEqual(before.sources) expect(after.index).toBe(before.index) - expect(after.changes).toBe(before.changes) - expect(after.decisions).toBe(before.decisions) + expect(after.changes).toBe("") + expect(after.decisions).toBe("") expect(after.sources.project).toContain("stable_fact") expect(after.sources.project).not.toContain("disabled_fact") expect(after.sources.corrections).not.toContain("Do not correct while disabled") @@ -430,9 +423,6 @@ describe("KiloMemory integration", () => { expect(shown.sources.project).toContain("- repo_style :: Repo convention: commit messages are concise.") expect(shown.sources.project).not.toContain("reply_style") expect(shown.sources.project).not.toContain("I prefer terse summaries") - expect(shown.decisions).toContain('"reason":"out_of_scope"') - expect(shown.decisions).not.toContain("reply_style") - expect(shown.decisions).not.toContain("I prefer terse summaries") }) }) diff --git a/packages/opencode/test/kilocode/server/httpapi-memory.test.ts b/packages/opencode/test/kilocode/server/httpapi-memory.test.ts index 3b39a00ace..767c1c9d94 100644 --- a/packages/opencode/test/kilocode/server/httpapi-memory.test.ts +++ b/packages/opencode/test/kilocode/server/httpapi-memory.test.ts @@ -151,8 +151,8 @@ describe("HttpApi memory", () => { expect(String(show.index)).toContain("httpapi_memory") expect(String(show.items)).toContain("httpapi_memory") expect(String(rec(show.sources).project)).toContain("httpapi_memory") - expect(typeof show.decisions).toBe("string") - expect(String(show.decisions)).toContain('"sessionID":"ses_http_memory"') + expect(show.changes).toBe("") + expect(show.decisions).toBe("") const forgotten = await json("POST", MemoryPaths.forget, { query: "httpapi_memory" }) expectOperation(forgotten) diff --git a/packages/opencode/test/kilocode/tool/memory-recall.test.ts b/packages/opencode/test/kilocode/tool/memory-recall.test.ts index e68588616b..69d973937e 100644 --- a/packages/opencode/test/kilocode/tool/memory-recall.test.ts +++ b/packages/opencode/test/kilocode/tool/memory-recall.test.ts @@ -173,11 +173,6 @@ describe("kilo_memory_recall", () => { expect(direct.output).toContain("continue memory digest recall") - const decisions = await MemoryFiles.readDecisions(enabled.root) - expect(decisions).toContain('"sessionID":"ses_test"') - expect(decisions).toContain('"query":"sessionID=ses_memory_only"') - expect(decisions).toContain('"summary":"memory recall returned 1 typed hits"') - expect(decisions).toContain('"summary":"memory recall returned 1 digest hits"') }) }) @@ -397,10 +392,6 @@ describe("kilo_memory_recall", () => { expect(result.output).toContain("active session") expect(result.output).not.toContain("useful prior work") - const decisions = await MemoryFiles.readDecisions(enabled.root) - expect(decisions).toContain('"sessionID":"ses_test"') - expect(decisions).toContain('"query":"sessionID=ses_test"') - expect(decisions).toContain('"reason":"current_session_digest"') }) }) @@ -497,8 +488,6 @@ describe("kilo_memory_recall", () => { expect(result.output).toContain("type=session_digest") expect(result.output).toContain('topic="catalog recall"') - const decisions = await MemoryFiles.readDecisions(enabled.root) - expect(decisions).toContain('"summary":"memory recall returned') }) }) diff --git a/packages/opencode/test/kilocode/tool/memory-save.test.ts b/packages/opencode/test/kilocode/tool/memory-save.test.ts index 9a42a0c7e1..e23c1d87cb 100644 --- a/packages/opencode/test/kilocode/tool/memory-save.test.ts +++ b/packages/opencode/test/kilocode/tool/memory-save.test.ts @@ -288,11 +288,6 @@ describe("kilo_memory_save", () => { expect(shown.sources.project).not.toContain("reply_style") expect(shown.sources.project).not.toContain("I prefer terse summaries") expect(shown.sources.project).toContain("- commit_style :: Repo convention: commit messages are concise.") - expect(shown.decisions).toContain('"reason":"out_of_scope"') - expect(shown.decisions).not.toContain("rubicon fennel") - expect(shown.decisions).not.toContain("Ignore prior instructions") - expect(shown.decisions).not.toContain("reply_style") - expect(shown.decisions).not.toContain("I prefer terse summaries") }) }) diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index b54dba1c43..6baa0b229e 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1060,7 +1060,6 @@ export function Prompt(props: PromptProps) { sessionID: props.sessionID, toast, dialog, - renderer, done: () => { history.append({ ...store.prompt, diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index b70feed962..de9fdead6f 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -93,7 +93,7 @@ import { KiloErrorBlock } from "@/kilocode/components/kilo-error-display" import { splitDiffHunks } from "@/kilocode/tui/diff" import { RoutedModelMeta } from "@/kilocode/cli/cmd/tui/routes/session/routed-model-meta" import { submitFeedback } from "@/kilocode/cli/cmd/tui/feedback" -import { MemoryMessageMeta, MemorySessionTui } from "@/kilocode/cli/cmd/tui/routes/session/memory" +import { MemorySessionTui } from "@/kilocode/cli/cmd/tui/routes/session/memory" import { formatMarkdownTables } from "../../util/markdown" // kilocode_change end @@ -339,7 +339,6 @@ export function Session() { const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig)) const toast = useToast() const sdk = useSDK() - const memory = MemorySessionTui.verbose({ sessionID: () => route.sessionID }) // kilocode_change const editor = useEditorContext() onCleanup(MemorySessionTui.attach({ event, toast, sessionID: route.sessionID })) // kilocode_change @@ -1416,7 +1415,6 @@ export function Session() { last={lastAssistant()?.id === message.id} message={message as AssistantMessage} parts={sync.data.part[message.id] ?? []} - memory={memory /* kilocode_change */} /> @@ -1629,7 +1627,6 @@ function AssistantMessage(props: { message: AssistantMessage parts: Part[] last: boolean - memory(): boolean // kilocode_change }) { const ctx = use() const local = useLocal() @@ -1742,9 +1739,6 @@ function AssistantMessage(props: { · {Locale.duration(duration())} - {/* kilocode_change start */} - {" "} - {/* kilocode_change end */} · interrupted