Show a session's subagents as subcards, each with a read-only transcript
A subagent is a second transcript owned by a session, in the same event model, with no process and no controls. The claude translator routes lines carrying parent_tool_use_id to a per-subagent translator and transcript under <session>/subagents/<tool_use_id>; three routes expose the list, a transcript page and the SSE stream. Echo grows /subagent [n] as the rig. On the phone a card with subagents ends in a chevron expander, collapsed by default, opening to outlined subcards styled like dev-updater's components; a subcard opens SessionScreen in read-only form, addressed through TranscriptAddress so paging, cache and stream are shared. Design in SUBAGENTS.md; choices awaiting review in DECISIONS.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
eff5c8b0c0
commit
9fa09b0af1
21 files changed
+1953
-332
No files matched your search
@@ -11,10 +11,12 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::super::driver::{Event, QuestionOption, SessionStatus, context_tokens};
|
||||
use super::super::subagent::Subagents;
|
||||
|
||||
/// Whether this line is the CLI opening a fresh model call.
|
||||
///
|
||||
@@ -92,10 +94,20 @@ pub(super) struct Translator {
|
||||
/// reports none rather than repeating the previous turn's.
|
||||
context: Option<u64>,
|
||||
session_dir: PathBuf,
|
||||
/// This session's subagents, shared with every child translator below --
|
||||
/// see `SUBAGENTS.md`. One registry per session, so a subagent started
|
||||
/// through this translator or any of its children lands in the same
|
||||
/// place a route reads it back from.
|
||||
subagents: Arc<Subagents>,
|
||||
/// One translator per subagent id, holding *its* streaming and
|
||||
/// tool-tracking state -- separate from the parent's because tool ids
|
||||
/// are unique but a `stream_event`'s content-block index is not, and
|
||||
/// parallel subagents interleave their deltas on one stdout.
|
||||
children: HashMap<String, Arc<Mutex<Translator>>>,
|
||||
}
|
||||
|
||||
impl Translator {
|
||||
pub(super) fn new(session_dir: PathBuf) -> Self {
|
||||
pub(super) fn new(session_dir: PathBuf, subagents: Arc<Subagents>) -> Self {
|
||||
Self {
|
||||
session_id: None,
|
||||
pending: HashMap::new(),
|
||||
@@ -103,6 +115,8 @@ impl Translator {
|
||||
interrupting: false,
|
||||
context: None,
|
||||
session_dir,
|
||||
subagents,
|
||||
children: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,13 +136,54 @@ impl Translator {
|
||||
pub(super) fn translate(&mut self, message: &Value) -> Vec<Event> {
|
||||
// Events from subagents (Task tool internals) carry a
|
||||
// parent_tool_use_id; the transcript shows the Task tool's own
|
||||
// start/end instead of every nested step.
|
||||
if message
|
||||
.get("parent_tool_use_id")
|
||||
.is_some_and(|id| !id.is_null())
|
||||
{
|
||||
return Vec::new();
|
||||
// start/end instead of every nested step. Routed into that
|
||||
// subagent's own transcript rather than dropped -- see
|
||||
// `SUBAGENTS.md`.
|
||||
if let Some(parent_id) = message.get("parent_tool_use_id").and_then(Value::as_str) {
|
||||
return self.translate_child(parent_id, message);
|
||||
}
|
||||
self.dispatch(message)
|
||||
}
|
||||
|
||||
/// A line belonging to a subagent rather than to this translator's own
|
||||
/// session. Always returns nothing to the *caller*: everything it
|
||||
/// produces goes into the subagent's own transcript instead.
|
||||
fn translate_child(&mut self, id: &str, message: &Value) -> Vec<Event> {
|
||||
match self.subagents.get(id) {
|
||||
Some(subagent) if !subagent.is_open() => {
|
||||
// The Task call already ended (or this line is stale from a
|
||||
// resumed conversation) -- see `SUBAGENTS.md`'s lifecycle.
|
||||
tracing::debug!("dropping a line for subagent {id}, which has already finished");
|
||||
return Vec::new();
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
// Nobody has heard of this id yet: the Task call itself
|
||||
// either has not been seen or never will be. Started here
|
||||
// with the best title available -- the tool name of this
|
||||
// first line -- since SUBAGENTS.md's real title only
|
||||
// arrives with the Task call.
|
||||
self.subagents.start(id, &fallback_title(message), None);
|
||||
}
|
||||
}
|
||||
let child = self
|
||||
.children
|
||||
.entry(id.to_string())
|
||||
.or_insert_with(|| {
|
||||
Arc::new(Mutex::new(Translator::new(
|
||||
self.session_dir.clone(),
|
||||
Arc::clone(&self.subagents),
|
||||
)))
|
||||
})
|
||||
.clone();
|
||||
let events = child.lock().unwrap().dispatch(message);
|
||||
for event in events {
|
||||
self.subagents.record(id, event);
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn dispatch(&mut self, message: &Value) -> Vec<Event> {
|
||||
match message.get("type").and_then(Value::as_str) {
|
||||
Some("system") => self.translate_system(message),
|
||||
// The CLI's own announcement that `/clear` took effect, sent just
|
||||
@@ -379,22 +434,47 @@ impl Translator {
|
||||
content
|
||||
.iter()
|
||||
.filter(|block| block.get("type").and_then(Value::as_str) == Some("tool_use"))
|
||||
.map(|block| Event::ToolStart {
|
||||
id: block
|
||||
.map(|block| {
|
||||
let id = block
|
||||
.get("id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
tool: block
|
||||
.to_string();
|
||||
let tool = block
|
||||
.get("name")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
input: block.get("input").cloned().unwrap_or(Value::Null),
|
||||
.to_string();
|
||||
let input = block.get("input").cloned().unwrap_or(Value::Null);
|
||||
// A subagent this call is about to start -- see
|
||||
// `SUBAGENTS.md`'s lifecycle #1. The parent's own transcript
|
||||
// still shows only the Task call itself, below.
|
||||
if tool == "Task" || tool == "Agent" {
|
||||
self.start_subagent_from_task(&id, &input);
|
||||
}
|
||||
Event::ToolStart { id, tool, input }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Starts the subagent a Task call names, with the title and prompt
|
||||
/// SUBAGENTS.md describes: the call's `description`, then
|
||||
/// `(<subagent_type>)` when one is given, falling back to the tool's own
|
||||
/// name when there is no description to build one from.
|
||||
fn start_subagent_from_task(&self, id: &str, input: &Value) {
|
||||
let description = text_field(input, "description");
|
||||
let subagent_type = text_field(input, "subagent_type");
|
||||
let prompt = input.get("prompt").and_then(Value::as_str);
|
||||
let title = match (description, subagent_type) {
|
||||
(Some(description), Some(subagent_type)) => {
|
||||
format!("{description} ({subagent_type})")
|
||||
}
|
||||
(Some(description), None) => description,
|
||||
(None, _) => "Task".to_string(),
|
||||
};
|
||||
self.subagents.start(id, &title, prompt);
|
||||
}
|
||||
|
||||
fn translate_control_request(&mut self, message: &Value) -> Vec<Event> {
|
||||
let request = &message["request"];
|
||||
if request.get("subtype").and_then(Value::as_str) != Some("can_use_tool") {
|
||||
@@ -598,11 +678,33 @@ impl Translator {
|
||||
id: about.clone(),
|
||||
output: texts.join("\n"),
|
||||
});
|
||||
// A no-op unless `about` is a subagent's own id -- see
|
||||
// `SUBAGENTS.md`'s lifecycle #3: the parent gets this `ToolEnd`
|
||||
// like any other tool result, and the subagent it names (if it
|
||||
// names one) gets its `Status::Exited`.
|
||||
self.subagents.finish(&about);
|
||||
}
|
||||
events
|
||||
}
|
||||
}
|
||||
|
||||
/// The title to start a subagent under when its own first line arrives
|
||||
/// before (or without) its Task call ever being seen: the tool name of that
|
||||
/// first line, which is the only thing known about it yet. `"subagent"` for
|
||||
/// a line this cannot even find a tool name in, such as one that opens with
|
||||
/// something other than a tool call.
|
||||
fn fallback_title(message: &Value) -> String {
|
||||
message["message"]["content"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|block| block.get("type").and_then(Value::as_str) == Some("tool_use"))
|
||||
.and_then(|block| block.get("name"))
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("subagent")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Whether a failed turn failed because the account is out of quota, and when
|
||||
/// the CLI said the limit lifts.
|
||||
///
|
||||
@@ -696,10 +798,18 @@ mod tests {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A fresh, empty subagent registry over the same temp dir a test's
|
||||
/// translator writes into -- every test here is about the parent's own
|
||||
/// events, so what a registry does with a subagent is `subagent.rs`'s
|
||||
/// tests to make, not these.
|
||||
fn test_subagents(dir: &tempfile::TempDir) -> Arc<Subagents> {
|
||||
Arc::new(Subagents::new(dir.path().to_path_buf()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captures_the_resume_token_and_the_settings_from_init() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -721,7 +831,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_setting_is_reported_when_the_cli_accepts_it_and_not_before() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
|
||||
// What `set_model` does: remember, send, and say nothing yet.
|
||||
translator.expect_setting("req-a".to_string(), Setting::Model("sonnet".to_string()));
|
||||
@@ -798,7 +908,7 @@ mod tests {
|
||||
// The line it sends just after answering `set_permission_mode`, which is
|
||||
// also how a mode changed from the terminal arrives.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -818,7 +928,7 @@ mod tests {
|
||||
fn streams_text_deltas_and_skips_the_consolidated_copy() {
|
||||
// Real lines (trimmed) from the 2.1.237 probe.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -838,7 +948,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tool_use_and_result_become_tool_events() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -865,7 +975,7 @@ mod tests {
|
||||
#[test]
|
||||
fn subagent_events_are_not_duplicated_into_the_transcript() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -875,10 +985,132 @@ mod tests {
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
/// A child line does not just vanish from the parent -- it lands in its
|
||||
/// own subagent's transcript, with that transcript's own sequence
|
||||
/// numbers, starting at 1 like any other.
|
||||
#[test]
|
||||
fn a_child_line_lands_in_its_own_subagents_transcript() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let subagents = test_subagents(&dir);
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), Arc::clone(&subagents));
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_c1","name":"Bash","input":{"command":"echo hi"}}]},"parent_tool_use_id":"toolu_parent"}"#,
|
||||
],
|
||||
);
|
||||
let subagent = subagents.get("toolu_parent").expect("subagent started");
|
||||
let lines = crate::session::transcript::read_after(&subagent.transcript_path(), 0)
|
||||
.expect("read subagent transcript");
|
||||
assert_eq!(lines[0].seq, 1);
|
||||
assert_eq!(
|
||||
lines[0].event,
|
||||
Event::Status {
|
||||
state: SessionStatus::Running
|
||||
}
|
||||
);
|
||||
assert!(
|
||||
lines.iter().any(
|
||||
|entry| matches!(&entry.event, Event::ToolStart { tool, .. } if tool == "Bash")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// The title and prompt shown for a subagent come from the Task call
|
||||
/// that started it, not from anything guessed at its first line.
|
||||
#[test]
|
||||
fn the_subagent_takes_its_title_and_prompt_from_the_task_call() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let subagents = test_subagents(&dir);
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), Arc::clone(&subagents));
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_task","name":"Task","input":{"description":"Investigate the bug","prompt":"Find why X fails","subagent_type":"general-purpose"}}]},"parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
let rows = subagents.list(true);
|
||||
assert_eq!(rows.len(), 1);
|
||||
assert_eq!(rows[0].title, "Investigate the bug (general-purpose)");
|
||||
let subagent = subagents.get(&rows[0].id).expect("subagent");
|
||||
let lines = crate::session::transcript::read_after(&subagent.transcript_path(), 0)
|
||||
.expect("read subagent transcript");
|
||||
assert!(lines.iter().any(
|
||||
|entry| matches!(&entry.event, Event::UserMessage { text, .. } if text == "Find why X fails")
|
||||
));
|
||||
}
|
||||
|
||||
/// The parent's `tool_result` for the Task id is what ends the
|
||||
/// subagent -- SUBAGENTS.md's lifecycle #3 -- and nothing else does.
|
||||
#[test]
|
||||
fn the_parents_tool_result_finishes_the_subagent() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let subagents = test_subagents(&dir);
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), Arc::clone(&subagents));
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_task2","name":"Task","input":{"description":"helper"}}]},"parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
let subagent = subagents.get("toolu_task2").expect("subagent started");
|
||||
assert!(subagent.is_open());
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_task2","content":"done","is_error":false}]},"parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
assert!(!subagent.is_open());
|
||||
}
|
||||
|
||||
/// Two subagents running at once keep two separate transcripts: tool ids
|
||||
/// are unique but a `stream_event`'s content-block index is not, so
|
||||
/// sharing translation state between them would cross their streams.
|
||||
#[test]
|
||||
fn two_parallel_subagents_keep_separate_transcripts() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let subagents = test_subagents(&dir);
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), Arc::clone(&subagents));
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_a","name":"Bash","input":{}}]},"parent_tool_use_id":"toolu_task_a"}"#,
|
||||
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_b","name":"Read","input":{}}]},"parent_tool_use_id":"toolu_task_b"}"#,
|
||||
],
|
||||
);
|
||||
let a = subagents.get("toolu_task_a").expect("subagent a");
|
||||
let b = subagents.get("toolu_task_b").expect("subagent b");
|
||||
let a_events = crate::session::transcript::read_after(&a.transcript_path(), 0)
|
||||
.expect("read a's transcript");
|
||||
let b_events = crate::session::transcript::read_after(&b.transcript_path(), 0)
|
||||
.expect("read b's transcript");
|
||||
assert!(
|
||||
a_events.iter().any(
|
||||
|entry| matches!(&entry.event, Event::ToolStart { tool, .. } if tool == "Bash")
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
b_events.iter().any(
|
||||
|entry| matches!(&entry.event, Event::ToolStart { tool, .. } if tool == "Read")
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
!a_events.iter().any(
|
||||
|entry| matches!(&entry.event, Event::ToolStart { tool, .. } if tool == "Read")
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
!b_events.iter().any(
|
||||
|entry| matches!(&entry.event, Event::ToolStart { tool, .. } if tool == "Bash")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_permission_request_becomes_an_allow_deny_question() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -927,7 +1159,7 @@ mod tests {
|
||||
#[test]
|
||||
fn denying_a_permission_sends_deny() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -945,7 +1177,7 @@ mod tests {
|
||||
// The real 2.1.237 shape, verified live: answers go back inside
|
||||
// updatedInput, keyed by the question text.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -997,7 +1229,7 @@ mod tests {
|
||||
// in the event: a phone that had to read this dialect's tool input to
|
||||
// find them would be the only place that knew how.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1045,7 +1277,7 @@ mod tests {
|
||||
#[test]
|
||||
fn images_in_tool_results_are_saved_and_referenced() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
// A 1x1 PNG, the smallest real payload worth round-tripping.
|
||||
let png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
||||
let line = format!(
|
||||
@@ -1074,7 +1306,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_turn_result_reports_usage_and_returns_to_idle() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1109,7 +1341,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_turn_started_by_another_agent_records_who_and_what() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1143,7 +1375,7 @@ mod tests {
|
||||
#[test]
|
||||
fn an_ordinary_turn_carries_no_peer_note() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1168,7 +1400,7 @@ mod tests {
|
||||
#[test]
|
||||
fn the_context_is_what_the_last_message_held_not_the_turn_added_up() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1208,7 +1440,7 @@ mod tests {
|
||||
// Note the snake_case keys -- the CLI's transcript file writes the same
|
||||
// records in camelCase.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1238,7 +1470,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_failed_compaction_says_why_and_leaves_the_turn_running() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1265,7 +1497,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_boundary_without_counts_says_so_rather_than_inventing_them() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1285,7 +1517,7 @@ mod tests {
|
||||
#[test]
|
||||
fn an_error_result_surfaces_the_message() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1315,7 +1547,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_turn_stopped_by_the_usage_limit_says_so_and_carries_the_reset() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1333,7 +1565,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_limit_the_cli_gave_no_reset_for_is_reported_without_one() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
@@ -1366,7 +1598,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_turn_stopped_on_purpose_is_not_an_error() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let stopped_result = r#"{"type":"result","subtype":"error_during_execution","is_error":true,"result":"Interrupted by user","usage":{}}"#;
|
||||
|
||||
translator.expect_interrupt();
|
||||
@@ -1398,7 +1630,7 @@ mod tests {
|
||||
#[test]
|
||||
fn replayed_and_synthetic_user_text_is_skipped() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
|
||||
Reference in new issue
Block a user