mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
Merge pull request #12422 from Kilo-Org/johnnyeric/fix-memory-ui-feedback
fix(memory): refine CLI and extension experience
This commit is contained in:
@@ -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.
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a89d1a9a31f7de1780d772b7caf71d96f0b4b895df954c77dcd508a7febe7c9
|
||||
size 1950
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:431a0c12cb844b5d4e535cc4c7bf5546fd9e66a178b8023b00978d07c291a531
|
||||
size 3919
|
||||
@@ -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 <text>", description: "Save a project memory note" },
|
||||
{ usage: "correct <text>", description: "Save a correction to project memory" },
|
||||
{ usage: "forget <query>", 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<MemoryOperation, "remember" | "correct" | "forget" | "purge" | "auto" | "verbose">
|
||||
operation: Exclude<MemoryOperation, "remember" | "correct" | "forget" | "purge" | "auto">
|
||||
}
|
||||
|
||||
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.")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<string, number>()
|
||||
const seed: Record<MemorySchema.Source, string> = {
|
||||
"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: "",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -3962,7 +3962,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
detail,
|
||||
})
|
||||
}
|
||||
void this.memory.fetch(sessionID, false)
|
||||
void this.memory.fetch(sessionID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>): { 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<string, unknown>): Promise<boolean> {
|
||||
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<void> {
|
||||
return this.serial(() => this.load(sessionID, includeSources))
|
||||
fetch(sessionID?: string): Promise<void> {
|
||||
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<void> {
|
||||
private async load(sessionID?: string): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return this.serial(() => this.doShow(sessionID, mode))
|
||||
}
|
||||
|
||||
show(sessionID?: string): Promise<void> {
|
||||
return this.serial(() => this.doShow(sessionID))
|
||||
}
|
||||
|
||||
private async doShow(sessionID?: string): Promise<void> {
|
||||
private async doShow(sessionID: string | undefined, mode: "status" | "show"): Promise<void> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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" }))
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)", () => {
|
||||
|
||||
@@ -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<typeof addMemoryActivity>,
|
||||
)
|
||||
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"] })
|
||||
})
|
||||
})
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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<AssistantMessageProps> = (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<AssistantMessageProps> = (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 (
|
||||
<div style={{ "text-align": "left", "white-space": "normal", "max-width": "280px" }}>
|
||||
<Show
|
||||
when={values.length > 0}
|
||||
fallback={
|
||||
<div>{`${language.t("chat.memory.badge.recalled")} · ${language.t("chat.memory.badge.items", { count: count(item) })}`}</div>
|
||||
}
|
||||
>
|
||||
<For each={values}>{(value) => <div>{value}</div>}</For>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<For each={parts()}>
|
||||
@@ -331,17 +297,6 @@ export const AssistantMessage: Component<AssistantMessageProps> = (props) => {
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<Show when={mem.enabled() && recall()}>
|
||||
{(item) => (
|
||||
<Tooltip value={tip(item())} placement="top">
|
||||
<div data-component="assistant-memory-badge">
|
||||
{language.t("chat.memory.badge.recalled")} ·{" "}
|
||||
{language.t("chat.memory.badge.items", { count: count(item()) })}
|
||||
<Show when={verbose() && items(item()).length > 0}> · {items(item())[0]}</Show>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<PromptInputProps> = (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<PromptInputProps> = (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 } : {}),
|
||||
|
||||
@@ -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<TaskHeaderProps> = (props) => {
|
||||
const session = useSession()
|
||||
const memory = useMemory()
|
||||
const language = useLanguage()
|
||||
const search = useTranscriptSearch()
|
||||
|
||||
@@ -91,76 +86,6 @@ export const TaskHeader: Component<TaskHeaderProps> = (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 = () => (
|
||||
<div data-slot="task-header-memory-activity">
|
||||
<Show
|
||||
when={activityLines().length > 0}
|
||||
fallback={<div data-slot="task-header-memory-activity-summary">{language.t("chat.memory.activity.idle")}</div>}
|
||||
>
|
||||
<div data-slot="task-header-memory-activity-summary">
|
||||
<For each={activityLines()}>{(line) => <div>{line}</div>}</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
const activityTooltip = () => (
|
||||
<>
|
||||
<div data-slot="task-header-context-tooltip-title">{language.t("settings.context.title")}</div>
|
||||
<div data-slot="task-header-context-tooltip-status">{memoryStatus()}</div>
|
||||
{activitySummaryView()}
|
||||
<Show when={memoryVerbose() && activityItems().length > 0}>
|
||||
<div data-slot="task-header-memory-activity-list">
|
||||
<For each={activityItems()}>{(item) => <div>{activityLabel(item)}</div>}</For>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
|
||||
const vscode = useVSCode()
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
|
||||
@@ -311,121 +236,18 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</Show>
|
||||
<Tooltip value={activityTooltip()} placement="bottom" contentClass="task-header-memory-tooltip">
|
||||
<DeferredPopover
|
||||
placement="bottom-end"
|
||||
portal={false}
|
||||
class="task-header-context-popover"
|
||||
triggerAs="button"
|
||||
triggerProps={{
|
||||
type: "button",
|
||||
class: "task-header-context-trigger",
|
||||
get ["aria-label"]() {
|
||||
return language.t("settings.context.title")
|
||||
},
|
||||
}}
|
||||
trigger={
|
||||
<>
|
||||
<Icon name="server" size="small" />
|
||||
<Show when={memoryActive()}>
|
||||
<span data-slot="task-header-memory-dot" />
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div data-slot="task-header-context-menu">
|
||||
<div data-slot="task-header-context-title">{language.t("settings.context.title")}</div>
|
||||
<Show when={!props.readonly}>
|
||||
<section data-slot="task-header-context-section">
|
||||
<div data-slot="task-header-context-section-title">
|
||||
<Icon name="compress" size="small" />
|
||||
<span>{language.t("settings.context.compaction.title")}</span>
|
||||
</div>
|
||||
<div data-slot="task-header-context-actions">
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={!canCompact()}
|
||||
onClick={() => session.compact()}
|
||||
>
|
||||
{language.t("command.session.compact")}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
<section data-slot="task-header-context-section">
|
||||
<div data-slot="task-header-context-section-title">
|
||||
<Icon name="memory" size="small" />
|
||||
<span>{language.t("settings.context.memory.title")}</span>
|
||||
</div>
|
||||
<div data-slot="task-header-memory-status">{memoryStatus()}</div>
|
||||
{activitySummaryView()}
|
||||
<div data-slot="task-header-context-actions">
|
||||
<Show
|
||||
when={memory.enabled()}
|
||||
fallback={
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.enable()}
|
||||
>
|
||||
{language.t("chat.memory.enable")}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.loading() || memory.pending()}
|
||||
onClick={() => memory.showMemory()}
|
||||
>
|
||||
{language.t("chat.memory.inspect")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.remember()}
|
||||
>
|
||||
{language.t("chat.memory.remember")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.forget()}
|
||||
>
|
||||
{language.t("chat.memory.forget")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.rebuild()}
|
||||
>
|
||||
{language.t("chat.memory.rebuild")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.disable()}
|
||||
>
|
||||
{language.t("chat.memory.disable")}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={memory.enabled()}>
|
||||
<div data-slot="task-header-memory-verbose">
|
||||
<span>{language.t("chat.memory.verbose")}</span>
|
||||
<Switch
|
||||
checked={memoryVerbose()}
|
||||
disabled={memory.pending()}
|
||||
hideLabel
|
||||
onChange={(next) => memory.verbose(next ? "on" : "off")}
|
||||
>
|
||||
{language.t("chat.memory.verbose")}
|
||||
</Switch>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
</div>
|
||||
</DeferredPopover>
|
||||
</Tooltip>
|
||||
<Show when={!props.readonly}>
|
||||
<Tooltip value={language.t("command.session.compact")} placement="bottom">
|
||||
<IconButton
|
||||
icon="compress"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
disabled={!canCompact()}
|
||||
onClick={() => session.compact()}
|
||||
aria-label={language.t("command.session.compact")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Show when={hasMessages()}>
|
||||
<Tooltip value={language.t("chat.search.toggle")} placement="bottom">
|
||||
<IconButton
|
||||
|
||||
@@ -56,10 +56,9 @@ const ContextTab: Component = () => {
|
||||
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 = () => {
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={language.t("settings.context.memory.index.title")}
|
||||
title={language.t("settings.context.memory.storage.title")}
|
||||
description={
|
||||
memory.enabled()
|
||||
? language.t("settings.context.memory.index.path", { path: memory.status()!.root })
|
||||
: language.t("settings.context.memory.index.enable")
|
||||
? language.t("settings.context.memory.storage.path", { path: memory.status()!.root })
|
||||
: language.t("settings.context.memory.storage.enable")
|
||||
}
|
||||
last
|
||||
>
|
||||
<div style={{ display: "flex", gap: "6px", "align-items": "center" }}>
|
||||
<Button variant="secondary" size="small" icon="eye" onClick={() => memory.showMemory()}>
|
||||
{language.t("settings.context.memory.inspect")}
|
||||
</Button>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="ghost"
|
||||
icon="reset"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.rebuild()}
|
||||
aria-label={language.t("settings.context.memory.rebuild")}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
icon="eye"
|
||||
disabled={memory.loading() || memory.pending() || !memory.enabled() || memory.totalTokens() === 0}
|
||||
onClick={() => memory.inspect()}
|
||||
>
|
||||
{language.t("settings.context.memory.inspect")}
|
||||
</Button>
|
||||
</SettingsRow>
|
||||
<Show when={memory.error()}>
|
||||
{(err) => (
|
||||
|
||||
@@ -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<MemoryStatusResponse | undefined>
|
||||
show: Accessor<MemoryShowResponse | undefined>
|
||||
loading: Accessor<boolean>
|
||||
pending: Accessor<boolean>
|
||||
error: Accessor<string | undefined>
|
||||
enabled: Accessor<boolean>
|
||||
sessionTokens: Accessor<number>
|
||||
totalTokens: Accessor<number>
|
||||
activity: Accessor<MemoryActivity[]>
|
||||
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<MemoryContextValue>()
|
||||
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<MemoryStatusResponse | undefined>()
|
||||
const [show, setShow] = createSignal<MemoryShowResponse | undefined>()
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
const [pending, setPending] = createSignal<string | undefined>()
|
||||
const [error, setError] = createSignal<string | undefined>()
|
||||
const [saved, setSaved] = createSignal<MemoryActivity[]>([])
|
||||
const [markers, setMarkers] = createSignal<Record<string, Marker>>({})
|
||||
|
||||
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<ExtensionMessage, { type: "messagesLoaded" }>) => {
|
||||
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<ExtensionMessage, { type: "messageCreated" }>) => {
|
||||
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<string, Marker> = {}
|
||||
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<ExtensionMessage, { type: "memoryEvent" }>) => {
|
||||
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 <MemoryContext.Provider value={value}>{props.children}</MemoryContext.Provider>
|
||||
|
||||
@@ -27,6 +27,7 @@ interface VSCodeContext {
|
||||
export interface SlashCommandEntry extends SlashCommandInfo {
|
||||
action?: () => void
|
||||
enabled?: Accessor<boolean>
|
||||
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?.()
|
||||
}
|
||||
|
||||
|
||||
+7
-25
@@ -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 مخصص",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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é",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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 を使用",
|
||||
|
||||
+7
-25
@@ -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 사용",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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 แบบกำหนดเอง",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
+7
-25
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding>
|
||||
<SessionContext.Provider value={session as any}>
|
||||
<MemoryContext.Provider value={mockMemory}>
|
||||
<div style={{ width }}>
|
||||
<TaskHeader />
|
||||
</div>
|
||||
</MemoryContext.Provider>
|
||||
</SessionContext.Provider>
|
||||
</StoryProviders>
|
||||
)
|
||||
}
|
||||
|
||||
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"],
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(() => <DialogMemory workspace={input?.workspace} directory={input?.directory} />)
|
||||
}
|
||||
|
||||
export function showMemoryHelpDialog(dialog: DialogContext, reason?: string) {
|
||||
export function showMemoryHelpDialog(
|
||||
dialog: DialogContext,
|
||||
input?: { workspace?: string; directory?: string; reason?: string },
|
||||
) {
|
||||
dialog.setSize("large")
|
||||
dialog.replace(() => <DialogMemoryHelp reason={reason} />)
|
||||
dialog.replace(() => (
|
||||
<DialogMemoryHelp workspace={input?.workspace} directory={input?.directory} reason={input?.reason} />
|
||||
))
|
||||
}
|
||||
|
||||
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 (
|
||||
<box>
|
||||
<text fg={theme.text}>Activity</text>
|
||||
<text fg={theme.textMuted}>
|
||||
startup context {props.state.autoInject ? "on" : "off"}
|
||||
{props.state.stats.lastInjectedTokens > 0
|
||||
? ` · last injected ${fmt(props.state.stats.lastInjectedTokens)} tokens`
|
||||
: ""}
|
||||
</text>
|
||||
<Show when={props.state.stats.lastRecallCount > 0}>
|
||||
<text fg={theme.textMuted}>last recall {fmt(props.state.stats.lastRecallCount)} items</text>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
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<string>[] = MEMORY_COMMAND_CATALOG.map((item) => ({
|
||||
title: item.description,
|
||||
footer: `/memory ${item.usage}`,
|
||||
category: "Memory",
|
||||
value: item.usage,
|
||||
}))
|
||||
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
Memory
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={props.reason}>{(reason) => <text fg={theme.error}>{reason()}</text>}</Show>
|
||||
<box gap={0}>
|
||||
<For each={MEMORY_COMMAND_CATALOG}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={2}>
|
||||
<text fg={theme.text} flexShrink={0}>
|
||||
/memory {item.usage}
|
||||
</text>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
{item.description}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</box>
|
||||
<DialogSelect
|
||||
title="Memory"
|
||||
options={options}
|
||||
flat
|
||||
footer={<Show when={props.reason}>{(reason) => <text fg={theme.error}>{reason()}</text>}</Show>}
|
||||
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.
|
||||
</text>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={theme.text}>Verbose</text>
|
||||
<text fg={theme.textMuted}>{item().state.verbose ? "on" : "off"}</text>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
Verbose shows recall and save details; toggle with /memory verbose on|off.
|
||||
</text>
|
||||
</box>
|
||||
<MemoryActivityInfo state={item().state} />
|
||||
<MemorySourcesInfo sources={item().sources} />
|
||||
<MemoryItemsInfo items={item().items} />
|
||||
<box>
|
||||
@@ -307,7 +286,6 @@ export function DialogMemory(props: { workspace?: string; directory?: string })
|
||||
<box>
|
||||
<MemoryHeaderInfo root={item().root} state={item().state} />
|
||||
</box>
|
||||
<MemoryActivityInfo state={item().state} />
|
||||
<MemorySourcesInfo sources={item().sources} />
|
||||
<MemoryItemsInfo items={item().items} />
|
||||
</box>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<string>()
|
||||
const [saved, setSaved] = createSignal(false)
|
||||
const timer = { id: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||
const pulse = { id: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||
const prior = { ids: new Set<string>(), 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<Event, { type: "memory.status" | "memory.updated" }>) => {
|
||||
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 }) {
|
||||
</text>
|
||||
<text fg={props.api.theme.current.text}>
|
||||
{row().label}
|
||||
{row().caption && <span style={{ fg: props.api.theme.current.textMuted }}> · {row().caption}</span>}
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -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<void>
|
||||
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` })
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,6 @@ import type { MemoryStatusResponse } from "@kilocode/sdk/v2"
|
||||
type State = MemoryStatusResponse["state"]
|
||||
|
||||
export namespace MemoryTuiState {
|
||||
export function verbose(input: Pick<State, "verbose"> | undefined) {
|
||||
return input?.verbose ?? false
|
||||
}
|
||||
|
||||
export function enabled(input: Pick<State, "enabled"> | undefined) {
|
||||
return input?.enabled ?? false
|
||||
}
|
||||
|
||||
@@ -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<typeof MemoryTuiEvents.attach>[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 (
|
||||
<Show when={item()}>
|
||||
{(meta) => {
|
||||
const snippets = createMemo(() =>
|
||||
meta().type === "recall"
|
||||
? MemoryTuiMeta.items(meta())
|
||||
.slice(0, 2)
|
||||
.map((text) => text.trim().slice(0, 80))
|
||||
.filter(Boolean)
|
||||
: [],
|
||||
)
|
||||
return (
|
||||
<span style={{ fg: props.color }}>
|
||||
{" "}
|
||||
· memory · {meta().type === "startup" ? "Startup Context" : `recalled ${meta().count}`}
|
||||
<Show when={props.verbose() && snippets().length > 0}>
|
||||
<For each={snippets()}>{(text) => <> · {text}</>}</For>
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<Event, { type: "memo
|
||||
}
|
||||
}
|
||||
|
||||
function global(payload: Event): GlobalEvent {
|
||||
return { directory, project: "proj_test", payload }
|
||||
}
|
||||
|
||||
async function wait(fn: () => 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 <text>{verbose() ? "verbose" : "quiet"}</text>
|
||||
}
|
||||
|
||||
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(() => (
|
||||
<TestTuiContexts paths={{ state: tmp.path }}>
|
||||
<ArgsProvider>
|
||||
<ExitProvider exit={() => {}}>
|
||||
<KVProvider>
|
||||
<ToastProvider>
|
||||
<SDKProvider url="http://test" directory={directory} fetch={fetch.fetch} events={events.source}>
|
||||
<ProjectProvider>
|
||||
<SyncProvider>
|
||||
<Probe sessionID={id} />
|
||||
</SyncProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</ToastProvider>
|
||||
</KVProvider>
|
||||
</ExitProvider>
|
||||
</ArgsProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
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<Part[]>([])
|
||||
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(
|
||||
() => (
|
||||
<text>
|
||||
<MemoryMessageMeta parts={parts()} color={RGBA.fromHex("#ffffff")} verbose={verbose} />
|
||||
</text>
|
||||
),
|
||||
{ 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<Part[]>([])
|
||||
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)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1060,7 +1060,6 @@ export function Prompt(props: PromptProps) {
|
||||
sessionID: props.sessionID,
|
||||
toast,
|
||||
dialog,
|
||||
renderer,
|
||||
done: () => {
|
||||
history.append({
|
||||
...store.prompt,
|
||||
|
||||
@@ -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 */}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
@@ -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: {
|
||||
<Show when={duration()}>
|
||||
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
|
||||
</Show>
|
||||
{/* kilocode_change start */}
|
||||
<MemoryMessageMeta parts={props.parts} color={theme.textMuted} verbose={props.memory} />{" "}
|
||||
{/* kilocode_change end */}
|
||||
<Show when={props.message.error?.name === "MessageAbortedError"}>
|
||||
<span style={{ fg: theme.textMuted }}> · interrupted</span>
|
||||
</Show>
|
||||
|
||||
Reference in New Issue
Block a user