Use native Codex steering and transcript deletion

This commit is contained in:
iris committed 2026-09-09 12:19:11 -04:00
1 parent 00538cc19b
commit 8c88a7e991
12 files changed
+1004 -390

No files matched your search

+71 -14
View File
@@ -205,6 +205,9 @@ pub struct SessionInfo {
/// Reported rather than worked out on the phone, because the phone has
/// the provider's *name* and this is a property of its *kind*.
pub keeps_own_transcript: bool,
/// The CLI whose copy the delete dialog can optionally remove.
#[serde(skip_serializing_if = "Option::is_none")]
pub own_transcript_name: Option<&'static str>,
/// How much this session asks before acting. Reported so the phone can
/// *show* the current mode rather than assume one -- a picker that
/// guesses its own value is how you change something you thought you
@@ -600,6 +603,7 @@ impl LiveSession {
usage_provider: kind.and_then(DriverKind::usage_provider),
imported,
keeps_own_transcript: kind.is_some_and(DriverKind::keeps_own_transcript),
own_transcript_name: kind.and_then(DriverKind::own_transcript_name),
cwd: cwd.map(Path::to_path_buf),
status: *self.shared.status.lock().unwrap(),
last_activity: *self.shared.last_activity.lock().unwrap(),
@@ -625,9 +629,8 @@ pub struct SessionManager {
/// Where every session's pump reports what this layer does not act on --
/// see [`Announcements`].
announce: Announcements,
/// Imports and deletes running against a machine's Claude Code
/// sessions: like the notifications, state the phone reads but does not
/// own.
/// Provider-transcript operations running against a machine: like the
/// notifications, state the phone reads but does not own.
pending: Arc<pending::Registry>,
/// What to mark sessions spawned here as -- see
/// [`SessionManager::marking_new_sessions_throwaway`].
@@ -640,6 +643,38 @@ pub struct SessionManager {
inner: RwLock<Inner>,
}
/// The CLI-owned copy optionally removed with an ai-app session.
#[derive(Debug, PartialEq, Eq)]
pub struct ForeignTranscript {
pub setup: String,
pub id: String,
kind: DriverKind,
}
impl ForeignTranscript {
/// Removes the provider's own durable record. The route remains generic:
/// adding another transcript-owning driver extends this provider boundary.
pub async fn delete(&self, transport: &Transport) -> Result<()> {
match self.kind {
DriverKind::ClaudeCli => import::delete(transport, std::slice::from_ref(&self.id))
.await?
.remove(&self.id)
.unwrap_or_else(|| Err(format!("nothing was reported about {}", self.id)))
.map_err(anyhow::Error::msg),
DriverKind::CodexCli => codex::delete_transcript(transport, &self.id).await,
DriverKind::Echo | DriverKind::LlamaCpp => {
anyhow::bail!("this provider keeps no transcript of its own")
}
}
}
pub fn owner_name(&self) -> &'static str {
self.kind
.own_transcript_name()
.expect("a foreign transcript has an owner")
}
}
impl SessionManager {
/// Loads the config and brings every persisted session back: its
/// transcript, its pump, and the process it left running where it left
@@ -1034,15 +1069,26 @@ impl SessionManager {
Some((setup.ssh.clone()?, meta.cwd.clone()))
}
pub fn foreign_transcript(&self, id: &str) -> Option<(String, String)> {
pub fn foreign_transcript(&self, id: &str) -> Option<ForeignTranscript> {
let inner = self.inner.read().unwrap();
let meta = inner.config.sessions.iter().find(|meta| meta.id == id)?;
let (followed, resuming) = foreign_ids(&self.data_dir.join(&meta.id));
// The cursor first: an imported session follows a file that exists
// whether or not a CLI has resumed it yet.
followed
.or(resuming)
.map(|foreign| (meta.setup.clone(), foreign))
let kind = kind_of(&inner.config, &meta.setup, &meta.provider)?;
let session_dir = self.data_dir.join(&meta.id);
let foreign = match kind {
DriverKind::ClaudeCli => {
let (followed, resuming) = foreign_ids(&session_dir);
// The cursor first: an imported session follows a file that exists
// whether or not a CLI has resumed it yet.
followed.or(resuming)
}
DriverKind::CodexCli => codex::read_thread(&session_dir),
DriverKind::Echo | DriverKind::LlamaCpp => None,
}?;
Some(ForeignTranscript {
setup: meta.setup.clone(),
id: foreign,
kind,
})
}
/// Every session, in config order, with live status joined in. A
@@ -1088,6 +1134,8 @@ impl SessionManager {
&meta.setup,
&meta.provider,
),
own_transcript_name: kind_of(&inner.config, &meta.setup, &meta.provider)
.and_then(DriverKind::own_transcript_name),
cwd: meta.cwd.clone(),
status: status_of_unlaunched(&self.data_dir.join(&meta.id)),
last_activity: meta.created,
@@ -2804,6 +2852,8 @@ mod tests {
#[test]
fn only_a_driver_that_keeps_its_own_record_survives_deletion() {
assert!(DriverKind::ClaudeCli.keeps_own_transcript());
assert!(DriverKind::CodexCli.keeps_own_transcript());
assert_eq!(DriverKind::CodexCli.own_transcript_name(), Some("Codex"));
assert!(!DriverKind::Echo.keeps_own_transcript());
assert!(!DriverKind::LlamaCpp.keeps_own_transcript());
}
@@ -3268,14 +3318,17 @@ mod tests {
let dir = tempfile::tempdir().expect("tempdir");
let config_path = dir.path().join("config.ron");
let data_dir = dir.path().join("sessions");
seed_echo_only(&config_path);
let provider = seed_stand_in_cli(&config_path, dir.path());
let manager = SessionManager::new(
config_path.clone(),
data_dir.clone(),
data_dir.join("models"),
)
.expect("manager");
let info = manager.spawn_session(echo_spec()).expect("spawn");
.expect("manager")
.marking_new_sessions_throwaway(true);
let info = manager
.spawn_session(stand_in_spec(&provider))
.expect("spawn");
// Nothing recorded yet, so there is nothing a delete would reach.
assert_eq!(manager.foreign_transcript(&info.id), None);
@@ -3283,7 +3336,11 @@ mod tests {
claude::write_resume_token(&data_dir.join(&info.id), "5ecf21da-d53f");
assert_eq!(
manager.foreign_transcript(&info.id),
Some((info.setup.clone(), "5ecf21da-d53f".to_string()))
Some(ForeignTranscript {
setup: info.setup.clone(),
id: "5ecf21da-d53f".to_string(),
kind: DriverKind::ClaudeCli,
})
);
// A session that is not there has no transcript to name, rather