forked from 1b-agent/fieldcraft-upstream
ui: Help › Crash Reports lists the saved reports to report, copy or delete (#300)
diagnostics.crash_reports.delete deletes one report or all. The window lists each report (when, what, how many times) with Report…, Copy Path and Delete, and Delete All; ui.prompt answers it over the control channel. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
4711f9474a
commit
441f03d093
11 files changed
+231
-7
No files matched your search
@@ -30,6 +30,16 @@ pub const CRASH_REPORT: CommandSpec = CommandSpec {
|
||||
run: crash_report,
|
||||
};
|
||||
|
||||
pub const CRASH_REPORTS_DELETE: CommandSpec = CommandSpec {
|
||||
id: "diagnostics.crash_reports.delete",
|
||||
label: "Delete Crash Reports",
|
||||
params: r#"{"id": "20261014-101500-…"} or {"all": true}; "dir"?: "…" — deletes one crash report, or all of them, from this computer: how many were deleted"#,
|
||||
form: Some(super::forms::CRASH_REPORTS_DELETE),
|
||||
kind: CommandKind::Session,
|
||||
requires: &[],
|
||||
run: crash_reports_delete,
|
||||
};
|
||||
|
||||
pub const BUG_REPORT: CommandSpec = CommandSpec {
|
||||
id: "diagnostics.bug_report",
|
||||
label: "Report a Bug",
|
||||
@@ -51,8 +61,12 @@ pub const CRASH_TEST: CommandSpec = CommandSpec {
|
||||
};
|
||||
|
||||
fn crash_dir(s: &Session, p: &Value) -> Result<PathBuf> {
|
||||
crash_dir_for(s, p, Access::Read)
|
||||
}
|
||||
|
||||
fn crash_dir_for(s: &Session, p: &Value, access: Access) -> Result<PathBuf> {
|
||||
if p.get("dir").is_some() {
|
||||
return Ok(PathBuf::from(file_param(s, p, "dir", Access::Read)?));
|
||||
return Ok(PathBuf::from(file_param(s, p, "dir", access)?));
|
||||
}
|
||||
crash::default_dir().ok_or_else(|| EngineError::Params("no crash folder: set FIELDCRAFT_CRASH_DIR or give `dir`".into()))
|
||||
}
|
||||
@@ -71,6 +85,21 @@ fn crash_report(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
crash::read(&dir, &id).map_err(EngineError::Params)
|
||||
}
|
||||
|
||||
fn crash_reports_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let id = opt::<String>(p, "id")?;
|
||||
let all = opt::<bool>(p, "all")?.unwrap_or(false);
|
||||
let dir = crash_dir_for(s, p, Access::Write)?;
|
||||
let deleted = match (id.as_deref(), all) {
|
||||
(Some(id), false) => match crash::clear(&dir, Some(id)) {
|
||||
0 => return Err(EngineError::Params(format!("no crash report `{}`", crash::limited(id, 64)))),
|
||||
n => n,
|
||||
},
|
||||
(None, true) => crash::clear(&dir, None),
|
||||
_ => return Err(EngineError::Params("give one of `id` (a crash report) or `all: true`".into())),
|
||||
};
|
||||
Ok(json!({"deleted": deleted}))
|
||||
}
|
||||
|
||||
/// The draft input for `p` from the session: its commands and model (when shared), or a crash
|
||||
/// report's replay and model.
|
||||
pub fn bug_report_input(s: &Session, p: &Value) -> Result<bug_report::Input> {
|
||||
@@ -131,3 +160,39 @@ fn crash_test(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
_ => Err(EngineError::invalid("mode", "must be panic, recovered or abort")),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// `diagnostics.crash_reports.delete` deletes one report by id, then the rest; an unknown or
|
||||
/// hostile id and a missing choice are errors that delete nothing.
|
||||
#[test]
|
||||
fn crash_reports_are_deleted_one_or_all() {
|
||||
let dir = std::env::temp_dir().join(format!("fieldcraft-crash-delete-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let ids = ["20261014-101500-0000000000000001", "20261014-101600-0000000000000002", "20261014-101700-0000000000000003"];
|
||||
for id in ids {
|
||||
std::fs::create_dir_all(dir.join(id)).unwrap();
|
||||
std::fs::write(dir.join(id).join("report.json"), r#"{"kind": "panic", "message": "x"}"#).unwrap();
|
||||
}
|
||||
let mut s = Session::default();
|
||||
let d = dir.to_string_lossy().to_string();
|
||||
let listed = |s: &mut Session| s.execute("diagnostics.crash_reports", &json!({"dir": d})).unwrap()["reports"].as_array().map_or(0, Vec::len);
|
||||
assert_eq!(listed(&mut s), 3);
|
||||
for bad in [
|
||||
json!({"dir": d}),
|
||||
json!({"dir": d, "id": "../x"}),
|
||||
json!({"dir": d, "id": "20261014-101500-00000000000000ff"}),
|
||||
json!({"dir": d, "id": ids[0], "all": true}),
|
||||
] {
|
||||
assert!(s.execute("diagnostics.crash_reports.delete", &bad).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!(listed(&mut s), 3);
|
||||
assert_eq!(s.execute("diagnostics.crash_reports.delete", &json!({"dir": d, "id": ids[1]})).unwrap()["deleted"], 1);
|
||||
assert_eq!(listed(&mut s), 2);
|
||||
assert_eq!(s.execute("diagnostics.crash_reports.delete", &json!({"dir": d, "all": true})).unwrap()["deleted"], 2);
|
||||
assert_eq!(listed(&mut s), 0);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
@@ -1518,6 +1518,11 @@ pub const CRASH_REPORT: &[Field] = &[
|
||||
text("id", "Report").help("The crash report to show.").req(),
|
||||
path("dir", "Folder", false).help("The crash folder; the default one when empty.").hint("the crash folder"),
|
||||
];
|
||||
pub const CRASH_REPORTS_DELETE: &[Field] = &[
|
||||
text("id", "Report").help("The crash report to delete.").hint("or All"),
|
||||
flag("all", "All").help("Delete every crash report instead of one."),
|
||||
path("dir", "Folder", false).help("The crash folder; the default one when empty.").hint("the crash folder"),
|
||||
];
|
||||
pub const BUG_REPORT: &[Field] = &[
|
||||
text("title", "Title").help("The issue's title; one is made up when empty."),
|
||||
text("observed", "What happened").help("What went wrong; the last error when empty."),
|
||||
|
||||
@@ -165,6 +165,7 @@ pub static ALL: &[CommandSpec] = &[
|
||||
results::CLEAR,
|
||||
diagnostics::CRASH_REPORTS,
|
||||
diagnostics::CRASH_REPORT,
|
||||
diagnostics::CRASH_REPORTS_DELETE,
|
||||
diagnostics::BUG_REPORT,
|
||||
diagnostics::CRASH_TEST,
|
||||
update::CHECK,
|
||||
|
||||
Reference in new issue
Block a user