feat: add auto-continue support to all contexts

This commit is contained in:
2026-05-08 12:02:10 -06:00
parent ca52629a24
commit b308c39d6d
11 changed files with 397 additions and 60 deletions
+5 -3
View File
@@ -17,9 +17,11 @@ agent_session: null # Set a session to use when starting the agent.
name: <agent-name> # Name of the agent, used in the UI and logs
description: <description> # Description of the agent, used in the UI
version: 1 # Version of the agent
# Todo System & Auto-Continuation
# These settings help smaller models handle multi-step tasks more reliably.
# See docs/TODO-SYSTEM.md for detailed documentation.
# Auto-Continue (Todo System)
# The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain
max_auto_continues: 10 # Maximum number of automatic continuations before stopping
inject_todo_instructions: true # Inject the default todo tool usage instructions into the agent's system prompt
+10
View File
@@ -81,6 +81,16 @@ mapping_mcp_servers: # Alias for an MCP server or set of servers
git: github,gitmcp
enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack,ddg-search')
# ---- Auto-Continue (Todo System) ----
# The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true)
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
# ---- Session ----
# See the [Session documentation](./docs/SESSIONS.md) for more information
save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save save; if null, ask the user what to do
+14 -1
View File
@@ -1,5 +1,9 @@
---
# Everything in this section is optional
############################################
## Everything in this section is optional ##
############################################
# Role Configuration
name: <role-name> # The name of the role
model: openai:gpt-4o # The model to use for this role
temperature: 0.2 # The temperature to use for this role when querying the model
@@ -8,5 +12,14 @@ enabled_tools: fs_ls,fs_cat # A comma-separated list of tools to enabl
enabled_mcp_servers: github,gitmcp # A comma-separated list of MCP servers to enable for this role
prompt: null # A custom prompt to use for this role that will immediately query
# the model for output instead of using the instructions below
# Auto-Continue (Todo System)
# The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
inject_todo_instructions: true # Inject default todo tool usage instructions into the system prompt (default: true)
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
---
You are an expert at doing things. This is where you write the instructions for the role.
+8 -12
View File
@@ -415,6 +415,14 @@ impl Agent {
self.config.max_auto_continues
}
pub fn inject_todo_instructions(&self) -> bool {
self.config.inject_todo_instructions
}
pub fn continuation_prompt_value(&self) -> Option<String> {
self.config.continuation_prompt.clone()
}
pub fn can_spawn_agents(&self) -> bool {
self.config.can_spawn_agents
}
@@ -439,18 +447,6 @@ impl Agent {
self.config.escalation_timeout
}
pub fn continuation_prompt(&self) -> String {
self.config.continuation_prompt.clone().unwrap_or_else(|| {
formatdoc! {"
[SYSTEM REMINDER - TODO CONTINUATION]
You have incomplete tasks. Rules:
1. BEFORE marking a todo done: verify the work compiles/works. No premature completion.
2. If a todo is broad (e.g. \"implement X and implement Y\"): break it into specific subtasks FIRST using todo__add, then work on those.\n\
3. Each todo should be atomic and be \"single responsibility\" - completable in one focused action.
4. Continue with the next pending item now. Call tools immediately."}
})
}
pub fn compression_threshold(&self) -> Option<usize> {
self.config.compression_threshold
}
+15
View File
@@ -39,6 +39,11 @@ pub struct AppConfig {
pub mapping_mcp_servers: IndexMap<String, String>,
pub enabled_mcp_servers: Option<String>,
pub auto_continue: bool,
pub max_auto_continues: usize,
pub inject_todo_instructions: bool,
pub continuation_prompt: Option<String>,
pub repl_prelude: Option<String>,
pub cmd_prelude: Option<String>,
pub agent_session: Option<String>,
@@ -95,6 +100,11 @@ impl Default for AppConfig {
mapping_mcp_servers: Default::default(),
enabled_mcp_servers: None,
auto_continue: false,
max_auto_continues: 10,
inject_todo_instructions: true,
continuation_prompt: None,
repl_prelude: None,
cmd_prelude: None,
agent_session: None,
@@ -152,6 +162,11 @@ impl AppConfig {
mapping_mcp_servers: config.mapping_mcp_servers,
enabled_mcp_servers: config.enabled_mcp_servers,
auto_continue: config.auto_continue,
max_auto_continues: config.max_auto_continues,
inject_todo_instructions: config.inject_todo_instructions,
continuation_prompt: config.continuation_prompt,
repl_prelude: config.repl_prelude,
cmd_prelude: config.cmd_prelude,
agent_session: config.agent_session,
+10
View File
@@ -121,6 +121,11 @@ pub struct Config {
pub mapping_mcp_servers: IndexMap<String, String>,
pub enabled_mcp_servers: Option<String>,
pub auto_continue: bool,
pub max_auto_continues: usize,
pub inject_todo_instructions: bool,
pub continuation_prompt: Option<String>,
pub repl_prelude: Option<String>,
pub cmd_prelude: Option<String>,
pub agent_session: Option<String>,
@@ -177,6 +182,11 @@ impl Default for Config {
mapping_mcp_servers: Default::default(),
enabled_mcp_servers: None,
auto_continue: false,
max_auto_continues: 10,
inject_todo_instructions: true,
continuation_prompt: None,
repl_prelude: None,
cmd_prelude: None,
agent_session: None,
+153 -7
View File
@@ -1,4 +1,3 @@
use super::MessageContentToolCalls;
use super::rag_cache::{RagCache, RagKey};
use super::session::Session;
use super::todo::TodoList;
@@ -9,6 +8,7 @@ use super::{
SUMMARIZATION_PROMPT, SUMMARY_CONTEXT_PROMPT, StateFlags, TEMP_ROLE_NAME, TEMP_SESSION_NAME,
WorkingMode, ensure_parent_exists, list_agents, paths,
};
use super::{MessageContentToolCalls, prompts};
use crate::client::{Model, ModelType, list_models};
use crate::function::{
FunctionDeclaration, Functions, ToolCallTracker, ToolResult,
@@ -38,6 +38,13 @@ use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct AutoContinueConfig {
pub enabled: bool,
pub max_continues: usize,
pub inject_instructions: bool,
pub continuation_prompt: Option<String>,
}
pub struct RequestContext {
pub app: Arc<AppState>,
@@ -523,7 +530,7 @@ impl RequestContext {
}
pub fn extract_role(&self, app: &AppConfig) -> Role {
if let Some(session) = self.session.as_ref() {
let mut role = if let Some(session) = self.session.as_ref() {
session.to_role()
} else if let Some(agent) = self.agent.as_ref() {
agent.to_role()
@@ -539,6 +546,65 @@ impl RequestContext {
app.enabled_mcp_servers.clone(),
);
role
};
if self.agent.is_none() {
let config = self.auto_continue_config();
if config.enabled && config.inject_instructions {
role.append_to_prompt(prompts::DEFAULT_TODO_INSTRUCTIONS);
}
}
role
}
pub fn auto_continue_config(&self) -> AutoContinueConfig {
if let Some(agent) = &self.agent {
return AutoContinueConfig {
enabled: agent.auto_continue_enabled(),
max_continues: agent.max_auto_continues(),
inject_instructions: agent.inject_todo_instructions(),
continuation_prompt: agent.continuation_prompt_value(),
};
}
let app = &self.app.config;
let enabled = self
.session
.as_ref()
.and_then(|s| s.auto_continue())
.or_else(|| self.role.as_ref().and_then(|r| r.auto_continue()))
.unwrap_or(app.auto_continue);
let max = self
.session
.as_ref()
.and_then(|s| s.max_auto_continues())
.or_else(|| self.role.as_ref().and_then(|r| r.max_auto_continues()))
.unwrap_or(app.max_auto_continues);
let inject = self
.session
.as_ref()
.and_then(|s| s.inject_todo_instructions())
.or_else(|| {
self.role
.as_ref()
.and_then(|r| r.inject_todo_instructions())
})
.unwrap_or(app.inject_todo_instructions);
let prompt = self
.session
.as_ref()
.and_then(|s| s.continuation_prompt().map(|v| v.to_string()))
.or_else(|| {
self.role
.as_ref()
.and_then(|r| r.continuation_prompt().map(|v| v.to_string()))
})
.or_else(|| app.continuation_prompt.clone());
AutoContinueConfig {
enabled,
max_continues: max,
inject_instructions: inject,
continuation_prompt: prompt,
}
}
@@ -747,6 +813,8 @@ impl RequestContext {
app.function_calling_support.to_string(),
),
("mcp_server_support", app.mcp_server_support.to_string()),
("auto_continue", app.auto_continue.to_string()),
("max_auto_continues", app.max_auto_continues.to_string()),
("stream", app.stream.to_string()),
("save", app.save.to_string()),
("keybindings", app.keybindings.clone()),
@@ -1402,12 +1470,24 @@ impl RequestContext {
}
pub async fn update(&mut self, data: &str, abort_signal: AbortSignal) -> Result<()> {
let parts: Vec<&str> = data.split_whitespace().collect();
if parts.len() != 2 {
let (key, raw_value) = match data.split_once(char::is_whitespace) {
Some((k, v)) => (k, v.trim()),
None => bail!("Usage: .set <key> <value>. If value is null, unset key."),
};
if raw_value.is_empty() {
bail!("Usage: .set <key> <value>. If value is null, unset key.");
}
let key = parts[0];
let value = parts[1];
let value = match key {
"continuation_prompt" => raw_value,
_ => {
if raw_value.contains(char::is_whitespace) {
bail!("Usage: .set <key> <value>. If value is null, unset key.");
}
raw_value
}
};
match key {
"temperature" => {
let value = super::parse_value(value)?;
@@ -1522,6 +1602,49 @@ impl RequestContext {
let value = value.parse().with_context(|| "Invalid value")?;
self.update_app_config(|app| app.highlight = value);
}
"auto_continue" => {
let value: bool = value.parse().with_context(|| "Invalid value")?;
if value && !self.app.config.function_calling_support {
bail!(
"Cannot enable auto_continue: function calling is disabled. Set 'function_calling_support: true' first."
);
}
if let Some(session) = self.session.as_mut() {
session.set_auto_continue(Some(value));
} else {
self.update_app_config(|app| app.auto_continue = value);
}
if value
&& self.app.config.function_calling_support
&& !self.tool_scope.functions.contains("todo__init")
{
self.tool_scope.functions.append_todo_functions();
}
}
"max_auto_continues" => {
let value: usize = value.parse().with_context(|| "Invalid value")?;
if let Some(session) = self.session.as_mut() {
session.set_max_auto_continues(Some(value));
} else {
self.update_app_config(|app| app.max_auto_continues = value);
}
}
"inject_todo_instructions" => {
let value: bool = value.parse().with_context(|| "Invalid value")?;
if let Some(session) = self.session.as_mut() {
session.set_inject_todo_instructions(Some(value));
} else {
self.update_app_config(|app| app.inject_todo_instructions = value);
}
}
"continuation_prompt" => {
let value: Option<String> = super::parse_value(value)?;
if let Some(session) = self.session.as_mut() {
session.set_continuation_prompt(value);
} else {
self.update_app_config(|app| app.continuation_prompt = value);
}
}
_ => bail!("Unknown key '{key}'"),
}
Ok(())
@@ -1603,10 +1726,14 @@ impl RequestContext {
},
".set" => {
let mut values = vec![
"auto_continue",
"continuation_prompt",
"temperature",
"top_p",
"enabled_tools",
"enabled_mcp_servers",
"inject_todo_instructions",
"max_auto_continues",
"save_session",
"compression_threshold",
"rag_reranker_model",
@@ -1721,6 +1848,19 @@ impl RequestContext {
.map(|v| v.id())
.collect(),
"highlight" => super::complete_bool(app.highlight),
"auto_continue" => {
let config = self.auto_continue_config();
super::complete_bool(config.enabled)
}
"max_auto_continues" => {
let config = self.auto_continue_config();
vec![config.max_continues.to_string()]
}
"inject_todo_instructions" => {
let config = self.auto_continue_config();
super::complete_bool(config.inject_instructions)
}
"continuation_prompt" => vec!["null".to_string()],
_ => vec![],
};
values = candidates.into_iter().map(|v| (v, None)).collect();
@@ -1810,6 +1950,12 @@ impl RequestContext {
if self.working_mode.is_repl() {
functions.append_user_interaction_functions();
}
if self.agent.is_none()
&& app.function_calling_support
&& self.auto_continue_config().enabled
{
functions.append_todo_functions();
}
if !mcp_runtime.is_empty() {
functions.append_mcp_meta_functions(mcp_runtime.server_names());
}
@@ -2196,7 +2342,7 @@ impl RequestContext {
.clone()
.unwrap_or_else(|| SUMMARY_CONTEXT_PROMPT.into());
let todo_prefix = if self.agent.is_some() && !self.todo_list.is_empty() {
let todo_prefix = if self.auto_continue_config().enabled && !self.todo_list.is_empty() {
format!(
"[ACTIVE TODO LIST]\n{}\n\n",
self.todo_list.render_for_model()
+50
View File
@@ -55,6 +55,14 @@ pub struct Role {
enabled_tools: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
enabled_mcp_servers: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
auto_continue: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
max_auto_continues: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
inject_todo_instructions: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
continuation_prompt: Option<String>,
#[serde(skip)]
model: Model,
@@ -90,6 +98,16 @@ impl Role {
"enabled_mcp_servers" => {
role.enabled_mcp_servers = value.as_str().map(|v| v.to_string())
}
"auto_continue" => role.auto_continue = value.as_bool(),
"max_auto_continues" => {
role.max_auto_continues = value.as_u64().map(|v| v as usize)
}
"inject_todo_instructions" => {
role.inject_todo_instructions = value.as_bool()
}
"continuation_prompt" => {
role.continuation_prompt = value.as_str().map(|v| v.to_string())
}
_ => (),
}
}
@@ -131,6 +149,18 @@ impl Role {
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
metadata.push(format!("enabled_mcp_servers: {enabled_mcp_servers}"));
}
if let Some(auto_continue) = self.auto_continue {
metadata.push(format!("auto_continue: {auto_continue}"));
}
if let Some(max_auto_continues) = self.max_auto_continues {
metadata.push(format!("max_auto_continues: {max_auto_continues}"));
}
if let Some(inject_todo_instructions) = self.inject_todo_instructions {
metadata.push(format!("inject_todo_instructions: {inject_todo_instructions}"));
}
if let Some(continuation_prompt) = &self.continuation_prompt {
metadata.push(format!("continuation_prompt: {continuation_prompt}"));
}
if metadata.is_empty() {
format!("{}\n", self.prompt)
} else if self.prompt.is_empty() {
@@ -225,6 +255,26 @@ impl Role {
self.prompt.contains(INPUT_PLACEHOLDER)
}
pub fn auto_continue(&self) -> Option<bool> {
self.auto_continue
}
pub fn max_auto_continues(&self) -> Option<usize> {
self.max_auto_continues
}
pub fn inject_todo_instructions(&self) -> Option<bool> {
self.inject_todo_instructions
}
pub fn continuation_prompt(&self) -> Option<&str> {
self.continuation_prompt.as_deref()
}
pub fn append_to_prompt(&mut self, text: &str) {
self.prompt.push_str(text);
}
pub fn echo_messages(&self, input: &Input) -> String {
let input_markdown = input.render();
if self.is_empty_prompt() {
+77
View File
@@ -32,6 +32,14 @@ pub struct Session {
save_session: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
compression_threshold: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
auto_continue: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
max_auto_continues: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
inject_todo_instructions: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
continuation_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
role_name: Option<String>,
@@ -170,6 +178,18 @@ impl Session {
if let Some(save_session) = self.save_session() {
data["save_session"] = save_session.into();
}
if let Some(auto_continue) = self.auto_continue() {
data["auto_continue"] = auto_continue.into();
}
if let Some(max_auto_continues) = self.max_auto_continues() {
data["max_auto_continues"] = max_auto_continues.into();
}
if let Some(inject_todo_instructions) = self.inject_todo_instructions() {
data["inject_todo_instructions"] = inject_todo_instructions.into();
}
if let Some(continuation_prompt) = self.continuation_prompt() {
data["continuation_prompt"] = continuation_prompt.into();
}
let (tokens, percent) = self.tokens_usage();
data["total_tokens"] = tokens.into();
if let Some(max_input_tokens) = self.model().max_input_tokens() {
@@ -225,6 +245,19 @@ impl Session {
items.push(("compression_threshold", compression_threshold.to_string()));
}
if let Some(auto_continue) = self.auto_continue() {
items.push(("auto_continue", auto_continue.to_string()));
}
if let Some(max_auto_continues) = self.max_auto_continues() {
items.push(("max_auto_continues", max_auto_continues.to_string()));
}
if let Some(inject_todo_instructions) = self.inject_todo_instructions() {
items.push(("inject_todo_instructions", inject_todo_instructions.to_string()));
}
if let Some(continuation_prompt) = self.continuation_prompt() {
items.push(("continuation_prompt", continuation_prompt.to_string()));
}
if let Some(max_input_tokens) = self.model().max_input_tokens() {
items.push(("max_input_tokens", max_input_tokens.to_string()));
}
@@ -335,6 +368,50 @@ impl Session {
}
}
pub fn auto_continue(&self) -> Option<bool> {
self.auto_continue
}
pub fn max_auto_continues(&self) -> Option<usize> {
self.max_auto_continues
}
pub fn set_auto_continue(&mut self, value: Option<bool>) {
if self.auto_continue != value {
self.auto_continue = value;
self.dirty = true;
}
}
pub fn set_max_auto_continues(&mut self, value: Option<usize>) {
if self.max_auto_continues != value {
self.max_auto_continues = value;
self.dirty = true;
}
}
pub fn inject_todo_instructions(&self) -> Option<bool> {
self.inject_todo_instructions
}
pub fn continuation_prompt(&self) -> Option<&str> {
self.continuation_prompt.as_deref()
}
pub fn set_inject_todo_instructions(&mut self, value: Option<bool>) {
if self.inject_todo_instructions != value {
self.inject_todo_instructions = value;
self.dirty = true;
}
}
pub fn set_continuation_prompt(&mut self, value: Option<String>) {
if self.continuation_prompt != value {
self.continuation_prompt = value;
self.dirty = true;
}
}
pub fn needs_compression(&self, global_compression_threshold: usize) -> bool {
if self.compressing {
return false;
+8 -2
View File
@@ -94,8 +94,14 @@ pub fn handle_todo_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value)
.strip_prefix(TODO_FUNCTION_PREFIX)
.unwrap_or(cmd_name);
if ctx.agent.is_none() {
bail!("No active agent");
if !ctx.app.config.function_calling_support {
bail!("Cannot use todo tools: function calling is disabled.");
}
let auto_config = ctx.auto_continue_config();
if !auto_config.enabled {
bail!(
"Auto-continue is not enabled. Set 'auto_continue: true' in your config to use todo tools."
);
}
match action {
+47 -35
View File
@@ -31,9 +31,19 @@ use reedline::{
use reedline::{MenuBuilder, Signal};
use std::sync::LazyLock;
use std::{env, process, sync::Arc};
use indoc::indoc;
const MENU_NAME: &str = "completion_menu";
pub const DEFAULT_CONTINUATION_PROMPT: &str = indoc! {"
[SYSTEM REMINDER - TODO CONTINUATION]
You have incomplete tasks. Rules:
1. BEFORE marking a todo done: verify the work compiles/works. No premature completion.
2. If a todo is broad (e.g. \"implement X and implement Y\"): break it into specific subtasks FIRST using todo__add, then work on those.\n\
3. Each todo should be atomic and be \"single responsibility\" - completable in one focused action.
4. Continue with the next pending item now. Call tools immediately."
};
static REPL_COMMANDS: LazyLock<[ReplCommand; 39]> = LazyLock::new(|| {
[
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
@@ -141,7 +151,7 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 39]> = LazyLock::new(|| {
ReplCommand::new(
".clear todo",
"Clear the todo list and stop auto-continuation",
AssertState::True(StateFlags::AGENT),
AssertState::pass(),
),
ReplCommand::new(
".rag",
@@ -764,25 +774,18 @@ pub async fn run_repl_command(
bail!("Use '.empty session' instead");
}
Some("todo") => {
let cleared = match ctx.agent.as_mut() {
Some(agent) => {
if !agent.auto_continue_enabled() {
bail!(
"The todo system is not enabled for this agent. Set 'auto_continue: true' in the agent's config.yaml to enable it."
);
}
if ctx.todo_list.is_empty() {
println!("Todo list is already empty.");
false
} else {
ctx.clear_todo_list();
println!("Todo list cleared.");
true
}
}
None => bail!("No active agent"),
};
let _ = cleared;
let config = ctx.auto_continue_config();
if !config.enabled {
bail!(
"Auto-continue is not enabled. Set 'auto_continue: true' in your config to enable it."
);
}
if ctx.todo_list.is_empty() {
println!("Todo list is already empty.");
} else {
ctx.clear_todo_list();
println!("Todo list cleared.");
}
}
_ => unknown_command()?,
},
@@ -881,19 +884,22 @@ async fn ask(
)
.await
} else {
let should_continue = agent_should_continue(ctx);
let do_continue = should_continue(ctx);
if should_continue {
if do_continue {
let full_prompt = {
let config = ctx.auto_continue_config();
let todo_state = ctx.todo_list.render_for_model();
let remaining = ctx.todo_list.incomplete_count();
ctx.set_last_continuation_response(output.clone());
ctx.increment_auto_continue_count();
let agent = ctx.agent.as_mut().expect("agent checked above");
let count = ctx.auto_continue_count;
let max = agent.max_auto_continues();
let max = config.max_continues;
let prompt = agent.continuation_prompt();
let prompt = config
.continuation_prompt
.as_deref()
.unwrap_or(DEFAULT_CONTINUATION_PROMPT);
let color = if app.light_theme() {
nu_ansi_term::Color::LightGray
@@ -934,7 +940,7 @@ async fn ask(
.is_some_and(|s| s.needs_compression(app.compression_threshold));
if needs_compression {
let agent_can_continue_after_compress = agent_should_continue(ctx);
let agent_can_continue_after_compress = should_continue(ctx);
if let Some(session) = ctx.session.as_mut() {
session.set_compressing(true);
@@ -956,14 +962,17 @@ async fn ask(
if agent_can_continue_after_compress {
let full_prompt = {
let config = ctx.auto_continue_config();
let todo_state = ctx.todo_list.render_for_model();
let remaining = ctx.todo_list.incomplete_count();
ctx.increment_auto_continue_count();
let agent = ctx.agent.as_mut().expect("agent checked above");
let count = ctx.auto_continue_count;
let max = agent.max_auto_continues();
let max = config.max_continues;
let prompt = agent.continuation_prompt();
let prompt = config
.continuation_prompt
.as_deref()
.unwrap_or(DEFAULT_CONTINUATION_PROMPT);
let color = if app.light_theme() {
nu_ansi_term::Color::LightGray
@@ -989,10 +998,11 @@ async fn ask(
}
}
fn agent_should_continue(ctx: &RequestContext) -> bool {
ctx.agent.as_ref().is_some_and(|agent| {
agent.auto_continue_enabled() && ctx.auto_continue_count < agent.max_auto_continues()
}) && ctx.todo_list.has_incomplete()
fn should_continue(ctx: &RequestContext) -> bool {
let config = ctx.auto_continue_config();
config.enabled
&& ctx.auto_continue_count < config.max_continues
&& ctx.todo_list.has_incomplete()
}
fn reset_continuation(ctx: &mut RequestContext) {
@@ -1311,13 +1321,15 @@ mod tests {
}
#[test]
fn repl_commands_clear_todo_requires_agent() {
fn repl_commands_clear_todo_always_available() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".clear todo")
.unwrap();
assert!(cmd.is_valid(StateFlags::AGENT));
assert!(!cmd.is_valid(StateFlags::empty()));
assert!(cmd.is_valid(StateFlags::empty()));
assert!(cmd.is_valid(StateFlags::SESSION));
assert!(cmd.is_valid(StateFlags::ROLE));
}
#[test]