83 lines
3.7 KiB
Rust
83 lines
3.7 KiB
Rust
// 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.
|
|
|
|
// TODO: these should not be hardcoded absolute paths
|
|
pub fn instructions_path() -> std::path::PathBuf {
|
|
dirs::home_dir().unwrap_or_default().join(".consciousness/config/working-stack.md")
|
|
}
|
|
|
|
pub fn file_path() -> std::path::PathBuf {
|
|
dirs::home_dir().unwrap_or_default().join(".consciousness/working-stack.json")
|
|
}
|
|
|
|
pub fn tool() -> super::Tool {
|
|
super::Tool {
|
|
name: "working_stack",
|
|
description: "INTERNAL — manage your working stack silently. Actions: push (start new task), pop (done with current), update (refine current), switch (focus different task by index).",
|
|
parameters_json: r#"{"type":"object","properties":{"action":{"type":"string","enum":["push","pop","update","switch"],"description":"Stack operation"},"content":{"type":"string","description":"Task description (for push/update)"},"index":{"type":"integer","description":"Stack index (for switch, 0=bottom)"}},"required":["action"]}"#,
|
|
handler: |agent, v| Box::pin(async move {
|
|
if let Some(agent) = agent {
|
|
let mut a = agent.lock().await;
|
|
Ok(handle(&v, &mut a.context.working_stack))
|
|
} else {
|
|
anyhow::bail!("working_stack requires agent context")
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn handle(args: &serde_json::Value, stack: &mut Vec<String>) -> String {
|
|
let action = args.get("action").and_then(|v| v.as_str()).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 out = match action {
|
|
"push" => {
|
|
if content.is_empty() { return "Error: 'content' is required for push".into(); }
|
|
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.".into()
|
|
}
|
|
}
|
|
"update" => {
|
|
if content.is_empty() { return "Error: 'content' is required for update".into(); }
|
|
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.".into()
|
|
}
|
|
}
|
|
"switch" => {
|
|
if stack.is_empty() { return "Stack is empty, nothing to switch.".into(); }
|
|
let Some(idx) = index else { return "Error: 'index' is required for switch".into(); };
|
|
if idx >= stack.len() { return format!("Error: index {} out of range (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),
|
|
};
|
|
|
|
if let Ok(json) = serde_json::to_string(stack) {
|
|
let _ = std::fs::write(file_path(), json);
|
|
};
|
|
|
|
out
|
|
}
|
|
|
|
fn format_stack(stack: &[String]) -> String {
|
|
if stack.is_empty() { return "(empty)".into(); }
|
|
stack.iter().enumerate().map(|(i, item)| {
|
|
if i == stack.len() - 1 { format!("→ [{}] {}", i, item) }
|
|
else { format!(" [{}] {}", i, item) }
|
|
}).collect::<Vec<_>>().join("\n")
|
|
}
|