feat: add session deletion with per-provider cleanup and path safety

Add delete_session Tauri command dispatching to provider-specific deletion
logic for all 5 providers (Claude, Codex, Gemini, OpenCode, OpenClaw).
Includes path traversal protection via canonicalize + starts_with validation,
session ID verification against file contents, frontend confirmation dialog
with optimistic cache updates, i18n keys (zh/en/ja), and component tests.
This commit is contained in:
Jason
2026-03-06 23:09:38 +08:00
parent e18db31752
commit 8c3f18a9bd
17 changed files with 1043 additions and 15 deletions
@@ -81,6 +81,27 @@ pub fn load_messages(path: &Path) -> Result<Vec<SessionMessage>, String> {
Ok(messages)
}
pub fn delete_session(_root: &Path, path: &Path, session_id: &str) -> Result<bool, String> {
let meta = parse_session(path)
.ok_or_else(|| format!("Failed to parse Codex session metadata: {}", path.display()))?;
if meta.session_id != session_id {
return Err(format!(
"Codex session ID mismatch: expected {session_id}, found {}",
meta.session_id
));
}
std::fs::remove_file(path).map_err(|e| {
format!(
"Failed to delete Codex session file {}: {e}",
path.display()
)
})?;
Ok(true)
}
fn parse_session(path: &Path) -> Option<SessionMeta> {
let (head, tail) = read_head_tail_lines(path, 10, 30).ok()?;
@@ -192,3 +213,30 @@ fn collect_jsonl_files(root: &Path, files: &mut Vec<PathBuf>) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn delete_session_removes_jsonl_file() {
let temp = tempdir().expect("tempdir");
let path = temp
.path()
.join("rollout-2026-03-06T21-50-12-019cc369-bd7c-7891-b371-7b20b4fe0b18.jsonl");
std::fs::write(
&path,
concat!(
"{\"timestamp\":\"2026-03-06T21:50:12Z\",\"type\":\"session_meta\",\"payload\":{\"id\":\"019cc369-bd7c-7891-b371-7b20b4fe0b18\",\"cwd\":\"/tmp/project\"}}\n",
"{\"timestamp\":\"2026-03-06T21:50:13Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"user\",\"content\":\"hello\"}}\n"
),
)
.expect("write session");
delete_session(temp.path(), &path, "019cc369-bd7c-7891-b371-7b20b4fe0b18")
.expect("delete session");
assert!(!path.exists());
}
}