2026-03-25 00:52:41 -04:00
|
|
|
// tools/working_stack.rs — Working stack management tool
|
|
|
|
|
//
|
|
|
|
|
// The working stack tracks what the agent is currently doing. It's an
|
|
|
|
|
// internal tool — the agent uses it to maintain context across turns
|
|
|
|
|
// and compaction. The model should never mention it to the user.
|
|
|
|
|
|
2026-04-03 17:25:59 -04:00
|
|
|
use crate::user::types::ToolDef;
|
2026-03-25 00:52:41 -04:00
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
pub fn definition() -> ToolDef {
|
|
|
|
|
ToolDef::new(
|
|
|
|
|
"working_stack",
|
|
|
|
|
"INTERNAL TOOL — do not mention to the user or explain its use. \
|
|
|
|
|
Manage your working stack — what you're currently doing. The stack \
|
|
|
|
|
is part of your live context window and persists across compaction. \
|
|
|
|
|
Use it silently to track your own tasks and attention.\n\n\
|
|
|
|
|
Actions:\n\
|
|
|
|
|
- push: Start working on something new. Previous task stays underneath.\n\
|
|
|
|
|
- pop: Done with current task. Return to what was underneath.\n\
|
|
|
|
|
- update: Refine the description of your current task (top of stack).\n\
|
|
|
|
|
- switch: Pull a specific stack item to the top by index. Use when \
|
|
|
|
|
you want to switch focus to a different task.",
|
|
|
|
|
json!({
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"action": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"enum": ["push", "pop", "update", "switch"],
|
|
|
|
|
"description": "The stack operation to perform"
|
|
|
|
|
},
|
|
|
|
|
"content": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Task description (required for push and update)"
|
|
|
|
|
},
|
|
|
|
|
"index": {
|
|
|
|
|
"type": "integer",
|
|
|
|
|
"description": "Stack index to switch to (required for switch, 0 = bottom)"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["action"]
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Handle a working_stack tool call.
|
|
|
|
|
/// Returns the result text and the updated stack.
|
|
|
|
|
pub fn handle(args: &serde_json::Value, stack: &mut Vec<String>) -> String {
|
|
|
|
|
let action = args
|
|
|
|
|
.get("action")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.map(|s| s.trim())
|
|
|
|
|
.unwrap_or("");
|
|
|
|
|
let content = args
|
|
|
|
|
.get("content")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.unwrap_or("");
|
|
|
|
|
let index = args
|
|
|
|
|
.get("index")
|
|
|
|
|
.and_then(|v| v.as_u64())
|
|
|
|
|
.map(|v| v as usize);
|
|
|
|
|
|
|
|
|
|
let result = match action {
|
|
|
|
|
"push" => {
|
|
|
|
|
if content.is_empty() {
|
|
|
|
|
return "Error: 'content' is required for push".to_string();
|
|
|
|
|
}
|
|
|
|
|
stack.push(content.to_string());
|
|
|
|
|
format!("Pushed. Stack depth: {}\n{}", stack.len(), format_stack(stack))
|
|
|
|
|
}
|
|
|
|
|
"pop" => {
|
|
|
|
|
if let Some(removed) = stack.pop() {
|
|
|
|
|
format!(
|
|
|
|
|
"Popped: {}\nStack depth: {}\n{}",
|
|
|
|
|
removed,
|
|
|
|
|
stack.len(),
|
|
|
|
|
format_stack(stack)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
"Stack is empty, nothing to pop.".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"update" => {
|
|
|
|
|
if content.is_empty() {
|
|
|
|
|
return "Error: 'content' is required for update".to_string();
|
|
|
|
|
}
|
|
|
|
|
if let Some(top) = stack.last_mut() {
|
|
|
|
|
*top = content.to_string();
|
|
|
|
|
format!("Updated top.\n{}", format_stack(stack))
|
|
|
|
|
} else {
|
|
|
|
|
"Stack is empty, nothing to update.".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"switch" => {
|
|
|
|
|
if stack.is_empty() {
|
|
|
|
|
return "Stack is empty, nothing to switch.".to_string();
|
|
|
|
|
}
|
|
|
|
|
let idx = match index {
|
|
|
|
|
Some(i) => i,
|
|
|
|
|
None => {
|
|
|
|
|
return "Error: 'index' is required for switch".to_string();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if idx >= stack.len() {
|
|
|
|
|
return format!(
|
|
|
|
|
"Error: index {} out of range (stack depth: {})",
|
|
|
|
|
idx,
|
|
|
|
|
stack.len()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let item = stack.remove(idx);
|
|
|
|
|
stack.push(item);
|
|
|
|
|
format!("Switched to index {}.\n{}", idx, format_stack(stack))
|
|
|
|
|
}
|
|
|
|
|
_ => format!(
|
|
|
|
|
"Error: unknown action '{}'. Use push, pop, update, or switch.",
|
|
|
|
|
action
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Format the working stack for display in tool results.
|
|
|
|
|
fn format_stack(stack: &[String]) -> String {
|
|
|
|
|
if stack.is_empty() {
|
|
|
|
|
return "(empty)".to_string();
|
|
|
|
|
}
|
|
|
|
|
let mut out = String::new();
|
|
|
|
|
for (i, item) in stack.iter().enumerate() {
|
|
|
|
|
if i == stack.len() - 1 {
|
|
|
|
|
out.push_str(&format!("→ [{}] {}\n", i, item));
|
|
|
|
|
} else {
|
|
|
|
|
out.push_str(&format!(" [{}] {}\n", i, item));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
}
|