Merge branch 'main' of git.arirex.me:iris/ai-app
This commit is contained in:
commit
c9d74b63f2
9 files changed
+154
-17
No files matched your search
@@ -257,6 +257,30 @@ fn parse_listing(found: &str) -> Result<Vec<Importable>> {
|
||||
(false, false) => InUse::Unknown,
|
||||
};
|
||||
}
|
||||
// One row per session id, because the id is what everything downstream
|
||||
// addresses: `--resume` takes it, deleting globs for it, and the
|
||||
// in-flight registry is keyed on it. So two rows sharing an id are two
|
||||
// rows that no operation can tell apart -- and the phone keys its list
|
||||
// on it too, which turned this into a crash rather than a confusion.
|
||||
//
|
||||
// It is a real state of the machine, not corruption: resuming a session
|
||||
// from a different working directory makes the CLI write a second file
|
||||
// under that directory's project folder with the same id. One of the two
|
||||
// is then usually a stub of a few hundred bytes and the other is the
|
||||
// conversation somebody means.
|
||||
//
|
||||
// So the copy with the most in it wins, and the row's `cwd` comes from
|
||||
// that same copy -- which is the directory `--resume` will find it under.
|
||||
// Ties go to the more recent, and the *stub* is often the more recent, so
|
||||
// the size has to be the first key rather than the tie-break.
|
||||
sessions.sort_by(|a, b| {
|
||||
b.lines
|
||||
.cmp(&a.lines)
|
||||
.then_with(|| b.modified.total_cmp(&a.modified))
|
||||
});
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
sessions.retain(|session| seen.insert(session.id.clone()));
|
||||
|
||||
// Most recent first, and only that. Naming was tried as the first key
|
||||
// and is a worse list: it buries what somebody was just doing under
|
||||
// everything they ever named, and the reason to open this screen is
|
||||
@@ -672,12 +696,15 @@ pub async fn delete(transport: &Transport, id: &str) -> Result<()> {
|
||||
// `context_of` below already resolved an id the cheap way; this is the
|
||||
// same lookup, and the two now agree.
|
||||
ensure!(is_session_id(id), "not a Claude Code session id: {id}");
|
||||
// Every copy, not the first. The same id can name a file under two
|
||||
// project directories -- see the de-duplication in `parse_listing` --
|
||||
// and stopping at the first left the other behind, so the row came back
|
||||
// on the next listing after a delete that had reported success.
|
||||
let script = r#"
|
||||
for f in "$HOME"/.claude/projects/*/"$1".jsonl; do
|
||||
[ -f "$f" ] || continue
|
||||
rm -f "$f" || exit 1
|
||||
printf '%s\n' "$f"
|
||||
exit 0
|
||||
done
|
||||
"#;
|
||||
let launch = Launch::new(
|
||||
@@ -852,6 +879,37 @@ pub async fn replay_after(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// One session id, two files, one row.
|
||||
///
|
||||
/// Resuming a session from a different working directory makes the CLI
|
||||
/// write a second file with the same id under that directory's project
|
||||
/// folder, so this is an ordinary state of a machine rather than a
|
||||
/// corrupt one. Everything downstream addresses a session by id, and
|
||||
/// the phone keys its list on it, so two rows sharing one was a crash.
|
||||
///
|
||||
/// The stub is deliberately the *newer* of the two here, because that
|
||||
/// is how the real case looked: ordering by recency alone picks the
|
||||
/// near-empty copy and describes the session by the wrong cwd.
|
||||
#[test]
|
||||
fn a_session_recorded_under_two_projects_is_offered_once() {
|
||||
let id = "3114dee1-2f95-4de0-9c04-3d6fcc594afe";
|
||||
let said = r#"{"cwd":"/home/bob/repos/survey","message":{"role":"user","content":"the real conversation"}}"#;
|
||||
let stub = r#"{"cwd":"/home/bob/repos/elsewhere","message":{"role":"user","content":"resumed here once"}}"#;
|
||||
let listing = format!(
|
||||
"LIVEKNOWN\n\
|
||||
1000.0\t412\t160638\t\t/home/bob/.claude/projects/-home-bob-repos-survey/{id}.jsonl\t{said}\n\
|
||||
2000.0\t4\t614\t\t/home/bob/.claude/projects/-home-bob-repos-elsewhere/{id}.jsonl\t{stub}\n"
|
||||
);
|
||||
|
||||
let rows = parse_listing(&listing).expect("parse");
|
||||
|
||||
assert_eq!(rows.len(), 1, "one id is one row: {rows:#?}");
|
||||
assert_eq!(rows[0].lines, 412, "the conversation, not the stub");
|
||||
// The cwd has to come from the copy that was kept, because that is
|
||||
// the directory `--resume` will find those 412 lines under.
|
||||
assert_eq!(rows[0].cwd, "/home/bob/repos/survey");
|
||||
}
|
||||
|
||||
/// The guard on the only thing this module ever puts in a glob.
|
||||
///
|
||||
/// Worth a test of its own because what it protects is a `rm`: `delete`
|
||||
|
||||
Reference in new issue
Block a user