feat: Support persisting paused todo lists to sessions so users can exit and resume paused todo sessions; Also fixed a bug so todo list pausing is only supported when being used in a session since it typically requires multi-turn communication

This commit is contained in:
Dark-Alex-17 committed 2026-09-14 13:57:08 -06:00
1 parent 5d59adadbc
commit 77da48a40f
7 files changed
+473 -33

No files matched your search

+5 -1
View File
@@ -2681,7 +2681,7 @@ mod tests {
FIXTURE_ANNOTATED_TEXT, FIXTURE_ANNOTATED_URI, FIXTURE_BLOB_BYTES, FIXTURE_BLOB_URI,
FIXTURE_LOG_TEXT, FIXTURE_LOG_URI, FixtureServer, fixture_runtime,
};
use crate::config::{Agent, AgentConfig, AppConfig, AppState, WorkingMode};
use crate::config::{Agent, AgentConfig, AppConfig, AppState, Session, WorkingMode};
use crate::supervisor::escalation::{EscalationQueue, EscalationRequest};
use crate::supervisor::mailbox::Inbox;
use crate::supervisor::notification::{agent_notification, job_notification};
@@ -5081,6 +5081,9 @@ mod tests {
};
let mut ctx = RequestContext::new(Arc::new(app), WorkingMode::Cmd);
ctx.tool_scope.functions.append_todo_functions();
// Pausing requires an active session (there is no cross-turn history
// to resume from otherwise), so give the ctx one.
ctx.session = Some(Session::default());
// A blank reason is rejected and does not pause.
let out = run_async(call_with_args("todo__pause", json!({"reason": " "})).eval(&mut ctx))
@@ -5130,6 +5133,7 @@ mod tests {
};
let mut ctx = RequestContext::new(Arc::new(app), WorkingMode::Cmd);
ctx.tool_scope.functions.append_todo_functions();
// session is deliberately None: the subagent reject must win over the session reject.
ctx.self_agent_id = Some("agent_sisyphus_abc123".to_string());
let out = run_async(
+38 -3
View File
@@ -92,9 +92,11 @@ pub fn todo_function_declarations() -> Vec<FunctionDeclaration> {
incomplete todos intact. Use when you cannot proceed without user input \
or a user-side action. NEVER mark unfinished todos as done just to stop \
auto-continuation. Auto-continue resumes automatically on the user's \
next message. Unavailable to subagents: they are driven to completion \
autonomously — a subagent needing input must use a user__* tool, which \
escalates to the root agent mid-turn and blocks until answered."
next message. Requires an active session (the pause/resume cycle needs \
cross-turn history). Unavailable to subagents: they are driven to \
completion autonomously — a subagent needing input must use a user__* \
tool, which escalates to the root agent mid-turn and blocks until \
answered."
.to_string(),
parameters: JsonSchema {
type_value: Some("object".to_string()),
@@ -189,6 +191,15 @@ pub fn handle_todo_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value)
}));
}
if ctx.session.is_none() {
return Ok(json!({
"error": "todo__pause requires an active session: without one there is no \
cross-turn history, so the user's answer would arrive with no \
context to resume from. Ask the user your question directly in \
your reply instead, then end your turn."
}));
}
let reason = args
.get("reason")
.and_then(Value::as_str)
@@ -211,3 +222,27 @@ pub fn handle_todo_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value)
_ => bail!("Unknown todo action: {action}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{AppConfig, AppState, WorkingMode};
use std::sync::Arc;
#[test]
fn pause_without_session_returns_teaching_error() {
let mut app_state = AppState::test_default();
app_state.config = Arc::new(AppConfig {
auto_continue: true,
..AppConfig::default()
});
let mut ctx = RequestContext::new(Arc::new(app_state), WorkingMode::Cmd);
let result =
handle_todo_tool(&mut ctx, "todo__pause", &json!({"reason": "need input"})).unwrap();
let error = result["error"].as_str().unwrap();
assert!(error.contains("requires an active session"));
assert!(ctx.auto_continue_paused.is_none());
}
}