feat: support auto continuation in subagents and prevent them from calling todo__pause

This commit is contained in:
Dark-Alex-17 committed 2026-09-03 15:50:00 -06:00
1 parent 101b706be5
commit cb5b46800f
8 files changed
+305 -3

No files matched your search

+78
View File
@@ -727,6 +727,10 @@ impl Functions {
.retain(|f| !f.name.starts_with(TODO_FUNCTION_PREFIX));
}
pub fn remove_function(&mut self, name: &str) {
self.declarations.retain(|f| f.name != name);
}
pub fn append_memory_functions(&mut self) {
self.declarations
.extend(memory::memory_function_declarations());
@@ -3371,6 +3375,7 @@ mod tests {
assert!(f.contains("todo__done"));
assert!(f.contains("todo__list"));
assert!(f.contains("todo__clear"));
assert!(f.contains("todo__pause"));
}
#[test]
@@ -4914,6 +4919,79 @@ mod tests {
assert!(err.contains("Auto-continue is not enabled"), "{err}");
}
#[test]
fn todo_pause_sets_and_clears_pause_flag() {
let app = AppState {
config: Arc::new(AppConfig {
auto_continue: true,
..Default::default()
}),
..AppState::test_default()
};
let mut ctx = RequestContext::new(Arc::new(app), WorkingMode::Cmd);
ctx.tool_scope.functions.append_todo_functions();
// A blank reason is rejected and does not pause.
let out = run_async(call_with_args("todo__pause", json!({"reason": " "})).eval(&mut ctx))
.unwrap();
assert_eq!(out["error"], "reason is required");
assert!(ctx.auto_continue_paused.is_none());
// A real reason pauses auto-continue, preserving the todo list.
run_async(call_with_args("todo__init", json!({"goal": "ship"})).eval(&mut ctx)).unwrap();
run_async(call_with_args("todo__add", json!({"task": "deploy"})).eval(&mut ctx)).unwrap();
let out = run_async(
call_with_args("todo__pause", json!({"reason": "need prod API key"})).eval(&mut ctx),
)
.unwrap();
assert_eq!(out["status"], "ok");
assert_eq!(
ctx.auto_continue_paused.as_deref(),
Some("need prod API key")
);
assert!(ctx.todo_list.has_incomplete());
// The next real user message lifts the pause.
ctx.resume_auto_continue();
assert!(ctx.auto_continue_paused.is_none());
// Re-initializing or clearing the list also lifts a stale pause.
ctx.pause_auto_continue("stale");
run_async(call_with_args("todo__init", json!({"goal": "new"})).eval(&mut ctx)).unwrap();
assert!(ctx.auto_continue_paused.is_none());
ctx.pause_auto_continue("stale again");
run_async(call_with_args("todo__clear", json!({})).eval(&mut ctx)).unwrap();
assert!(ctx.auto_continue_paused.is_none());
}
/// Subagents are driven to completion autonomously: their turn boundary
/// has no interactive user, and user__* escalations work mid-turn, so
/// pausing is never the right move for a child. The handler must reject
/// the call, leave the pause flag unset, and redirect to escalation.
#[test]
fn todo_pause_is_rejected_for_subagents() {
let app = AppState {
config: Arc::new(AppConfig {
auto_continue: true,
..Default::default()
}),
..AppState::test_default()
};
let mut ctx = RequestContext::new(Arc::new(app), WorkingMode::Cmd);
ctx.tool_scope.functions.append_todo_functions();
ctx.self_agent_id = Some("agent_sisyphus_abc123".to_string());
let out = run_async(
call_with_args("todo__pause", json!({"reason": "need user input"})).eval(&mut ctx),
)
.unwrap();
let err = out["error"].as_str().unwrap();
assert!(err.contains("unavailable to subagents"), "{err}");
assert!(err.contains("user__"), "{err}");
assert!(ctx.auto_continue_paused.is_none());
}
#[test]
fn eval_routes_memory_prefix_to_memory_handler() {
let mut ctx = RequestContext::new(Arc::new(AppState::test_default()), WorkingMode::Cmd);