update: session-grouping review follow-up
- PickerOutcome::Picked から未使用の session_id を除去(pod-cli が lookup_session_of で再解決) - picker preview が singular AssistantItem も拾うように - fs_store layout doc に migration(後方互換なし、旧 flat sessions は破棄)を明記 - TaskStore は Session-lifetime、ScopedFs/Tracker は Pod-process lifetime と用語整理 - Pod::session_id / from_manifest_spawned のコメント補強
This commit is contained in:
@@ -570,7 +570,9 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
||||
}
|
||||
|
||||
/// The Session this Pod belongs to. Stable across compaction and
|
||||
/// in-Session fork — only `fork` (a brand-new Session) changes it.
|
||||
/// auto-fork (both stay within the same Session); there is no
|
||||
/// Pod-level operation today that moves a running Pod to a different
|
||||
/// Session.
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.segment_state.session_id()
|
||||
}
|
||||
@@ -2864,6 +2866,8 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
|
||||
let mut common = prepare_pod_common(&manifest, &loader, /* parse_template */ true)?;
|
||||
let skill_shadows = std::mem::take(&mut common.skill_shadows);
|
||||
|
||||
// A spawned child starts its own conversation, so it mints a
|
||||
// fresh Session rather than joining the spawner's.
|
||||
let session_id = session_store::new_session_id();
|
||||
let segment_id = session_store::new_segment_id();
|
||||
let scope_allocation = pod_registry::adopt_allocation(
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
//!
|
||||
//! The per-Session directory makes `list_segments(session_id)` an O(dir)
|
||||
//! scan and gives the fork tree a visible grouping in the filesystem.
|
||||
//!
|
||||
//! Migration: this layout is incompatible with the pre-`session-grouping`
|
||||
//! flat `{root}/{segment_id}.jsonl` form. Project policy is no
|
||||
//! backward compatibility — discard `~/.insomnia/sessions/` (or whatever
|
||||
//! `root` resolved to) before running the new code. `list_sessions`
|
||||
//! ignores top-level files outside session directories, so leftover
|
||||
//! flat files do not corrupt new sessions, but they are no longer
|
||||
//! enumerable by the picker.
|
||||
|
||||
use crate::event_trace::TraceEntry;
|
||||
use crate::segment_log::LogEntry;
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
//! `llm-worker` `Tool` infrastructure. Filesystem access is mediated by
|
||||
//! two orthogonal concerns:
|
||||
//!
|
||||
//! - [`ScopedFs`] — pod-lifetime, expresses the write-block boundary for
|
||||
//! the current scope. Derived from the manifest and shareable across
|
||||
//! sessions.
|
||||
//! - [`Tracker`] — session-lifetime, enforces the "read before edit"
|
||||
//! - [`ScopedFs`] — Pod-process lifetime, expresses the write-block
|
||||
//! boundary for the current scope. Derived from the manifest; not
|
||||
//! persisted across Pod restart.
|
||||
//! - [`Tracker`] — Pod-process lifetime, enforces the "read before edit"
|
||||
//! policy via content hashes and tracks the recency of touched files.
|
||||
//! Recreated fresh per session.
|
||||
//! Recreated fresh on each Pod start (including resume).
|
||||
//!
|
||||
//! The Pod layer owns both instances and passes them to
|
||||
//! [`builtin_tools`] when registering tools on a `Worker`.
|
||||
@@ -42,11 +42,11 @@ pub use tracker::Tracker;
|
||||
pub use write::write_tool;
|
||||
|
||||
/// Register all builtin tools, wiring them to a shared `ScopedFs`
|
||||
/// (pod-lifetime) and `Tracker` (session-lifetime).
|
||||
/// (Pod-process lifetime) and `Tracker` (Pod-process lifetime).
|
||||
///
|
||||
/// All returned factories share the same tracker instance so that
|
||||
/// `Read` / `Write` / `Edit` see a consistent history across tool
|
||||
/// invocations within a single session.
|
||||
/// invocations within a single Pod run.
|
||||
///
|
||||
/// `bash_output_dir` is where the Bash tool spills long outputs. The
|
||||
/// caller is responsible for adding that path to the readable scope
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Pod-lifetime TaskStore and builtin task tools.
|
||||
//! Session-lifetime TaskStore and builtin task tools.
|
||||
//!
|
||||
//! The store is Pod/session-lifetime state shared by the four Task* tools. It
|
||||
//! is reconstructed on resume by replaying TaskCreate / TaskUpdate tool-call
|
||||
//! arguments from persisted history.
|
||||
//! The store survives compaction and Pod restart — it is reconstructed
|
||||
//! on resume by replaying TaskCreate / TaskUpdate tool-call arguments
|
||||
//! from persisted history, so its effective lifetime is the
|
||||
//! [`session_store::SessionId`] (the conversation), not the Pod process.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
//!
|
||||
//! # Lifetime
|
||||
//!
|
||||
//! A `Tracker` is **session-scoped**: the Pod layer creates a fresh
|
||||
//! instance at the start of each agent session and discards it when the
|
||||
//! session ends. The `ScopedFs` write boundary, by contrast, is
|
||||
//! pod-lifetime (derived from the manifest). The two are orthogonal and
|
||||
//! the Pod wires them together when registering builtin tools.
|
||||
//! A `Tracker` is **Pod-process scoped**: the Pod layer creates a fresh
|
||||
//! instance at the start of each Pod run (including resume) and discards
|
||||
//! it when the process exits — it is not persisted, so a resumed
|
||||
//! conversation starts with an empty read/edit history. The `ScopedFs`
|
||||
//! write boundary is likewise Pod-process scoped (derived from the
|
||||
//! manifest). The two are orthogonal and the Pod wires them together
|
||||
//! when registering builtin tools.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use std::path::PathBuf;
|
||||
|
||||
@@ -210,7 +210,7 @@ async fn run_resume() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// viewport before the name dialog opens so each phase gets fresh
|
||||
// vertical room.
|
||||
let leaf_segment_id = match picker::run().await? {
|
||||
PickerOutcome::Picked { segment_id, .. } => segment_id,
|
||||
PickerOutcome::Picked { segment_id } => segment_id,
|
||||
PickerOutcome::Cancelled => return Ok(()),
|
||||
};
|
||||
run_spawn(Some(leaf_segment_id)).await
|
||||
|
||||
@@ -62,11 +62,10 @@ impl From<session_store::StoreError> for PickerError {
|
||||
}
|
||||
|
||||
pub enum PickerOutcome {
|
||||
/// User picked a session; resume at its leaf segment.
|
||||
Picked {
|
||||
session_id: SessionId,
|
||||
segment_id: SegmentId,
|
||||
},
|
||||
/// User picked a session; resume at its leaf segment. The pod-cli
|
||||
/// rehydrates `session_id` via `Store::lookup_session_of` so we only
|
||||
/// need to surface the segment here.
|
||||
Picked { segment_id: SegmentId },
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
@@ -134,10 +133,8 @@ pub async fn run() -> Result<PickerOutcome, PickerError> {
|
||||
}
|
||||
Some(Action::Submit) => {
|
||||
close_viewport(&mut terminal)?;
|
||||
let row = &rows[selected];
|
||||
return Ok(PickerOutcome::Picked {
|
||||
session_id: row.session_id,
|
||||
segment_id: row.leaf_segment_id,
|
||||
segment_id: rows[selected].leaf_segment_id,
|
||||
});
|
||||
}
|
||||
Some(Action::Cancel) => {
|
||||
@@ -200,6 +197,11 @@ fn last_message_preview(entries: &[LogEntry]) -> Option<String> {
|
||||
return Some(format!("user: {}", trim_one_line(&text, 60)));
|
||||
}
|
||||
}
|
||||
LogEntry::AssistantItem { item, .. } => {
|
||||
if let Some(text) = first_text_logged(item) {
|
||||
return Some(format!("assistant: {}", trim_one_line(&text, 60)));
|
||||
}
|
||||
}
|
||||
LogEntry::AssistantItems { items, .. } => {
|
||||
if let Some(text) = items.iter().find_map(first_text_logged) {
|
||||
return Some(format!("assistant: {}", trim_one_line(&text, 60)));
|
||||
|
||||
Reference in New Issue
Block a user