7547d168a4
Adds a small, opt-in user-memory layer so the model has access to durable preferences and conventions across sessions, and the user can dump quick notes without leaving the TUI. ### What ships - **Hierarchy loader** (#490): on every prompt assembly the engine reads `Config::memory_path()` (defaults to `~/.deepseek/memory.md`, override via `memory_path` in config or `DEEPSEEK_MEMORY_PATH`) and injects the file as a `<user_memory>` block alongside the existing `<project_instructions>` block. Goes above the volatile-content boundary so prefix-cache stays warm. Oversize files (>100 KiB) are truncated with a marker. - **`# foo` composer quick-add** (#492): typing a single line that starts with `#` (but not `##` / `#!`) appends a timestamped bullet to the memory file and consumes the input — no turn fires. The composer status line surfaces the path that was written. Multi-`#` prefixes deliberately fall through so users can paste Markdown headings. - **`/memory` slash command** (#491): `/memory` (or `/memory show`) prints the resolved path and contents inline; `/memory path`, `/memory clear`, and `/memory edit` (prints `${VISUAL:-${EDITOR:-vi}} <path>`) cover the rest of the manual-curation surface. - **`remember` tool** (auto-update): model-callable tool that takes a `note` string and appends it as a bullet — the same persistence path as `# foo`. Auto-approved (writes only the user's own memory file). Only registered when memory is enabled, so it doesn't pollute the catalog when the feature is off. - **Opt-in toggle** (#493): default behaviour is off. Enable with `[memory] enabled = true` in `config.toml` or `DEEPSEEK_MEMORY=on` in the environment. ### What's wired - New `crates/tui/src/memory.rs` module (`load`, `as_system_block`, `compose_block`, `append_entry`). - New `crates/tui/src/tools/remember.rs` (`RememberTool` + 3 tests). - New `crates/tui/src/commands/memory.rs` (`memory(app, arg)` handler). - `EngineConfig` gains `memory_enabled: bool` + `memory_path: PathBuf`. - `ToolContext` gains `memory_path: Option<PathBuf>`. - `App` exposes `memory_path` + `use_memory` from `AppOptions` (previously destructured-and-dropped); `main.rs` populates `use_memory` from `config.memory_enabled()`. - `system_prompt_for_mode_with_context_and_skills` accepts an optional `user_memory_block` parameter; the engine computes it via `memory::compose_block(...)` and threads it through. - Composer Enter handler intercepts `# foo` only when `config.memory_enabled()` is true; otherwise falls through to existing turn-submission path. - `MemoryConfig` table (`[memory] enabled`) added to `Config`, surfaced in `config.example.toml`, plumbed through `merge_config`. ### Tests - 8 unit tests in `memory::tests` covering `load` (missing / whitespace / real), `as_system_block` (xml shape, empty input, oversize truncation), and `append_entry` (creation, repeated append, empty-after-strip rejection). - 3 unit tests in `tools::remember::tests` covering disabled-state error, successful append, and missing-`note`-field validation. ### Verification cargo fmt --all -- --check ✓ cargo clippy --workspace --all-targets --all-features --locked -- -D warnings ✓ cargo test --workspace --all-features --locked ✓ (1821 + supporting; was 1809 on main) Closes #490 #491 #492 #493 Refines #489 (EPIC parent — phase-1 MVP delivered; phase-2 items 494–497 stay on the v0.9.0 board) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
2.6 KiB
Rust
63 lines
2.6 KiB
Rust
//! `/memory` slash command — inspect and edit the user memory file.
|
|
//!
|
|
//! When the user-memory feature is opted-in (`[memory] enabled = true` in
|
|
//! config or `DEEPSEEK_MEMORY=on` in the environment), `/memory` shows
|
|
//! the current memory file path and contents inline. Subcommands let the
|
|
//! user clear or open the file:
|
|
//!
|
|
//! - `/memory` — show path + content
|
|
//! - `/memory show` — alias for the no-arg form
|
|
//! - `/memory clear` — replace the file contents with an empty marker
|
|
//! - `/memory path` — show only the resolved path
|
|
//!
|
|
//! Editor integration (`/memory edit`) is intentionally minimal: the
|
|
//! command prints a copy-pasteable shell line to open the file in the
|
|
//! user's `$VISUAL` / `$EDITOR`, since the in-process external editor
|
|
//! plumbing requires terminal teardown that the slash-command handler
|
|
//! doesn't have access to.
|
|
|
|
use std::fs;
|
|
|
|
use super::CommandResult;
|
|
use crate::tui::app::App;
|
|
|
|
pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
|
|
if !app.use_memory {
|
|
return CommandResult::error(
|
|
"user memory is disabled. Enable with `[memory] enabled = true` in `~/.deepseek/config.toml` or `DEEPSEEK_MEMORY=on` in your environment, then restart the TUI.",
|
|
);
|
|
}
|
|
|
|
let path = app.memory_path.clone();
|
|
let sub = arg.unwrap_or("show").trim();
|
|
|
|
match sub {
|
|
"" | "show" => {
|
|
let body = match fs::read_to_string(&path) {
|
|
Ok(text) if text.trim().is_empty() => format!(
|
|
"{}\n(empty — add via `# foo` from the composer or have the model use the `remember` tool)",
|
|
path.display()
|
|
),
|
|
Ok(text) => format!("{}\n\n{}", path.display(), text.trim_end()),
|
|
Err(_) => format!(
|
|
"{}\n(file does not exist yet — add via `# foo` from the composer to create it)",
|
|
path.display()
|
|
),
|
|
};
|
|
CommandResult::message(body)
|
|
}
|
|
"path" => CommandResult::message(path.display().to_string()),
|
|
"clear" => match fs::write(&path, "") {
|
|
Ok(()) => CommandResult::message(format!("memory cleared: {}", path.display())),
|
|
Err(err) => CommandResult::error(format!("failed to clear {}: {err}", path.display())),
|
|
},
|
|
"edit" => CommandResult::message(format!(
|
|
"to edit your memory file, run:\n\n ${{VISUAL:-${{EDITOR:-vi}}}} {}",
|
|
path.display()
|
|
)),
|
|
_ => CommandResult::error(format!(
|
|
"unknown subcommand `{sub}`. usage: /memory [show|path|clear|edit]"
|
|
)),
|
|
}
|
|
}
|