Repository navigation
feat(session): locator-based thread view, spend, background append and legacy rewrite #463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
3799067
feat(session): display read, thread listing and background append on …
senamakel 5e04645
feat(session): project and total a thread through a locator
senamakel 01c02f4
feat(legacy): rewrite_legacy_transcripts for pre-import fixups
senamakel 84f0a06
test(session): locator parity across files and a driver store
senamakel 462395c
docs(session): locator-based transcript API
senamakel cf63cfa
Merge branch 'main' into storage-7809-tinyagents-2
senamakel bd962f4
chore(session): add transcripts adoption driver
senamakel 2d247ee
chore(session): remove unused transcript driver module
senamakel ab2ab2c
chore(session): add transcript display driver and adoption tests
senamakel 140c0e7
feat(session): adopt legacy roots into a session on driver-backed tra…
senamakel 0145fc5
Merge remote-tracking branch 'origin/storage-7809-tinyagents-2' into …
senamakel dd2f9ee
refactor(session): extract transcript writer into its own module
senamakel a5ee799
refactor(session): extract legacy rewrite helpers
senamakel 9ece838
refactor(session): move legacy transcript rewriting into its own module
senamakel e0a7d05
test(session): cover legacy transcript rewrite edge cases
senamakel 01f4c6e
test(session): import write_transcript in legacy rewrite tests
senamakel 0b62d22
fix(session): release turn lock when background task is cancelled
senamakel a6c93cf
refactor(session): extract background task handling into its own module
senamakel 1c58d40
test(session): cover background transcript task lifecycle
senamakel 2a06189
test(transcript): qualify TranscriptRead path in background test locator
senamakel c03c8a6
fix(session): preserve agent file entries when rewriting transcripts
senamakel 9ec373f
test(transcript): cover sub-agent listing after root adoption
senamakel 089ee7f
chore: I don't see a diff in your message — the "Diff:" section is em…
senamakel 7ef8129
test(session): cover compacted subagent spend and generation ordering
senamakel f72ad20
feat(session): add transcripts display driver
senamakel 70b032f
refactor(transcripts-display): simplify wanted filter construction
senamakel 0a49808
refactor(session): extract transcript display driver
senamakel 1bf80d5
test(session): cover display read repairing a stale index
senamakel 4b08d77
test(session): exercise display read through a fresh handle
senamakel 90139f9
test(session): cover transcript root selection and prefix filtering
senamakel 7af4dcf
feat(session): fold legacy roots found only in the entry log
senamakel bf867c8
test(session): cover folding a root missing from the index
senamakel a1536b8
refactor(session): extract transcript projection into its own module
senamakel 738f22e
style: apply rustfmt formatting to session transcript code
senamakel be0b3f7
docs(changelog): document review follow-ups for transcript and spend …
senamakel 6e214d6
feat(transcript): add jsonl transcript display support
senamakel 2774598
chore(transcript): import background helpers in jsonl module
senamakel e1c27a4
refactor(session): simplify background transcript handling
senamakel 66e27a7
test(session): cover background delivery provenance across rewrites a…
senamakel df47ff7
chore: files changed crates/tinyagents-session/src/transcript/backgro…
senamakel fd28bbe
refactor(session): extract in-memory transcript test helper
senamakel e5d968c
refactor(tinyagents-session): restructure memory port module
senamakel 33dde5d
chore(session): import thread transcript types in memory port
senamakel 7a5072b
test(session): cover thread listing and display partials
senamakel cb75136
docs(session): clarify legacy rewrite symlink handling
senamakel caccee7
fix(session): in-memory thread listing and display partials; document…
senamakel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| //! Pre-import maintenance of classic JSONL transcripts. | ||
| //! | ||
| //! A host that must fix legacy content before it is imported (for example a | ||
| //! migration that strips a retired prompt section from every persisted | ||
| //! system message) used to walk `session_raw/` and call the path-taking | ||
| //! reader and writer itself. [`rewrite_legacy_transcripts`] owns that walk | ||
| //! here, beside the importer that owns the format, so the host names no file. | ||
|
|
||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| use super::transcripts::raw_dir; | ||
| use super::types::LegacyRewriteReport; | ||
| use crate::transcript::{SessionTranscript, read_transcript, write_transcript_with_tools}; | ||
|
|
||
| const LOG_PREFIX: &str = "[session:legacy:rewrite]"; | ||
|
|
||
| /// Applies `rewrite` to every classic transcript under | ||
| /// `<workspace_dir>/session_raw/` (flat and `DDMMYYYY/` date-grouped | ||
| /// `*.jsonl`), writing a transcript back in full when `rewrite` returns | ||
| /// `true`. A rewritten file is a full rewrite of its reduced context (its | ||
| /// pre-compaction history is not kept), exactly as the classic writer's | ||
| /// `write_transcript` produces. | ||
| /// | ||
| /// Run it before the workspace is imported: the import is one-time, so a | ||
| /// file changed afterwards is not re-read. Per-file read or write failures | ||
| /// are counted in [`LegacyRewriteReport::errors`] and do not stop the walk; a | ||
| /// missing `session_raw/` is an empty report. A directory or directory entry | ||
| /// that cannot be read (anything but a missing `session_raw/`) is counted in | ||
| /// `errors` too, so a report with `errors == 0` means every transcript was | ||
| /// seen. Symlinks *inside* `session_raw/` are skipped, never followed, so | ||
| /// the walk cannot leave the transcript directory through an entry. | ||
| /// `session_raw` itself is resolved like every other transcript reader and | ||
| /// writer resolves it: a workspace may share its `session_raw` through a | ||
| /// symlink (the turn lock keys on the resolved directory for exactly that), | ||
| /// and that directory is the one whose transcripts the import will read. A | ||
| /// rewritten transcript keeps its latest recorded tool declarations | ||
| /// ([`SessionTranscript::tools`]) and each row's background-delivery | ||
| /// provenance. | ||
| pub fn rewrite_legacy_transcripts( | ||
| workspace_dir: &Path, | ||
| mut rewrite: impl FnMut(&mut SessionTranscript) -> bool, | ||
| ) -> LegacyRewriteReport { | ||
| let mut report = LegacyRewriteReport::default(); | ||
| for path in jsonl_files(&raw_dir(workspace_dir), &mut report.errors) { | ||
| report.scanned += 1; | ||
| let mut session = match read_transcript(&path) { | ||
| Ok(session) => session, | ||
| Err(error) => { | ||
| report.errors += 1; | ||
| tracing::warn!("{LOG_PREFIX} unreadable path={}: {error:#}", path.display()); | ||
| continue; | ||
| } | ||
| }; | ||
| if !rewrite(&mut session) { | ||
| report.unchanged += 1; | ||
| continue; | ||
| } | ||
| match write_transcript_with_tools( | ||
| &path, | ||
| &session.messages, | ||
| &session.meta, | ||
| None, | ||
| session.tools.as_ref(), | ||
| ) { | ||
| Ok(()) => { | ||
| report.rewritten += 1; | ||
| tracing::debug!("{LOG_PREFIX} rewrote path={}", path.display()); | ||
| } | ||
| Err(error) => { | ||
| report.errors += 1; | ||
| tracing::warn!( | ||
| "{LOG_PREFIX} write failed path={}: {error:#}", | ||
| path.display() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| tracing::debug!( | ||
| "{LOG_PREFIX} scanned={} rewritten={} unchanged={} errors={}", | ||
| report.scanned, | ||
| report.rewritten, | ||
| report.unchanged, | ||
| report.errors | ||
| ); | ||
| report | ||
| } | ||
|
|
||
| /// Every `*.jsonl` directly under `raw` or one level down, sorted. | ||
| /// | ||
| /// A missing `raw` is no files. Any other failure to read a directory or one | ||
| /// of its entries adds one to `errors` (and is logged) instead of silently | ||
| /// shrinking the scan. Symlinks are skipped without being followed. | ||
| fn jsonl_files(raw: &Path, errors: &mut usize) -> Vec<PathBuf> { | ||
| let mut found = Vec::new(); | ||
| let entries = match std::fs::read_dir(raw) { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Ok(entries) => entries, | ||
| Err(error) if error.kind() == std::io::ErrorKind::NotFound => return found, | ||
| Err(error) => { | ||
| *errors += 1; | ||
| tracing::warn!("{LOG_PREFIX} unreadable dir={}: {error}", raw.display()); | ||
| return found; | ||
|
senamakel marked this conversation as resolved.
|
||
| } | ||
| }; | ||
| for entry in entries { | ||
| let Some((path, kind)) = scanned_entry(raw, entry, errors) else { | ||
| continue; | ||
| }; | ||
| if kind.is_dir() { | ||
| let inner = match std::fs::read_dir(&path) { | ||
| Ok(inner) => inner, | ||
| Err(error) => { | ||
| *errors += 1; | ||
| tracing::warn!("{LOG_PREFIX} unreadable dir={}: {error}", path.display()); | ||
| continue; | ||
| } | ||
| }; | ||
| for entry in inner { | ||
| if let Some((path, kind)) = scanned_entry(&path, entry, errors) | ||
| && kind.is_file() | ||
| && is_jsonl(&path) | ||
| { | ||
| found.push(path); | ||
| } | ||
| } | ||
| } else if kind.is_file() && is_jsonl(&path) { | ||
| found.push(path); | ||
| } | ||
| } | ||
| found.sort(); | ||
| found | ||
| } | ||
|
|
||
| /// One directory entry with its own (not followed) file type; `None` for a | ||
| /// symlink, or for an entry that could not be read (counted in `errors`). | ||
| fn scanned_entry( | ||
| dir: &Path, | ||
| entry: std::io::Result<std::fs::DirEntry>, | ||
| errors: &mut usize, | ||
| ) -> Option<(PathBuf, std::fs::FileType)> { | ||
| // `DirEntry::file_type` does not traverse symlinks: a link reports as a | ||
| // link, whatever it points at. | ||
| let read = entry.and_then(|entry| Ok((entry.path(), entry.file_type()?))); | ||
| match read { | ||
| Ok((path, kind)) if kind.is_symlink() => { | ||
| tracing::debug!("{LOG_PREFIX} skipping symlink path={}", path.display()); | ||
| None | ||
| } | ||
| Ok(found) => Some(found), | ||
| Err(error) => { | ||
| *errors += 1; | ||
| tracing::warn!( | ||
| "{LOG_PREFIX} unreadable entry in dir={}: {error}", | ||
| dir.display() | ||
| ); | ||
| None | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn is_jsonl(path: &Path) -> bool { | ||
| path.extension().and_then(|ext| ext.to_str()) == Some("jsonl") | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| #[path = "rewrite_tests.rs"] | ||
| mod tests; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.