返回 DeepSeek-TUI-2026
memory.rs
根目录 / crates / tui / src / commands / memory.rs
1 //! `/memory` slash command — inspect and edit the user memory file.
2 //!
3 //! When the user-memory feature is opted-in (`[memory] enabled = true` in
4 //! config or `DEEPSEEK_MEMORY=on` in the environment), `/memory` shows
5 //! the current memory file path and contents inline. Subcommands let the
6 //! user clear or open the file:
7 //!
8 //! - `/memory` — show path + content
9 //! - `/memory show` — alias for the no-arg form
10 //! - `/memory clear` — replace the file contents with an empty marker
11 //! - `/memory path` — show only the resolved path
12 //! - `/memory help` — show command-specific help and the resolved path
13 //!
14 //! Editor integration (`/memory edit`) is intentionally minimal: the
15 //! command prints a copy-pasteable shell line to open the file in the
16 //! user's `$VISUAL` / `$EDITOR`, since the in-process external editor
17 //! plumbing requires terminal teardown that the slash-command handler
18 //! doesn't have access to.
19
20 use std::fs;
21 use std::path::Path;
22
23 use super::CommandResult;
24 use crate::tui::app::App;
25
26 const MEMORY_USAGE: &str = "/memory [show|path|clear|edit|help]";
27
28 fn memory_help(path: &Path) -> String {
29 format!(
30 "Inspect or manage your persistent user-memory file.\n\n\
31 Usage: {MEMORY_USAGE}\n\n\
32 Current path: {}\n\n\
33 Subcommands:\n\
34 /memory Show the resolved path and current contents\n\
35 /memory show Alias for the no-arg form\n\
36 /memory path Print just the resolved path\n\
37 /memory clear Replace the file contents with an empty marker\n\
38 /memory edit Print the editor command for this file\n\
39 /memory help Show this help\n\n\
40 Quick capture: type `# foo` in the composer to append a timestamped\n\
41 bullet without firing a turn.",
42 path.display()
43 )
44 }
45
46 pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
47 if !app.use_memory {
48 return CommandResult::error(
49 "user memory is disabled. Enable with `[memory] enabled = true` in `~/.deepseek/config.toml` or `DEEPSEEK_MEMORY=on` in your environment, then restart the TUI.",
50 );
51 }
52
53 let path = app.memory_path.clone();
54 let sub = arg.unwrap_or("show").trim();
55
56 match sub {
57 "" | "show" => {
58 let body = match fs::read_to_string(&path) {
59 Ok(text) if text.trim().is_empty() => format!(
60 "{}\n(empty — add via `# foo` from the composer or have the model use the `remember` tool)",
61 path.display()
62 ),
63 Ok(text) => format!("{}\n\n{}", path.display(), text.trim_end()),
64 Err(_) => format!(
65 "{}\n(file does not exist yet — add via `# foo` from the composer to create it)",
66 path.display()
67 ),
68 };
69 CommandResult::message(body)
70 }
71 "path" => CommandResult::message(path.display().to_string()),
72 "clear" => match fs::write(&path, "") {
73 Ok(()) => CommandResult::message(format!("memory cleared: {}", path.display())),
74 Err(err) => CommandResult::error(format!("failed to clear {}: {err}", path.display())),
75 },
76 "edit" => CommandResult::message(format!(
77 "to edit your memory file, run:\n\n ${{VISUAL:-${{EDITOR:-vi}}}} {}",
78 path.display()
79 )),
80 "help" => CommandResult::message(memory_help(&path)),
81 _ => CommandResult::error(format!(
82 "unknown subcommand `{sub}`. Try `/memory help`.\n\n{}",
83 memory_help(&path)
84 )),
85 }
86 }
87
88 #[cfg(test)]
89 mod tests {
90 use super::*;
91 use crate::config::Config;
92 use crate::tui::app::{App, TuiOptions};
93 use tempfile::TempDir;
94
95 fn create_test_app_with_memory(tmpdir: &TempDir, use_memory: bool) -> App {
96 let options = TuiOptions {
97 model: "deepseek-v4-pro".to_string(),
98 workspace: tmpdir.path().to_path_buf(),
99 config_path: None,
100 config_profile: None,
101 allow_shell: false,
102 use_alt_screen: true,
103 use_mouse_capture: false,
104 use_bracketed_paste: true,
105 max_subagents: 1,
106 skills_dir: tmpdir.path().join("skills"),
107 memory_path: tmpdir.path().join("memory.md"),
108 notes_path: tmpdir.path().join("notes.txt"),
109 mcp_config_path: tmpdir.path().join("mcp.json"),
110 use_memory,
111 start_in_agent_mode: false,
112 skip_onboarding: true,
113 yolo: false,
114 resume_session_id: None,
115 initial_input: None,
116 };
117 App::new(options, &Config::default())
118 }
119
120 #[test]
121 fn memory_help_lists_subcommands_and_resolved_path() {
122 let tmpdir = TempDir::new().expect("tempdir");
123 let mut app = create_test_app_with_memory(&tmpdir, true);
124 let result = memory(&mut app, Some("help"));
125 let msg = result.message.expect("help should return text");
126 assert!(msg.contains("Usage: /memory [show|path|clear|edit|help]"));
127 assert!(msg.contains("/memory edit"));
128 assert!(msg.contains(app.memory_path.to_string_lossy().as_ref()));
129 }
130
131 #[test]
132 fn memory_unknown_subcommand_points_to_help() {
133 let tmpdir = TempDir::new().expect("tempdir");
134 let mut app = create_test_app_with_memory(&tmpdir, true);
135 let result = memory(&mut app, Some("wat"));
136 let msg = result
137 .message
138 .expect("unknown subcommand should return text");
139 assert!(msg.contains("Try `/memory help`"));
140 assert!(msg.contains("/memory clear"));
141 }
142
143 #[test]
144 fn memory_disabled_returns_enablement_hint() {
145 let tmpdir = TempDir::new().expect("tempdir");
146 let mut app = create_test_app_with_memory(&tmpdir, false);
147 let result = memory(&mut app, None);
148 let msg = result.message.expect("disabled memory should return text");
149 assert!(msg.contains("user memory is disabled"));
150 assert!(msg.contains("DEEPSEEK_MEMORY=on"));
151 }
152 }
153
153 lines RUST